Das Verständnis der Kernanforderungs- und Reaktionspipeline von ASP.. ]- ? Part ?4: ? Routing and Endpoints (Deutsch (German))

Das Verständnis der Kernanforderungs- und Reaktionspipeline von ASP.. ]- ? Part ?4: ? Routing and Endpoints

Sunday, 09 November 2025

//

14 minute read

Einleitung

Wir reisten durch die Hosting-Layer., , erforscht Kestrel ,, , und gemasterte Middleware , . , Jetzt kommen wir zu einer entscheidenden Frage , sobald eine Anfrage macht es durch Ihre Middleware Pipeline , wie ASP , . ,NET Core wissen, welcher Code auszuführen , um ? , Die Antwort ist Routing und Endpunkte.

Routing ist der Mechanismus, der eingehende HTTP-Anfragen zu ausführbarem Code zusammenpasst..-Endpunkte sind die Zielorte. —Der eigentliche Code, der zusammen ausgeführt wird.MS K3, sie bilden ein leistungsstarkes flexibles System, das alles unterstützt, von einfachen Routenbearbeitern bis hin zu komplexen MVC-Anwendungen.

In diesem Teil erfahren wir, wie das Routing funktioniert, wie Endpunkte registriert und ausgeführt werden und wie Sie dieses System nutzen können, um anspruchsvolle Anwendungen zu erstellen.

HINWEIS:-Dies ist Teil meiner Experimente mit KI-/-ein Weg, um zu verbringen $1000-Calude Code Web-credits..-I'-ve gefüttert dies ein BUNCH von Papieren...,-mein Verständnis Fragen Ich musste diesen Artikel generieren.-Es macht Spaß und füllt eine Lücke ich habe '-t überall sonst gefüllt gesehen

Die Evolution des Routing

ASP-.-NET Core-'-Routing hat sich signifikant weiterentwickelt

timeline
    title ASP.NET Core Routing Evolution
    ASP.NET Core 1.0 - 2.2 : Traditional Routing : Routes defined in middleware : MapRoute for MVC
    ASP.NET Core 3.0 - 3.1 : Endpoint Routing : UseRouting + UseEndpoints : Routing metadata aware
    ASP.NET Core 6.0+ : Minimal APIs : Direct MapGet/MapPost : Simplified registration

Traditionelles Routing

app.UseMvc(routes =>
{
    routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}");
});

Endpunkt-Routing

app.UseRouting();
app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
});

Moderne Minimal-APIs

app.MapGet("/users/{id}", (int id) => $"User {id}");

Wie das Routing funktioniert, der Zwei---Phasen-Prozess

Endpoint-Routing funktioniert in zwei Phasen

graph LR
    A[Request] --> B[Routing Middleware]
    B --> C{Match Route?}
    C -->|Yes| D[Set Endpoint]
    C -->|No| E[No Endpoint Set]
    D --> F[Authorization Middleware]
    E --> F
    F --> G[Other Middleware]
    G --> H[Endpoint Middleware]
    H --> I{Endpoint Exists?}
    I -->|Yes| J[Execute Endpoint]
    I -->|No| K[404 Not Found]

    style B stroke:#ef4444,stroke-width:3px
    style H stroke:#10b981,stroke-width:3px
    style J stroke:#6366f1,stroke-width:3px

Phase der Route, die zusammenpasstUseRouting())

Die Routing-Middleware untersucht die Anfrage und versucht, sie an einen registrierten Endpunkt anzupassen.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// Phase 1: Route matching happens here
app.UseRouting();

// Between routing and endpoints, you can use middleware that needs route information
app.Use(async (context, next) =>
{
    var endpoint = context.GetEndpoint();
    if (endpoint != null)
    {
        Console.WriteLine($"Matched endpoint: {endpoint.DisplayName}");

        // Access route values
        var routeValues = context.Request.RouteValues;
        foreach (var (key, value) in routeValues)
        {
            Console.WriteLine($"  {key} = {value}");
        }
    }

    await next(context);
});

app.UseAuthorization(); // Can make decisions based on matched endpoint

// Phase 2: Endpoint execution happens here
app.MapGet("/users/{id}", (int id) => $"User {id}");

app.Run();

Strömungsdiagramm (MSK)

sequenceDiagram
    participant Request
    participant Routing as UseRouting
    participant Auth as UseAuthorization
    participant Endpoint as Endpoint Middleware

    Request->>Routing: GET /users/123
    Note over Routing: Parse URL<br/>Match against patterns<br/>Extract route values
    Routing->>Routing: Found match: /users/{id}
    Routing->>Routing: Set endpoint metadata<br/>Set route values {id: 123}

    Routing->>Auth: Continue with endpoint set
    Note over Auth: Can check endpoint metadata<br/>for [Authorize] attributes

    Auth->>Endpoint: Continue
    Note over Endpoint: Endpoint is set
    Endpoint->>Endpoint: Execute matched endpoint<br/>with route values

    Endpoint->>Request: Return response

Phase der Endpoint-Execution

Die Endpunkt-Middleware führt den abgestimmten Endpunkt aus.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseRouting();

// This middleware runs BEFORE endpoint execution
app.Use(async (context, next) =>
{
    Console.WriteLine("Before endpoint execution");
    await next(context);
    Console.WriteLine("After endpoint execution");
});

// Endpoint execution happens here
app.MapGet("/", () =>
{
    Console.WriteLine("Executing endpoint");
    return "Hello World";
});

app.Run();

Ausgabe„ :"

Before endpoint execution
Executing endpoint
After endpoint execution

Routenvorlagen

Routenvorlagen definieren das URL-Muster, um mit dem Muster übereinzustimmen.

Grundlegende Vorlagen

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// Literal path
app.MapGet("/", () => "Home page");

// Single parameter
app.MapGet("/users/{id}", (int id) => $"User {id}");

// Multiple parameters
app.MapGet("/posts/{year}/{month}/{day}", (int year, int month, int day) =>
    $"Posts from {year}-{month:D2}-{day:D2}");

// Optional parameter
app.MapGet("/products/{id?}", (int? id) =>
    id.HasValue ? $"Product {id}" : "All products");

// Default value
app.MapGet("/search/{query=all}", (string query) =>
    $"Searching for: {query}");

// Catch-all parameter
app.MapGet("/files/{*path}", (string path) =>
    $"File path: {path}");

app.Run();

Beispiele

GET /                           → "Home page"
GET /users/123                  → "User 123"
GET /posts/2024/1/15            → "Posts from 2024-01-15"
GET /products                   → "All products"
GET /products/42                → "Product 42"
GET /search                     → "Searching for: all"
GET /search/aspnet              → "Searching for: aspnet"
GET /files/docs/guide.pdf       → "File path: docs/guide.pdf"

Streckenbeschränkungen

Einschränkungen stellen sicher, dass Parameter mit spezifischen Mustern übereinstimmen.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// Integer constraint
app.MapGet("/users/{id:int}", (int id) =>
    $"User {id}");

// Minimum value
app.MapGet("/products/{id:int:min(1)}", (int id) =>
    $"Product {id}");

// Range
app.MapGet("/items/{id:int:range(1,100)}", (int id) =>
    $"Item {id}");

// String length
app.MapGet("/codes/{code:length(5)}", (string code) =>
    $"Code {code}");

// Min/max length
app.MapGet("/names/{name:minlength(2):maxlength(20)}", (string name) =>
    $"Name {name}");

// Regex
app.MapGet("/posts/{slug:regex(^[a-z0-9-]+$)}", (string slug) =>
    $"Post slug: {slug}");

// GUID
app.MapGet("/orders/{id:guid}", (Guid id) =>
    $"Order {id}");

// DateTime
app.MapGet("/appointments/{date:datetime}", (DateTime date) =>
    $"Appointment on {date:yyyy-MM-dd}");

// Alpha (letters only)
app.MapGet("/tags/{tag:alpha}", (string tag) =>
    $"Tag: {tag}");

// Multiple constraints (AND)
app.MapGet("/archive/{year:int:min(2000):max(2030)}", (int year) =>
    $"Archive for {year}");

app.Run();

Eingebaute --in Einschränkungen

ZUSAMMENFASSUNG |------------|---------|---------| | int | {id:int} | 123, -456 | | bool | {active:bool} | true, , false, | . | datetime | {date:datetime} | 2024-01-15 | | decimal | {price:decimal} | 19.99 | | double | {lat:double} | 51.5074 | | float | {temp:float} | 98.6 | | guid | {id:guid} |-550-8400--29--41-d4--716-446655440000-|-m | long | {size:long} | 9223372036854775807 | | minlength(n) | {name:minlength(3)} |-Abc-,-abcd-|-Abk | maxlength(n) | {name:maxlength(5)} |-Abc-,-abcde-Abd-|- | length(n) | {code:length(5)} |-Abcde-Abkde | min(n) | {age:min(18)} | 18, 19, 100 | | max(n) | {age:max(120)} | 1, 100, 120 | | range(min,max) | {num:range(1,10)} | 1, 5, 10 | | alpha | {tag:alpha} |-abc-,-XYZ-Xyz-|- | regex(pattern) | {code:regex(^[A-Z]{3}$)} ,| , ABC-, , XYZ , | , | required | {id:required} Nicht--Leere Werte

Prüfungsbeschränkungen (MSK)

# ✅ Matches {id:int}
GET /users/123          → Success

# ❌ Doesn't match {id:int}
GET /users/abc          → 404

# ✅ Matches {id:int:min(1)}
GET /products/5         → Success

# ❌ Doesn't match {id:int:min(1)}
GET /products/0         → 404
GET /products/-1        → 404

Benutzerdefinierte Routenbeschränkungen

Erstellen Sie Ihre eigenen Einschränkungen

// Custom constraint: validates slugs
public class SlugConstraint : IRouteConstraint
{
    private readonly Regex _regex = new Regex(@"^[a-z0-9]+(?:-[a-z0-9]+)*$",
        RegexOptions.Compiled | RegexOptions.IgnoreCase);

    public bool Match(HttpContext? httpContext, IRouter? route, string routeKey,
        RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (values.TryGetValue(routeKey, out var value) && value != null)
        {
            var slug = value.ToString();
            return !string.IsNullOrEmpty(slug) && _regex.IsMatch(slug);
        }

        return false;
    }
}

// Register the constraint
var builder = WebApplication.CreateBuilder(args);

builder.Services.Configure<RouteOptions>(options =>
{
    options.ConstraintMap.Add("slug", typeof(SlugConstraint));
});

var app = builder.Build();

// Use the custom constraint
app.MapGet("/blog/{slug:slug}", (string slug) =>
    $"Blog post: {slug}");

app.Run();

Testen von :

GET /blog/my-first-post          → ✅ "Blog post: my-first-post"
GET /blog/hello-world-2024       → ✅ "Blog post: hello-world-2024"
GET /blog/My_Invalid_Slug        → ❌ 404
GET /blog/has spaces             → ❌ 404

Routenprioritäten und Ordnung

Wenn mehrere Routen zusammenpassen, verwendet ASP-.-NET Core ein Prioritätssystem.

graph TD
    A[Incoming Request] --> B{Route Matching}
    B --> C[Order by Priority]
    C --> D[ Literal segments highest]
    D --> E[ Constrained parameters]
    E --> F[ Unconstrained parameters]
    F --> G[ Optional parameters]
    G --> H[ Catch-all parameters lowest]
    H --> I[Select first match]


Beispiel:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// Priority 1: Literal segments (highest priority)
app.MapGet("/users/admin", () => "Admin user endpoint");

// Priority 2: Constrained parameter
app.MapGet("/users/{id:int}", (int id) => $"User {id}");

// Priority 3: Unconstrained parameter
app.MapGet("/users/{username}", (string username) => $"User @{username}");

// Priority 4: Optional parameter
app.MapGet("/users/{id:int?}", (int? id) =>
    id.HasValue ? $"User {id}" : "All users");

// Priority 5: Catch-all (lowest priority)
app.MapGet("/users/{*path}", (string path) => $"Catch-all: {path}");

app.Run();

Passendes Verhalten

GET /users/admin        → "Admin user endpoint" (literal match)
GET /users/123          → "User 123" (constrained parameter)
GET /users/john         → "User @john" (unconstrained parameter)
GET /users/admin/test   → "Catch-all: admin/test" (catch-all)

Sie können auch explizite Reihenfolge setzen

app.MapGet("/products/{id}", (int id) => $"Product {id}")
    .WithOrder(1);

app.MapGet("/products/featured", () => "Featured products")
    .WithOrder(0); // Lower number = higher priority

Parameterbindung

ASP-.-NET Core kann Routenparameter aus mehreren Quellen binden

Streckenwerte

app.MapGet("/users/{id}/posts/{postId}", (int id, int postId) =>
    $"User {id}, Post {postId}");

Abfrage-Zeichenkette

app.MapGet("/search", (string? q, int page = 1, int pageSize = 10) =>
    $"Query: {q}, Page: {page}, Size: {pageSize}");

// GET /search?q=aspnet&page=2&pageSize=20

Kopfzeilen

app.MapGet("/api/data", ([FromHeader(Name = "X-API-Key")] string apiKey) =>
    $"API Key: {apiKey}");

Körper

app.MapPost("/users", ([FromBody] User user) =>
{
    return Results.Created($"/users/{user.Id}", user);
});

public record User(int Id, string Name, string Email);

Dienstleistungen

app.MapGet("/data", (IMyService service) =>
{
    var data = service.GetData();
    return data;
});

HttpContext

app.MapGet("/info", (HttpContext context) =>
{
    var userAgent = context.Request.Headers["User-Agent"];
    var ip = context.Connection.RemoteIpAddress;

    return new
    {
        UserAgent = userAgent.ToString(),
        IpAddress = ip?.ToString()
    };
});

Komplexes Bindebeispiel

public record CreatePostRequest(string Title, string Content, List<string> Tags);

app.MapPost("/api/posts",
    async (
        [FromRoute] int userId,
        [FromQuery] bool publish,
        [FromBody] CreatePostRequest request,
        [FromHeader(Name = "X-Request-ID")] string requestId,
        [FromServices] IPostService postService,
        HttpContext context) =>
    {
        var post = await postService.CreatePostAsync(
            userId,
            request.Title,
            request.Content,
            request.Tags,
            publish
        );

        context.Response.Headers["X-Request-ID"] = requestId;

        return Results.Created($"/api/posts/{post.Id}", post);
    });

// POST /api/posts?publish=true
// Header: X-Request-ID: abc123
// Body: { "title": "Hello", "content": "World", "tags": ["aspnet", "routing"] }

Endpunkt-Metadaten

Endpunkte können Metadaten haben, die sie beschreiben

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthorization();

var app = builder.Build();

app.UseAuthorization();

// Add metadata with extension methods
app.MapGet("/public", () => "Public endpoint")
    .WithName("GetPublic")
    .WithDisplayName("Public Endpoint")
    .WithDescription("A public endpoint that anyone can access")
    .WithTags("Public");

app.MapGet("/private", () => "Private endpoint")
    .RequireAuthorization() // Adds [Authorize] metadata
    .WithName("GetPrivate");

app.MapGet("/admin", () => "Admin only")
    .RequireAuthorization("AdminPolicy");

// Access metadata
app.Map("/metadata", (IEndpointRouteBuilder endpoints) =>
{
    var dataSources = endpoints.DataSources;

    var endpointList = dataSources
        .SelectMany(ds => ds.Endpoints)
        .OfType<RouteEndpoint>()
        .Select(e => new
        {
            Name = e.Metadata.GetMetadata<IEndpointNameMetadata>()?.EndpointName,
            DisplayName = e.DisplayName,
            Route = e.RoutePattern.RawText,
            RequiresAuth = e.Metadata.GetMetadata<IAuthorizeData>() != null
        });

    return endpointList;
});

app.Run();

Ausgabe von /metadata:

[
  {
    "name": "GetPublic",
    "displayName": "Public Endpoint",
    "route": "/public",
    "requiresAuth": false
  },
  {
    "name": "GetPrivate",
    "displayName": null,
    "route": "/private",
    "requiresAuth": true
  },
  {
    "name": null,
    "displayName": null,
    "route": "/admin",
    "requiresAuth": true
  }
]

Streckengruppen

Gruppenbezogene Endpunkte mit geteilter Konfiguration

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// Create a group with common prefix
var api = app.MapGroup("/api");

// All these routes are prefixed with /api
api.MapGet("/users", () => "Get all users");
api.MapGet("/users/{id}", (int id) => $"Get user {id}");
api.MapPost("/users", () => "Create user");

// Nested groups
var v1 = api.MapGroup("/v1");
v1.MapGet("/products", () => "V1 products");

var v2 = api.MapGroup("/v2");
v2.MapGet("/products", () => "V2 products");

// Groups with shared metadata
var adminApi = app.MapGroup("/admin")
    .RequireAuthorization("AdminPolicy")
    .WithTags("Admin");

adminApi.MapGet("/users", () => "Admin: Get users");
adminApi.MapDelete("/users/{id}", (int id) => $"Admin: Delete user {id}");

app.Run();

Geschaffene Routen

GET    /api/users
GET    /api/users/{id}
POST   /api/users
GET    /api/v1/products
GET    /api/v2/products
GET    /admin/users          [Authorize(AdminPolicy)]
DELETE /admin/users/{id}     [Authorize(AdminPolicy)]

Visualisierung

flowchart TD
    Root[Root] --> Api[api]
    Root --> Admin[admin auth]

    Api --> Users1[GET users]
    Api --> Users2[GET users id]
    Api --> Users3[POST users]
    Api --> V1[v1]
    Api --> V2[v2]

    V1 --> V1Products[GET products]
    V2 --> V2Products[GET products]

    Admin --> AdminUsers[GET users]
    Admin --> AdminDelete[DELETE users id]

Erweiterte Routing-Szenarien

Inhalt Verhandlungen auf der Route

app.MapGet("/data.json", () => Results.Json(new { data = "JSON" }));
app.MapGet("/data.xml", () => Results.Text("<data>XML</data>", "application/xml"));
app.MapGet("/data", (HttpContext context) =>
{
    var accept = context.Request.Headers["Accept"].ToString();

    if (accept.Contains("application/json"))
        return Results.Json(new { data = "JSON" });

    if (accept.Contains("application/xml"))
        return Results.Text("<data>XML</data>", "application/xml");

    return Results.Json(new { data = "Default JSON" });
});

Lokalisierte Routen

// English routes
app.MapGet("/en/products", () => "Products");
app.MapGet("/en/about", () => "About Us");

// Spanish routes
app.MapGet("/es/productos", () => "Productos");
app.MapGet("/es/acerca", () => "Acerca de Nosotros");

// Dynamic culture from route
app.MapGet("/{culture}/home", (string culture) =>
{
    CultureInfo.CurrentCulture = new CultureInfo(culture);
    return $"Home (Culture: {culture})";
});

Versionierte APIs

// Version in route
var v1 = app.MapGroup("/api/v1");
v1.MapGet("/users", () => new { version = 1, users = new[] { "Alice", "Bob" } });

var v2 = app.MapGroup("/api/v2");
v2.MapGet("/users", () => new { version = 2, users = new[]
{
    new { id = 1, name = "Alice" },
    new { id = 2, name = "Bob" }
}});

// Version in query string
app.MapGet("/api/users", (int version = 1) =>
{
    if (version == 2)
        return Results.Json(new { version = 2, users = new[]
        {
            new { id = 1, name = "Alice" },
            new { id = 2, name = "Bob" }
        }});

    return Results.Json(new { version = 1, users = new[] { "Alice", "Bob" } });
});

// Version in header
app.MapGet("/api/data", (HttpContext context) =>
{
    var version = context.Request.Headers["X-API-Version"].FirstOrDefault() ?? "1";
~~~~
    return version switch
    {
        "2" => Results.Json(new { version = 2, data = "New format" }),
        _ => Results.Json(new { version = 1, data = "Old format" })
    };
});

Subdomain Routing

app.Use(async (context, next) =>
{
    var host = context.Request.Host.Host;

    if (host.StartsWith("api."))
    {
        context.Request.RouteValues["area"] = "api";
    }
    else if (host.StartsWith("admin."))
    {
        context.Request.RouteValues["area"] = "admin";
    }

    await next(context);
});

app.MapGet("/", (HttpContext context) =>
{
    var area = context.Request.RouteValues["area"]?.ToString() ?? "main";

    return area switch
    {
        "api" => "API area",
        "admin" => "Admin area",
        _ => "Main area"
    };
});

Streckendebugging

Alle registrierten Routen anzeigen

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/users", () => "Users");
app.MapPost("/users", () => "Create user");
app.MapGet("/products/{id:int}", (int id) => $"Product {id}");

// Endpoint to inspect all routes
app.MapGet("/routes", (IEnumerable<EndpointDataSource> endpointSources) =>
{
    var endpoints = endpointSources
        .SelectMany(es => es.Endpoints)
        .OfType<RouteEndpoint>();

    var routes = endpoints.Select(e => new
    {
        Name = e.Metadata.GetMetadata<IEndpointNameMetadata>()?.EndpointName,
        Pattern = e.RoutePattern.RawText,
        Order = e.Order,
        HttpMethods = e.Metadata.GetMetadata<IHttpMethodMetadata>()?.HttpMethods,
        Metadata = e.Metadata.Select(m => m.GetType().Name)
    });

    return Results.Json(routes);
});

app.Run();

Ausgabe von /routes:

[
  {
    "name": null,
    "pattern": "/users",
    "order": 0,
    "httpMethods": ["GET"],
    "metadata": ["HttpMethodMetadata", ...]
  },
  {
    "name": null,
    "pattern": "/users",
    "order": 0,
    "httpMethods": ["POST"],
    "metadata": ["HttpMethodMetadata", ...]
  },
  {
    "name": null,
    "pattern": "/products/{id:int}",
    "order": 0,
    "httpMethods": ["GET"],
    "metadata": ["HttpMethodMetadata", "RoutePatternMetadata", ...]
  }
]

Leistungsbeurteilungen

Routen-Caching

// Routes are compiled and cached at startup
// This is fast:
for (int i = 0; i < 1000; i++)
{
    app.MapGet($"/endpoint{i}", () => $"Endpoint {i}");
}

// At runtime, route matching is O(1) for most cases

Erzeugung von Verknüpfungen

URLs von Routen generieren

app.MapGet("/users/{id}", (int id) => $"User {id}")
    .WithName("GetUser");

app.MapGet("/generate-link", (LinkGenerator linkGenerator, HttpContext context) =>
{
    var url = linkGenerator.GetPathByName("GetUser", new { id = 123 });
    // url = "/users/123"

    var absoluteUrl = linkGenerator.GetUriByName(context, "GetUser", new { id = 123 });
    // absoluteUrl = "https://localhost:5001/users/123"

    return new { url, absoluteUrl };
});

Schlüssel zum Mitnehmen

  • Routing ist ein Zwei---Phasen-Prozess, der mit : zusammenpasst.
  • Routenvorlagen unterstützen Parameter ,, , Constraints , , , optionale Werte , , , und fangen - , alle
  • Built--'in Einschränkungen validieren Parametertypen und Muster
  • Benutzerdefinierte Einschränkungen bieten anwendungsspezifische Validierung
  • Routenpriorität: :-Wörterliche Parameter: >-Beschränkte Parameter: >-Uneingeschränkte , >-Optional ,>-Fang
  • Parameter können von routes-,-Abfrage string-MSC1-Headern und Dienstleistungen gebunden werden
  • Endpoint Metadaten ermöglichen Funktionen wie Autorisierung und Dokumentation
  • Routengruppen vereinfachen die Konfiguration für verwandte Endpunkte
  • Linkgenerierung erzeugt URLs aus Routennamen
  • Das Routing-System ist hoch optimiert für die Leistung

Routing und Endpunkte bilden die Brücke zwischen HTTP-Anfragen und Ihrem Anwendungscode..-Mastering dieses Systems gibt Ihnen genaue Kontrolle darüber, wie URLs zur Funktionalität ordnen.

Finding related posts...
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.