• .NET 云原生架构师训练营(模块二 基础巩固 路由与终结点)--学习笔记


    2.3.3 Web API -- 路由与终结点

    • 路由模板
    • 约定路由
    • 特性路由
    • 路由冲突
    • 终结点

    ASP.NET Core 中的路由:https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/routing?view=aspnetcore-5.0

    UseRouting 添加路由中间件到管道,路由中间件用来匹配 url 和具体的 endpoint,然后执行 endpoint

    UseEndpoints 添加或者注册 endpoint 到程序中,使得路由中间件可以发现它们

    • MapRazorPages for Razor Pages 添加所有 Razor Pages 终结点
    • MapControllers for controllers 添加所有 controller 终结点
    • MapHub for SignalR 添加 SignalR 终结点
    • MapGrpcService for gRPC 添加 gRPC 终结点

    路由模板

    路由模板由 token 和其他特定字符组成。比如“/”,特定字符进行路由匹配的时候必须全部匹配

    /hello/{name:alpha}

    {name:alpha} 是一段 token,一段 token 包括一个参数名,可以跟着一个约束(alpha)或者一个默认值(mingson),比如 {name=mingson} ,或者直接 {name}

    app.UseEndpoints(endpoints =>
    {
        //endpoints.MapControllers();
        endpoints.MapGet("/hello/{name:alpha}", async context =>
        {
            var name = context.Request.RouteValues["name"];
            await context.Response.WriteAsync($"Hello {name}!");
        });
    });
    

    路由模板中的参数被存储在 HttpRequest.RouteValues 中

    大小写不敏感

    url 中如果有符合,在模板中用{}代替

    catch-all 路由模板

    • 在 token 前用 * 或者 ** 加在参数名前,比如 blog/{*slug}
    • blog/ 后面的字符串会当成 slug 的路由参数值,包括 "/",比如浏览器输入 blog/my/path 会匹配成 foo/my%2Fpath,如果想要得到 blog/my/path 则使用两个 ,foo/{path}
    • 字符串.也是可选的,比如 files/{filename}.{ext?},如果要输入 /files/myFile 也能匹配到这个路由
    //app.Run(async context =>
    //{
    //    await context.Response.WriteAsync("my middleware 2");
    //});
    
    app.UseEndpoints(endpoints =>
    {
        //endpoints.MapControllers();
    
        // 将终结点绑定到路由上
        endpoints.MapGet("/hello", async context =>
        {
            await context.Response.WriteAsync("Hello World!");
        });
    });
    

    启动程序,访问:https://localhost:5001/hello

    输出如下:

    my middleware 1Hello World!
    

    获取路由模板参数

    endpoints.MapGet("/blog/{*title}", async context =>
    {
        var title = context.Request.RouteValues["title"];
        await context.Response.WriteAsync($"blog title: {title}");
    });
    

    启动程序,访问:https://localhost:5001/blog/my-title

    输出如下:

    my middleware 1blog title: my-title
    

    constraint 约束

    [Route("users/{id:int:min(1)}")]
    public User GetUserById(int id) { }
    
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGet("{message:regex(^\d{{3}}-\d{{2}}-\d{{4}}$)}",
            context => 
            {
                return context.Response.WriteAsync("inline-constraint match");
            });
    });
    

    约定路由

    默认

    endpoints.MapDefaultControllerRoute();
    

    自定义

    endpoints.MapControllerRoute("default","{controller=Home}/{action=Index}/{id?}");
    
    // 约定路由
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });
    
    // 约定路由也可以同时定义多个
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    
        endpoints.MapControllerRoute(
            name: "blog",
            pattern: "blog/{*article}",
            defaults: new {controller = "blog", action = "Article"});
    });
    

    特性路由

    controller

    [Route("[controller]")]
    

    http method

    [HttpGet("option")]
    
    [HttpGet]
    [Route("option")]
    
    [HttpGet]
    [Route("option/{id:int}")]
    

    路由冲突

    [HttpGet]
    //[Route("option")]
    public IActionResult GetOption()
    {
        return Ok(_myOption);
    }
    

    如果路由相同,启动程序会报错:

    AmbiguousMatchException: The request matched multiple endpoints. Matches:
    HelloApi.Controllers.ConfigController.GetOption (HelloApi)
    HelloApi.Controllers.ConfigController.GetConfigurations (HelloApi)
    

    终结点

    ASP.NET Core 终结点是:

    • 可执行:具有 RequestDelegate。
    • 可扩展:具有元数据集合。
    • Selectable:可选择性包含路由信息。
    • 可枚举:可通过从 DI 中检索 EndpointDataSource 来列出终结点集合。

    终结点可以:

    • 通过匹配 URL 和 HTTP 方法来选择。
    • 通过运行委托来执行。

    中间件的每一步都在匹配终结点,所以路由和终结点之间的中间件可以拿到终结点的信息

    app.UseRouting();
    
    // 路由和终结点之间的中间件可以拿到终结点的信息
    app.Use(next => context =>
    {
        // 获取当前已经被选择的终结点
        var endpoint = context.GetEndpoint();
        if (endpoint is null)
        {
            return Task.CompletedTask;
        }
        // 输出终结点的名称
        Console.WriteLine($"Endpoint: {endpoint.DisplayName}");
        // 打印终结点匹配的路由
        if (endpoint is RouteEndpoint routeEndpoint)
        {
            Console.WriteLine("Endpoint has route pattern: " +
                              routeEndpoint.RoutePattern.RawText);
        }
        // 打印终结点的元数据
        foreach (var metadata in endpoint.Metadata)
        {
            Console.WriteLine($"Endpoint has metadata: {metadata}");
        }
    
        return Task.CompletedTask;
    });
    
    app.UseEndpoints(endpoints =>
    {
        //endpoints.MapControllers();
    
        // 将终结点绑定到路由上
        endpoints.MapGet("/blog/{title}", async context =>
        {
            var title = context.Request.RouteValues["title"];
            await context.Response.WriteAsync($"blog title: {title}");
        });
    });
    

    启动程序,访问:https://localhost:5001/blog/my-first-blog

    控制台输出如下:

    Endpoint: /blog/{title} HTTP: GET
    Endpoint has route pattern: /blog/{title}
    Endpoint has metadata: System.Runtime.CompilerServices.AsyncStateMachineAttribute
    Endpoint has metadata: System.Diagnostics.DebuggerStepThroughAttribute
    Endpoint has metadata: Microsoft.AspNetCore.Routing.HttpMethodMetadata
    

    打印 http 方法

    // 打印终结点的元数据
    foreach (var metadata in endpoint.Metadata)
    {
        Console.WriteLine($"Endpoint has metadata: {metadata}");
        // 打印 http 方法
        if (metadata is HttpMethodMetadata httpMethodMetadata)
        {
            Console.WriteLine($"Current Http Method: {httpMethodMetadata.HttpMethods.FirstOrDefault()}");
        }
    }
    

    启动程序,访问:https://localhost:5001/blog/my-first-blog

    控制台输出如下:

    Current Http Method: GET
    

    修改终结点名称、元数据

    app.UseEndpoints(endpoints =>
    {
        //endpoints.MapControllers();
    
        // 将终结点绑定到路由上
        endpoints.MapGet("/blog/{title}", async context =>
            {
                var title = context.Request.RouteValues["title"];
                await context.Response.WriteAsync($"blog title: {title}");
            }).WithDisplayName("Blog")// 修改名称
            .WithMetadata("10001");// 修改元数据
    });
    
    • 调用 UseRouting 之前,终结点始终为 null。
    • 如果找到匹配项,则 UseRouting 和 UseEndpoints 之间的终结点为非 null。
    • 如果找到匹配项,则 UseEndpoints 中间件即为终端。 稍后会在本文档中定义终端中间件。
    • 仅当找不到匹配项时才执行 UseEndpoints 后的中间件。

    GitHub源码链接:

    https://github.com/MINGSON666/Personal-Learning-Library/tree/main/ArchitectTrainingCamp/HelloApi

    知识共享许可协议

    本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

    欢迎转载、使用、重新发布,但务必保留文章署名 郑子铭 (包含链接: http://www.cnblogs.com/MingsonZheng/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。

    如有任何疑问,请与我联系 (MingsonZheng@outlook.com) 。

  • 相关阅读:
    A B
    hdu 4656 Evaluation [任意模数fft trick]
    bzoj 3451: Tyvj1953 Normal [fft 点分治 期望]
    bzoj 3509: [CodeChef] COUNTARI] [分块 生成函数]
    hdu 5730 Shell Necklace [分治fft | 多项式求逆]
    hdu 4609 3-idiots [fft 生成函数 计数]
    UVA 12633 Super Rooks on Chessboard [fft 生成函数]
    Codeforces Round #410 (Div. 2)
    形式幂级数 [学习笔记]
    Python 字符串前面加u,r,b,f的含义
  • 原文地址:https://www.cnblogs.com/MingsonZheng/p/14175687.html
Copyright © 2020-2023  润新知