0Pricing
C# Academy · Lesson

Route Groups, Parameters & Validation

Organize endpoints with MapGroup, bind route/query/body parameters, and validate with data annotations.

Route Groups, Parameters & Validation is a free C# Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the C# Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Route Groups for Organization

MapGroup() (introduced in .NET 7) lets you group related endpoints under a common prefix and apply shared configuration — middleware, auth policies, tags — without repeating it on each endpoint.

Creating a Route Group

Call app.MapGroup("/api/products") and then map endpoints on the returned group. The prefix is automatically prepended to all routes.

var products = app.MapGroup("/api/products").WithTags("Products");

products.MapGet("/",     GetAll);
products.MapGet("/{id}", GetById);
products.MapPost("/",    Create);
products.MapPut("/{id}", Update);
products.MapDelete("/{id}", Delete);
// Routes: /api/products, /api/products/{id}

Applying Auth to a Group

Use RequireAuthorization() on the group to protect all endpoints at once. Individual endpoints can still override with AllowAnonymous().

var adminGroup = app.MapGroup("/admin")
    .RequireAuthorization("AdminPolicy")
    .WithTags("Admin");

adminGroup.MapGet("/users",    GetAllUsers);
adminGroup.MapDelete("/users/{id}", DeleteUser);

// This one inside the group opts out
adminGroup.MapGet("/status", () => "OK").AllowAnonymous();

Route Parameter Constraints

Constrain route parameters with type patterns. The router rejects requests that don't match, returning 404 automatically.

// Only matches numeric IDs
app.MapGet("/products/{id:int}", (int id) => id);

// GUID constraint
app.MapGet("/orders/{orderId:guid}", (Guid orderId) => orderId);

// Length constraint
app.MapGet("/codes/{code:length(6)}", (string code) => code);

// Min value
app.MapGet("/page/{page:min(1)}", (int page) => page);

Binding from Different Sources

Parameters are bound from different sources using attributes. By default, simple types come from route/query and complex types from the body.

app.MapPost("/search", (
    [FromQuery] string q,
    [FromQuery] int page,
    [FromHeader(Name = "X-Api-Version")] string version,
    [FromBody] SearchFilters filters,
    AppDbContext db) =>
{
    // q and page from query string
    // version from request header
    // filters from JSON body
    return Results.Ok();
});

Custom Parameter Binding with TryParse

For custom types used as route or query parameters, implement a static TryParse method. The framework calls it automatically to parse the string value.

public record ProductFilter(string? Category, decimal? MinPrice)
{
    public static bool TryParse(string value, out ProductFilter result)
    {
        var parts = value.Split(':');
        result = new ProductFilter(
            parts.Length > 0 ? parts[0] : null,
            parts.Length > 1 && decimal.TryParse(parts[1], out var p) ? p : null);
        return true;
    }
}

// Usage: GET /products?filter=Electronics:50
app.MapGet("/products", ([FromQuery] ProductFilter filter) => filter);

Data Annotation Validation

Decorate your request DTOs with data annotation attributes. In Minimal APIs, use the IValidatableObject interface or a validation filter to trigger validation.

using System.ComponentModel.DataAnnotations;

record CreateProductRequest
{
    [Required, MaxLength(200)]
    public string Name { get; init; } = "";

    [Range(0.01, 999999)]
    public decimal Price { get; init; }

    [Range(0, int.MaxValue)]
    public int Stock { get; init; }
}

Endpoint Filters for Validation

Endpoint filters run before/after the handler. Use them to add model validation — rejecting invalid requests before they reach your business logic.

app.MapPost("/products", CreateProduct)
   .AddEndpointFilter(async (ctx, next) =>
   {
       var req = ctx.GetArgument<CreateProductRequest>(0);
       var errors = new List<ValidationResult>();
       if (!Validator.TryValidateObject(req,
               new ValidationContext(req), errors, true))
       {
           return Results.ValidationProblem(
               errors.ToDictionary(e => e.MemberNames.First(),
                                   e => new[] { e.ErrorMessage! }));
       }
       return await next(ctx);
   });

FluentValidation Integration

For complex validation rules, use FluentValidation with the SharpGrip.FluentValidation.AutoValidation.Endpoints package for automatic validation in Minimal APIs.

public class CreateProductValidator : AbstractValidator<CreateProductRequest>
{
    public CreateProductValidator()
    {
        RuleFor(x => x.Name).NotEmpty().MaximumLength(200);
        RuleFor(x => x.Price).GreaterThan(0);
        RuleFor(x => x.Stock).GreaterThanOrEqualTo(0);
    }
}

builder.Services.AddFluentValidationAutoValidation();
builder.Services.AddValidatorsFromAssemblyContaining<CreateProductValidator>();

Nested Route Groups

Route groups can be nested to model hierarchical resources, like orders containing line items.

var api = app.MapGroup("/api").RequireAuthorization();

var orders = api.MapGroup("/orders");
orders.MapGet("/", GetAllOrders);
orders.MapGet("/{id:int}", GetOrder);

// Nested: /api/orders/{orderId}/lines
var lines = orders.MapGroup("/{orderId:int}/lines");
lines.MapGet("/",        GetOrderLines);
lines.MapPost("/",       AddOrderLine);
lines.MapDelete("/{id}", RemoveOrderLine);

Real-World: Versioned API Group

Group endpoints by API version to support side-by-side versioning without duplicating auth or tag configuration.

var v1 = app.MapGroup("/api/v1")
    .RequireAuthorization()
    .WithTags("v1");

var v2 = app.MapGroup("/api/v2")
    .RequireAuthorization()
    .WithTags("v2");

v1.MapGet("/products", GetProductsV1);
v2.MapGet("/products", GetProductsV2); // richer response

Quick Check

What is the purpose of MapGroup() in Minimal APIs?

Recap: Route Groups, Parameters & Validation

Key takeaways:

  • MapGroup(): share prefix and config across multiple endpoints
  • Route constraints (:int, :guid, :min) auto-reject non-matching routes
  • [FromQuery], [FromHeader], [FromBody] for explicit binding source
  • Implement TryParse on custom types for automatic route/query binding
  • Endpoint filters enable cross-cutting concerns like validation

Frequently asked questions

Is the “Route Groups, Parameters & Validation” lesson free?

Yes — the full text of “Route Groups, Parameters & Validation” is free to read here on the web, and the C# Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the C# Academy course, upgrade to CoddyKit PRO.

What will I learn in “Route Groups, Parameters & Validation”?

Organize endpoints with MapGroup, bind route/query/body parameters, and validate with data annotations. You practise C# Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start C# Academy?

No prior experience is required. C# Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Route Groups, Parameters & Validation” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this C# Academy lesson?

Yes. Every C# Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Creating Your First Minimal API
  2. Route Groups, Parameters & Validation
  3. Middleware & Filters in Minimal APIs
  4. OpenAPI, Versioning & Deployment
← Back to C# Academy