ASP.NET Core 3 中的自定義路由


您是否曾經想停止使用Microsoft的內置URL路由並將其替換為自己的實現?在本教程中,我將向您展示如何在ASP.NET Core 3 Web API中實現自定義路由。這可以通過用我們自己的Microsoft替換請求管道中間件來實現。在本教程結束時,我們將使用以下路由語法提供一個具有兩個端點的有效Web Api:

 

4

這篇文章將介紹以下內容:

1. 先決條件
2. 創建ExampleController
3. 創建RouteSettings
4. 創建RouteManager
5. 創建EndpointActivator
6. 創建CustomRoutingMiddleware
7. 注冊中間件並測試

 

先決條件

在開始本教程之前,您應該熟悉反射和ASP.NET Core Web API請求管道。

首先,創建一個ASP.NET Core 3 Web API項目並刪除Startup.ConfigureServices和Startup.Configure中的方法主體。

 1 public class Startup
 2 {
 3     public Startup(IConfiguration configuration)
 4     {
 5         Configuration = configuration;
 6     }
 7  
 8     public IConfiguration Configuration { get; }
 9  
10     // This method gets called by the runtime. Use this method to add services to the container.
11     public void ConfigureServices(IServiceCollection services)
12     {
13  
14     }
15  
16     // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
17     public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
18     {
19  
20     }
21 }

 

創建控制器

創建控制器很簡單-我們將有兩個端點。一個不接受任何輸入並返回一個字符串,另一個接受一個請求對象並重復該字符串x次。

 1 public class ExampleController
 2 {
 3     public async Task<string> Marco()
 4     {
 5         return await Task.FromResult("Polo");
 6     }
 7  
 8     public async Task<string> Echo(EchoRequest echoRequest)
 9     {
10         StringBuilder echoBuilder = new StringBuilder();
11  
12         for(int i = 0; i < echoRequest.EchoCount; i++)
13         {
14             echoBuilder.Append($"{ echoRequest.Content}...");
15         }
16  
17         return await Task.FromResult(echoBuilder.ToString());
18     }
19 }
20  
21 public class EchoRequest
22 {
23     public string Content { get; set; }
24     public int EchoCount { get; set; }
25 }

 

創建RouteSettings

RouteSettings只是一個模型類,用於存儲每個路由的設置。

1 public class RouteSettings
2 {
3     public string URL { get; set; }
4     public string Action { get; set; }
5     public Type Controller { get; set; }
6     public string Endpoint { get; set; }
7 }

 

創建RouteManager

RouteManager承擔兩項職責-添加路由和解析URL。

添加路由時,該類將使用反射來獲取設置中描述的端點的MethodInfo,然后使用“(Action)(URL)”作為路由鍵添加路由。

要解析URL,RouteManager會根據給定的路由鍵返回MethodInfo。

 1 public class RouteManager
 2 {
 3     private IDictionary<string, MethodInfo> _routes = new Dictionary<string, MethodInfo>();
 4  
 5     public RouteManager AddRoute(Action<RouteSettings> setup)
 6     {
 7         var routeSettings = new RouteSettings();
 8         setup(routeSettings);
 9  
10         string routeKey = $"{routeSettings.Action} {routeSettings.URL}";
11  
12         var endpointMethod = Assembly.GetExecutingAssembly()
13             .GetTypes()
14             .FirstOrDefault(type => type.Equals(routeSettings.Controller))
15             .GetMethod(routeSettings.Endpoint);
16  
17         _routes.Add(routeKey, endpointMethod);
18  
19         return this;
20     }
21  
22     public MethodInfo Resolve(string action, string url)
23     {
24         if (url.StartsWith("/"))
25         {
26             url = url.Remove(0, 1);
27         }
28  
29         string routeKey = $"{action} {url}";
30  
31         if(_routes.TryGetValue(routeKey, out MethodInfo methodEndpoint))
32         {
33             return methodEndpoint;
34         }
35  
36         throw new Exception($"No matching route for {routeKey}");
37     }
38 }

 

創建EndpointActivator

EndPointActivator創建Controller的實例,然后執行給定的Endpoint。如果端點需要參數,例如ExampleController.Echo,則通過反序列化請求正文來初始化參數。

 

 1 public class EndpointActivator
 2 {
 3     public async Task<object> ActivateAsync(MethodInfo endpointMethod, string requestBody)
 4     {
 5         // create an instance of the controller
 6         var controllerType = endpointMethod.DeclaringType;
 7         var controller = Activator.CreateInstance(controllerType);
 8  
 9         var endpointParameter = endpointMethod.GetParameters().FirstOrDefault();
10  
11         if (endpointParameter is null)
12         {
13             var endpointResponse = endpointMethod.Invoke(controller, null);
14             var response = await IfAsync(endpointResponse);
15             return response;
16         }
17         else
18         {
19             var requestBodyParameter = DeserializeRequestBody(requestBody, endpointParameter);
20             var endpointResponse = endpointMethod.Invoke(controller, new object[] { requestBodyParameter });
21             var response = await IfAsync(endpointResponse);
22             return response;
23         }
24     }
25  
26     private static object DeserializeRequestBody(string requestBody, ParameterInfo endpointParameter)
27     {
28         var deserializedParamter = JsonConvert.DeserializeObject(requestBody, endpointParameter.ParameterType);
29  
30         if (deserializedParamter is null)
31         {
32             throw new ArgumentException($"Unable to deserialze request body to type {endpointParameter.ParameterType.Name}");
33         }
34  
35         return deserializedParamter;
36     }
37  
38     private static async Task<object> IfAsync(object endpointResponse)
39     {
40         var responseTask = endpointResponse as Task;
41  
42         if (responseTask is null)
43         {
44             return endpointResponse;
45         }
46  
47         await responseTask;
48  
49         var responseTaskResult = responseTask.GetType()
50             .GetProperty("Result")
51             .GetValue(responseTask);
52  
53         return responseTaskResult;
54     }
55 }

 

創建CustomRoutingMiddleware

CustomRoutingMiddleware匯集了RouteManager和EndpointActivator來處理從請求管道傳遞的HttpContext對象。它還公開了一個IApplicationBuilder擴展方法,該方法將自身注冊到應用程序的請求管道中並返回RouteManager實例,以便我們可以添加路由。

 1 public static class CustomRoutingMiddleware
 2 {
 3     private static RouteManager _routeManager = new RouteManager();
 4     private static EndpointActivator _endpointActivator = new EndpointActivator();
 5  
 6     public static RouteManager UseCustomRouting(this IApplicationBuilder app)
 7     {
 8         // Add TryProcess() to request pipeline
 9         app.Use(async (context, next) =>
10         {
11             await TryProcess(context);
12         });
13  
14         return _routeManager;
15     }
16  
17     public static async Task TryProcess(HttpContext context)
18     {
19         try
20         {
21             // get endpoint method
22             var endpointMethod = _routeManager.Resolve(context.Request.Method, context.Request.Path);
23  
24             // read request body
25             string requestBody = await new StreamReader(context.Request.Body, Encoding.UTF8).ReadToEndAsync();
26  
27             // activate the endpoint
28             var response = await _endpointActivator.ActivateAsync(endpointMethod, requestBody);
29  
30             // serialize the response
31             var serializedResponse = JsonConvert.SerializeObject(response, Formatting.Indented);
32  
33             // return response to client
34             await context.Response.WriteAsync(serializedResponse);
35         }
36         catch(Exception error)
37         {
38             context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;
39             await context.Response.WriteAsync(error.Message);
40         }
41     }
42 }

 

注冊中間件並測試

現在,我們要做的就是調用IApplicationBuilder.UseCustomRouting並將路由添加到Startup.Configure方法中。

 1 public class Startup
 2 {
 3     public Startup(IConfiguration configuration)
 4     {
 5         Configuration = configuration;
 6     }
 7  
 8     public IConfiguration Configuration { get; }
 9  
10     // This method gets called by the runtime. Use this method to add services to the container.
11     public void ConfigureServices(IServiceCollection services)
12     {
13  
14     }
15  
16     // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
17     public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
18     {
19         app.UseCustomRouting()
20             .AddRoute(settings =>
21             {
22                 settings.URL = "example/marco";
23                 settings.Action = "GET";
24                 settings.Controller = typeof(ExampleController);
25                 settings.Endpoint = nameof(ExampleController.Marco);
26             })
27             .AddRoute(settings =>
28             {
29                 settings.URL = "example/echo";
30                 settings.Action = "POST";
31                 settings.Controller = typeof(ExampleController);
32                 settings.Endpoint = nameof(ExampleController.Echo);
33             });
34     }
35 }

我將使用Postman來測試API。

1個


2


3

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM