Summary of knowledge points and methods of. Net Core routing processing

  • 2021-11-29 23:25:56
  • OfStack

Preface

The user requests the interface route, and the application returns the processing result. How to match the requested data in the application? Why can we find the corresponding treatment method so accurately? Let's talk about this route today. Routing is responsible for matching incoming HTTP requests and sending them to the executable endpoints. Endpoints are defined in the application and configured when the application starts, that is, processed in middleware.

Basic knowledge of routing

Routing-related code is automatically generated when a new project is created. Registered in the middleware pipeline in Startup. Configure. UseRouting and UseEndpoints middleware are mainly involved.

UseRouting adds route matching to middleware. The middleware also looks at the set of endpoints defined in the application. That is to say, all the routes in the application are registered to the middleware pipeline, which is convenient for matching when requesting.

UseEndpoints adds endpoint execution to middleware. The associated delegate runs. Simple will be the processing event running after the route match.


        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGet("/", async context =>
                {
                    await context.Response.WriteAsync("Hello World!");
                });
            });
        }

For example, the code above is the delegate that needs to be executed when HTPP GET request and Url is/. If the request here is not an Get request or is not "/", then there is no route match, and 404 will be returned. At the same time, MapDelete, MapMethods, MapPost, MapPut, Map, etc.

Endpoint

MapGet mentioned above, or MapPost not used, is used to define endpoints. They all contain two parameters, one for Url matching, and the other is the delegate to be executed. Here, different endpoint definition methods are adopted in different applications

MapRazorPages for Razor Pages MapControllers for Controller MapHub for SignalR MapGrpcService for gRPC

So what will we do if we need to use the authorization module? Endpoints also have corresponding processing methods. The following shows that middleware and routing 1 will be authorized for use, and MapHealthChecks will add health check endpoints. The RequireAuthorization that follows adds the authorization policy to the endpoint.


           app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapHealthChecks("/healthz").RequireAuthorization();
                endpoints.MapGet("/", async context =>
                {
                    await context.Response.WriteAsync("Hello World!");
                });
            });

And let's look at the use order in the middle. UseAuthentication and UseAuthorization are interspersed between UseRouting and UseEndpoints. This is written so that the authorization policy can find the endpoint in UseRouting, but the selected authorization policy can be applied before UseEndpoints is sent to the endpoint for execution

Endpoint metadata

The above example shows that the health check endpoint has an authorization policy attached. The authorization policy added is extra data, that is, endpoint metadata.

Metadata can be processed through route-aware middleware.
The metadata can be of any. NET type.

As mentioned above, metadata can be desirable. NET type, so what exactly is it? How to use metadata?


         // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();

            app.Use(next => context =>
            {
                var endpoint = context.GetEndpoint();
                if (endpoint?.Metadata.GetMetadata<AuditPolicyAttribute>()?.NeedsAudit ==true)
                {
                    Console.WriteLine(" Start processing transaction logic ");
                    Console.WriteLine($"ACCESS TO SENSITIVE DATA AT: {DateTime.UtcNow}");
                }
                return next(context);
            });

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGet("/", async context =>
                {
                    await context.Response.WriteAsync("Hello world!");
                });

                // Using metadata to configure the audit policy.
                endpoints.MapGet("/sensitive", async context =>
                {
                    await context.Response.WriteAsync($"sensitive data{DateTime.UtcNow}");
                })
                .WithMetadata(new AuditPolicyAttribute(needsAudit: true));
            });
        }
    }

    public class AuditPolicyAttribute : Attribute
    {
        public AuditPolicyAttribute(bool needsAudit)
        {
            NeedsAudit = needsAudit;
        }

        public bool NeedsAudit { get; }
    }

Look at the example above, where the metadata WithMetadata is appended when the endpoint binds "/sensitive". When accessing "/", it will output "Hello world! ". However, in app. Use, the output "processing transaction logic" is not executed because there is no matching metadata. But when "/sensitive" is executed, Console. WriteLine is output ("start processing transaction logic"); . Because metadata is added when the endpoint is defined. The metadata can be of type. NET. The metadata above is also our custom Class.

Compare terminal middleware and routing

Above we use app. Use to detect the matching metadata, and if the match is successful, we will perform the corresponding operation. We call it terminal middleware, why terminal middleware, because it stops searching, performs matching and operations, and finally returns.

So what is the difference between terminal middleware and routing?

Both methods allow termination of the processing pipeline: End middleware allows middleware to be placed anywhere in the pipeline:

Middleware terminates the pipeline by returning instead of calling next. Endpoints are always terminals.

Terminal middleware allows middleware to be placed anywhere in the pipeline:

The endpoint executes at the UseEndpoints location.

Terminal middleware allows arbitrary code to determine when middleware matches:

Custom route matching code can be complicated and difficult to write correctly. Routing provides a simple solution for typical applications. Most applications do not require custom route matching codes.

Endpoint interfaces with middleware, such as UseAuthorization and UseCors.

Using terminal middleware through UseAuthorization or UseCors requires manual interaction with the authorization system

Set up traditional routes

As we know above, route matching is added to middleware through UseRouting, and then endpoints are defined through UseEndpoints to perform matching delegates. So how to set it in MVC mode? Let's look at the traditional routing setup method.


         app.UseEndpoints(endpoints =>
            {
                app.UseEndpoints(endpoints =>
                {
                    endpoints.MapControllerRoute(
                        name: "default",
                        pattern: "{controller=Home}/{action=Index}/{id?}");
                });
            });

When we set up the traditional route above, we use endpoints. MapControllerRoute (); Which comes with two parameters, one is the name default, and the second is the routing template. Let's look at the routing template {controller=Home}/{action=Index}/{id?} When the Url path is matched, for example, the path WeatherForecast/Index/5 is executed. Then a processing method with controller WeatherForecast, method Index and parameter int type is matched.

REST Api Attribute Routing

The above is the traditional routing setting, so what is the routing setting for Api project? REST Api should model application functionality as a set of resources using attribute routing. Let's look at the sample code


     // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();

            app.UseAuthorization();

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

Use the MapControllers call in the above code. Map attribute routing. Let's look at how attribute routing is used when using it.

Route[]

In the following example, we use Route [], which can be either a separate scope controller or a separate scope action. It can also be used at the same time.


  [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {

        [Route("Index")]
        public string Index(int? id)
        {
            return "Test";
        }
    }


    [ApiController]
    [Route("[controller]/[action]")]
    public class WeatherForecastController : ControllerBase
    {
        public string Index(int? id)
        {
            return "Test";
        }
    }

    [ApiController]
    public class WeatherForecastController : ControllerBase
    {
        [Route("[controller]/Index")]
        public string Index(int? id)
        {
            return "Test";
        }
    }

Http[Verb]

With Http [Verb], it only works on action. For example, the following one writes [HttpGet] directly above Index


("[controller]/Index")] The rest is HttpPost , HttpDelete And so on 
     [ApiController]
    public class WeatherForecastController : ControllerBase
    {
        [HttpGet("[controller]/Index")]
        public string Index(int? id)
        {
            return "Test";
        }
    }

Mix Route [] and Http [Verb]

Sometimes, in practice, the two methods can be mixed. For example, the following example uses Route [] in the controller and Http [Verb] in action. Because when defining Api, we not only need to mark the name of action, but also need to know how to request action.


  [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {

        [HttpGet("Index")]
        public string Index(int? id)
        {
            return "Test";
        }
    }

Summarize


Related articles: