WebApi 重写 DefaultHttpControllerSelector 实现路由重定向


背景:

本项目为后台项目

近期项目组内推行前后端分离架构。前端使用vue,后端使用webapi进行重构。

因原项目为mvc,所以重构对于后台接口变化不大。

新建webapi项目,log4net引入,swagger引入,全局异常处理,实现登陆登出功能,实现登陆过滤器,实现token安全机制,规范下接口返回模型等等。

前端使用vue后url路由由前端接管,后端只用实现功能需要的数据接口和一个返回前端初始化资源的初始页面暂定Web/Index。

通常实现是,登陆后重定向到Web/Index页面,Web/Index页面输出前端初始化资源,后续路由跳转由前端接管。

但因为是旧项目重构,只能一部分一部分切换为vue+webapi的新架构,需要和mvc的旧项目共用一段时间。通过菜单url的不同来确定跳转到新或者旧项目

这样就需要额外实现:新旧项目登陆/登出联动,并且保证新旧系统框架样式(菜单,头部,底部)保持一致。

还引发一个问题:从旧项目可以跳转到任意一个新项目功能页面,并且需要输出前端初始化资源,后续路由跳转就由前端接管了。

这样就需要很多路由规则都匹配到Web/Index,然后输出前端初始化资源

原实现方式:

 1 using System.Net;
 2 using System.Net.Http;
 3 using System.Net.Http.Headers;
 4 using System.Web.Http;
 5 
 6 namespace AppApi.Controllers
 7 {
 8     /// 
 9     /// 
10     /// 
11     public class WebController : ApiController
12     {
13         #region Public APIs
14 
15         /// 
16         /// Index
17         /// 
18         /// 
19         [Route("fe/dashboard")]
20         [Route("fe/Product/Index")]
21         [Route("fe/User/Index")]
22         [Route("fe/Order/Index")]
23         [Route("fe/System/Menu/Index")]
24         [Route("fe/System/Config/Index")]
25         [HttpGet]
26         public HttpResponseMessage Index()
27         {
28             //前端资源
29             string resourceHtml = "";
30             var response = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(resourceHtml) };
31             response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/html");
32             return response;
33         }
34         #endregion
35     }
36 }

这种方式需要定义大量的路由规则。

新的实现方式:

使用重写DefaultHttpControllerSelector(Controller选择器),ApiControllerActionSelector(Action选择器)实现

因前端路由都包含 /fe/ ,所以我们就拦截所有包含 /fe/ 的url

1,重写 DefaultHttpControllerSelector 的 GetControllerName 方法

新建文件:CustomControllerSelector.cs

 1 using System.Net.Http;
 2 using System.Web.Http;
 3 using System.Web.Http.Dispatcher;
 4 
 5 namespace AppApi.App_Start
 6 {
 7     /// 
 8     /// 自定义Controller选择
 9     /// 
10     public class CustomControllerSelector : DefaultHttpControllerSelector
11     {
12         /// 
13         /// 
14         /// 
15         /// 
16         public CustomControllerSelector(HttpConfiguration configuration) : base(configuration)
17         {
18         }
19 
20         /// 
21         /// 摘要:获取指定 System.Net.Http.HttpRequestMessage 的控制器的名称。
22         /// 
23         /// 参数:request:HTTP 请求消息。
24         /// 返回结果:指定 System.Net.Http.HttpRequestMessage 的控制器的名称。
25         public override string GetControllerName(HttpRequestMessage request)
26         {
27             //Index规则(/fe/)满足的话,返回Web/Index
28             if (request.RequestUri.AbsoluteUri.Contains("/fe/"))
29             {
30                 return "Web";
31             }
32             else
33             {
34                 return base.GetControllerName(request);
35             }
36         }
37     }
38 }

,2,重写 ApiControllerActionSelector 的 SelectAction 方法

新建文件:CustomActionSelector.cs

 1 using System.Linq;
 2 using System.Web.Http.Controllers;
 3 
 4 namespace AppApi.App_Start
 5 {
 6     /// 
 7     /// 自定义Action选择
 8     /// 
 9     public class CustomActionSelector : ApiControllerActionSelector
10     {
11         /// 
12         /// 
13         /// 
14         /// 
15         /// 
16         public override HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
17         {
18             //Index规则(/fe/)满足的话,返回Web/Index
19             if (controllerContext.Request.RequestUri.AbsoluteUri.Contains("/fe/"))
20             {
21                 return base.GetActionMapping(controllerContext.ControllerDescriptor).FirstOrDefault(f => f.Key == "Index").FirstOrDefault(d => d.ActionName == "Index");
22             }
23             else
24             {
25                 return base.SelectAction(controllerContext);
26             }
27         }
28     }
29 }

3,然后添加路由规则

1             // WebIndex路由,返回前端资源
2             config.Routes.MapHttpRoute(
3                 name: "WebIndex",
4                 routeTemplate: "fe/{*.}",//catch-all
5                 defaults: new { controller = "Web", action = "Index" }
6                 );

注意:routeTemplate: "fe/{*.}",//catch-all

使用 fe/{*.} 不管路由由几级都可以匹配

4,使自定义ControllerSelector,ActionSelector生效

1             // 使用自定义ControllerSelector,ActionSelector
2             config.Services.Replace(typeof(IHttpControllerSelector), new CustomControllerSelector(config));
3             config.Services.Replace(typeof(IHttpActionSelector), new CustomActionSelector());

修改文件:WebApiConfig.cs

 1 using AppApi.App_Start;
 2 using Newtonsoft.Json.Serialization;
 3 using System.Web.Http;
 4 using System.Web.Http.Controllers;
 5 using System.Web.Http.Dispatcher;
 6 
 7 namespace AppApi
 8 {
 9     /// 
10     /// webapi配置
11     /// 
12     public static class WebApiConfig
13     {
14         /// 
15         /// webapi注册
16         /// 
17         /// 
18         public static void Register(HttpConfiguration config)
19         {
20             // Web API 配置和服务
21             config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); //json格式化小驼峰
22             config.Formatters.Remove(config.Formatters.XmlFormatter);// 取消XML返回格式
23 
24             // 使用自定义ControllerSelector,ActionSelector
25             config.Services.Replace(typeof(IHttpControllerSelector), new CustomControllerSelector(config));
26             config.Services.Replace(typeof(IHttpActionSelector), new CustomActionSelector());
27 
28             // 特性路由(RouteAttribute)
29             config.MapHttpAttributeRoutes();
30 
31             // Action路由
32             config.Routes.MapHttpRoute(
33                 name: "ActionApi",
34                 routeTemplate: "api/{controller}/{action}/{id}",
35                 defaults: new { controller = "Common", action = "GetHttpCode", id = RouteParameter.Optional }
36                 );
37 
38             // WebIndex路由,返回前端资源
39             config.Routes.MapHttpRoute(
40                 name: "WebIndex",
41                 routeTemplate: "fe/{*.}",//catch-all
42                 defaults: new { controller = "Web", action = "Index" }
43                 );
44         }
45     }
46 }

这样只要请求URL中包含 /fe/ 都会返回Web/Index响应输出

参考

https://docs.microsoft.com/zh-cn/aspnet/core/mvc/controllers/routing?view=aspnetcore-2.2#multiple-routes