Basic Usage

AspNetConventions is designed to be zero-effort implementation. This guide explains the core workflow from initialization to final production build, ensuring your application enforces consistent API conventions across your entire ASP.NET Core ecosystem.

MVC Controllers

When using MVC Controllers, the library integrates .AddAspNetConventions() with the IMvcBuilder to inject global action filters and route token transformers. This ensures that every controller and action follows your specified naming conventions without requiring manual [Route] overrides on every method.

Your Code

    [ApiController]
    [Route("api/[controller]")]
    public class ProfileController : ControllerBase
    {
        [HttpGet("[action]/{UserId:int}")]
        public ActionResult GetUser(int UserId) => Ok(new { id = UserId });
    
        [HttpPut("CreateAccount")]
        public ActionResult CreateUser([FromBody] CreateAccountRequest request) 
        {
            // Use ApiResults class to return a "201 Created" status code with custom message.
            return ApiResults.Created(request, "User created successfully.");
        }
    }

See ApiResults for more information about custom standardized responses.

Route Transformation

The following table demonstrates how the library automatically transforms standard PascalCase names into clean, SEO-friendly paths.

Original Standardized
GET /api/Profile/GetUser/{UserId:int} GET /api/profile/get-user/{user-id:int}
POST /api/Profile/CreateAccount POST /api/profile/create-account

Customizing the Route Style

If you prefer a different naming convention, such as snake_case or remove prefixes/suffixes on controller/action names, you can define it globally during service registration.

builder.Services
    .AddControllers()
    .AddAspNetConventions(options =>
    {
        // Result: GET /api/profile/user/{user_id:int}
        options.Route.CaseStyle = CasingStyle.SnakeCase;
        options.Route.Controllers.RemoveActionPrefixes.Add("Get");
    });

See RouteConventionOptions and ControllerRouteOptions for more information about MVC Controllers route customization.


Minimal APIs

For Minimal APIs, the library provides a .UseAspNetConventions() extension for WebApplication that returns a RouteGroupBuilder. Map your endpoints on the returned group to apply conventions to a specific branch of your API tree or the entire application.

Your Code

var api = app.UseAspNetConventions();

api.MapGet("/WeatherForecast/{city}", (string city) =>
    Results.Ok(new { city }));

api.MapGet("/GetUserById/{userId}", (int userId) =>
    Results.Ok(new { id = userId }));

Route Transformation

In Minimal APIs, route segments are transformed, but parameters are kept stable by default to ensure binding remains intact.

Original Standardized
GET /WeatherForecast/{city} GET /weather-forecast/{city}
GET /GetUserById/{userId} GET /get-user-by-id/{userId}
Important: Parameter Binding in Minimal APIs

Minimal APIs handle parameter binding strictly by name. If the library automatically transforms a route parameter like {userId} to {user-id}, the underlying .NET binder will fail to locate the value unless explicitly told where to look.

Enabling Parameter Transformation
To enable automatic casing for Minimal API route parameters, toggle this feature in your configuration:

app.UseAspNetConventions(options => {
    // Default is "false" to prevent binding breaks
    options.Route.MinimalApi.TransformRouteParameters = true;
});

The Solution: Explicit Binding
When transformation is enabled, you must use the [FromRoute(Name = "...")] attribute to manually map the transformed segment name back to your C# parameter:

// The URL becomes: /user-account/{user-id}
api.MapGet("/UserAccount/{userId}", ([FromRoute(Name = "user-id")] int userId) => {
    // Parameter binding works
    return Results.Ok(new { userId });
});

See MinimalApiRouteOptions for more information about Minimal APIs route customization.


Razor Pages

Razor Pages often suffer from inconsistent URL structures due to folder nesting. AspNetConventions automatically transforms physical file paths and route parameters into a consistent casing style, making your UI routes match your API routes.

Your Code

The transformation applies to the page path and any parameters defined in OnGet / OnPost method signatures or @page directives. Additionally, it can transform properties marked with [BindProperty].

// Pages/UserProfile/EditAddress.cshtml.cs
public class EditAddressModel : PageModel
{
    [BindProperty(SupportsGet = true)]
    public string? ZipCode { get; set; }

    public void OnGet(int UserId, int AddressId) { }
}

Route Transformation

State Route Example
Original GET /UserProfile/EditAddress/{UserId}/{AddressId}?ZipCode=...
Standardized GET /user-profile/edit-address/{user-id}/{address-id}?zip-code=...

Disabling Property Transformation

If you need to keep property names in their original C# casing (e.g., for compatibility with external integrations), you can disable property-level transformation.

builder.Services
    .AddRazorPages()
    .AddAspNetConventions(e =>
        e.Route.RazorPages.TransformPropertyNames = false
    );

See RazorPagesRouteOptions for more information about razor page route customization.


Response Standardization Examples

A core feature of the library is the Uniform Response Envelope. Whether an endpoint succeeds or fails, the client receives a predictable JSON structure containing the data, status information, and useful debugging metadata.

Success Response

Successful requests are wrapped in a success status with a 2xx code and a metadata object for request tracking.

{
    "status": "success",
    "statusCode": 201,
    "message": "User created successfully.",
    "data": {
        "userId": 1,
        "name": "John Doe"
    },
    "metadata": {
        "requestType": "PUT",
        "timestamp": "0000-00-00T00:00:00.000000Z",
        "traceId": "00-ed89d1cc507c35126d6f0e933984f774-99b8b9a3feb75652-00",
        "path": "/api/users/{id}"
    }
}

Validation Error Response

Errors are categorized by type (e.g., VALIDATION_ERROR) and include a detailed errors array that maps specific fields to their failure reasons.

{
    "status": "failure",
    "statusCode": 400,
    "type": "VALIDATION_ERROR",
    "message": "One or more validation errors occurred.",
    "errors": [
        {
            "Email": ["'Email' is not a valid email address."]
        }
    ],
    "metadata": {
        "requestType": "PUT",
        "timestamp": "0000-00-00T00:00:00.000000Z",
        "traceId": "00-8e5513ae9369648487c2323d9a3508aa-2a8f92c7d45d3f74-00",
        "path": "/api/users/{id}"
    }
}

Global Response Configuration

You can control the verbosity of your responses globally, such as hiding metadata in production or changing default messages.

builder.Services.AddControllers()
    .AddAspNetConventions(options =>
    {
        // Change Json serialization to "snake_case"
        options.Json.CaseStyle = CasingStyle.SnakeCase;

        // Hide metadata for cleaner responses
        options.Response.IncludeMetadata = false;
        options.Response.Pagination.DefaultPageSize = 50;
        options.Response.ErrorResponse.DefaultValidationMessage = "Check your input.";
    });

See ResponseFormattingOptions and JsonSerializationOptions for more information about response and json serialization customization.

Next Steps

Explore the deep-dive documentation for each specific feature to customize the behavior to your needs:

Route Standardization

Exception Handling

Response Formatting

JSON Serialization