1. 程式人生 > WINDOWS開發 >讓WebApi路由配置像Mvc一樣支援namespaces引數

讓WebApi路由配置像Mvc一樣支援namespaces引數

很多年前就知道我軟的WebApi的路由模板配置方法不支援namespaces引數的事,所以也追隨我軟的腳步在程式中不再造namespaces引數的設計與實現,最近小組裡有人思維不夠開源,想著使用namespaces引數把啟動專案和Api具體實現分成兩個專案,目的大概是為了保護原始碼,我極度排斥這種老舊思想,不過既然有人還惦念namespaces的事兒,也不妨從技術的角度出發去玩一玩。於是乎一頓大搜索後,經過學習、分析、理解、總結、設計、試驗後,最終實現了讓WebApi的路由配置方法像Mvc一樣支援namespaces引數,具體程式碼如下:

第一個檔案:HttpRouteCollectionExtensions,通過擴充套件方法的形式實現MapHttpRoute支援namespaces引數

技術分享圖片
 1 public static class HttpRouteCollectionExtensions
 2     {
 3         public static IHttpRoute MapHttpRoute(this HttpRouteCollection routes,string name,string routeTemplate,object defaults,string[] namespaces)
 4         {
 5             return routes.MapHttpRoute(name,routeTemplate,defaults,null
,null,namespaces); 6 } 7 public static IHttpRoute MapHttpRoute(this HttpRouteCollection routes,object constraints,HttpMessageHandler handler,string[] namespaces) 8 { 9 if (routes == null) 10 { 11 throw new ArgumentNullException("routes
"); 12 } 13 14 var dvd = new HttpRouteValueDictionary(defaults); 15 var cvd = new HttpRouteValueDictionary(constraints); 16 var nvd = new HttpRouteValueDictionary(new { Namespace = namespaces }); 17 18 var route = routes.CreateRoute(routeTemplate,dvd,cvd,nvd,handler); 19 routes.Add(name,route); 20 return route; 21 } 22 }
View Code

第二個檔案:NamespaceHttpControllerSelector,控制器的名稱空間選擇器,用於代替預設的選擇器

技術分享圖片
  1 public class NamespaceHttpControllerSelector : IHttpControllerSelector
  2     {
  3         private const string NamespaceKey = "Namespace";
  4         private const string ControllerKey = "controller";
  5 
  6         private readonly HttpConfiguration _configuration;
  7         private readonly Lazy<Dictionary<string,HttpControllerDescriptor>> _controllers;
  8         private readonly HashSet<string> _duplicates;
  9 
 10         public NamespaceHttpControllerSelector(HttpConfiguration config)
 11         {
 12             _configuration = config;
 13             _duplicates = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
 14             _controllers = new Lazy<Dictionary<string,HttpControllerDescriptor>>(InitializeControllerDictionary);
 15         }
 16 
 17         private Dictionary<string,HttpControllerDescriptor> InitializeControllerDictionary()
 18         {
 19             var dictionary = new Dictionary<string,HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase);
 20 
 21             // Create a lookup table where key is "namespace.controller". The value of "namespace" is the last
 22             // segment of the full namespace. For example:
 23             // MyApplication.Controllers.V1.ProductsController => "V1.Products"
 24             IAssembliesResolver assembliesResolver = _configuration.Services.GetAssembliesResolver();
 25             IHttpControllerTypeResolver controllersResolver = _configuration.Services.GetHttpControllerTypeResolver();
 26 
 27             ICollection<Type> controllerTypes = controllersResolver.GetControllerTypes(assembliesResolver);
 28 
 29             foreach (Type t in controllerTypes)
 30             {
 31                 // For the dictionary key,strip "Controller" from the end of the type name.
 32                 var controllerName = "";
 33                 var cItem = t.GetCustomAttribute(typeof(RoutePrefixAttribute));
 34                 if (cItem != null)
 35                 {
 36                     var segments = ((RoutePrefixAttribute)cItem).Prefix.Split(/);
 37                     controllerName = segments[segments.Length - 1];
 38                 }
 39                 else
 40                 {
 41                     // This matches the behavior of DefaultHttpControllerSelector.
 42                     controllerName = t.Name.Remove(t.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length);
 43                 }
 44 
 45                 var key = String.Format(CultureInfo.InvariantCulture,"{0}.{1}",t.Namespace,controllerName);
 46 
 47                 // Check for duplicate keys.
 48                 if (dictionary.Keys.Contains(key))
 49                 {
 50                     _duplicates.Add(key);
 51                 }
 52                 else
 53                 {
 54                     dictionary[key] = new HttpControllerDescriptor(_configuration,controllerName,t);
 55                 }
 56             }
 57 
 58             // Remove any duplicates from the dictionary,because these create ambiguous matches. 
 59             // For example,"Foo.V1.ProductsController" and "Bar.V1.ProductsController" both map to "v1.products".
 60             foreach (string s in _duplicates)
 61             {
 62                 dictionary.Remove(s);
 63             }
 64             return dictionary;
 65         }
 66         private T GetRouteVariable<T>(IHttpRouteData routeData,string name)
 67         {
 68             object result = null;
 69             if (routeData.Values.TryGetValue(name,out result))
 70             {
 71                 return (T)result;
 72             }
 73             return default(T);
 74         }
 75         private string GetControllerKey(IHttpRouteData routeData,string controllerName)
 76         {
 77             var keys = _controllers.Value.Keys.ToList();
 78             object namespaceName = null;
 79             if (routeData.Route.DataTokens == null || !routeData.Route.DataTokens.TryGetValue(NamespaceKey,out namespaceName))
 80             {
 81                 return keys.FirstOrDefault(o => o.ToLower().EndsWith(controllerName.ToLower()));
 82             }
 83 
 84             //get the defined namespace
 85             string[] namespaces = (string[])namespaceName;
 86             for (int i = 0; i < namespaces.Length; i++)
 87             {
 88                 var nss = keys.Where(o => o.StartsWith(namespaces[i])).ToList();
 89                 if (nss != null && nss.Count() > 0)
 90                 {
 91                     return nss.FirstOrDefault(o => o.ToLower().EndsWith(controllerName.ToLower()));
 92                 }
 93             }
 94             return null;
 95         }
 96 
 97         public HttpControllerDescriptor SelectController(HttpRequestMessage request)
 98         {
 99             IHttpRouteData routeData = request.GetRouteData();
100             if (routeData == null)
101             {
102                 throw new HttpResponseException(HttpStatusCode.NotFound);
103             }
104 
105             // Get the namespace and controller variables from the route data.
106             string controllerName = GetRouteVariable<string>(routeData,ControllerKey);
107             if (controllerName == null)
108             {
109                 throw new HttpResponseException(HttpStatusCode.NotFound);
110             }
111 
112             // Find a matching controller.
113             string key = GetControllerKey(routeData,controllerName);
114             if (key == null)
115             {
116                 throw new HttpResponseException(HttpStatusCode.NotFound);
117             }
118 
119             HttpControllerDescriptor controllerDescriptor;
120             if (_controllers.Value.TryGetValue(key,out controllerDescriptor))
121             {
122                 return controllerDescriptor;
123             }
124             else if (_duplicates.Contains(key))
125             {
126                 throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.InternalServerError,"Multiple controllers were found that match this request."));
127             }
128             else
129             {
130                 throw new HttpResponseException(HttpStatusCode.NotFound);
131             }
132         }
133         public IDictionary<string,HttpControllerDescriptor> GetControllerMapping()
134         {
135             return _controllers.Value;
136         }
137     }
View Code

第三個檔案:App_Start/WebApiConfig,不再使用預設的路由模板配置,而使用自定義實現

技術分享圖片
 1 public static class WebApiConfig
 2     {
 3         public static void Register(HttpConfiguration config)
 4         {
 5             //// Web API 路由
 6             //config.MapHttpAttributeRoutes();
 7             //config.Routes.MapHttpRoute(
 8             //    name: "DefaultApi", 9             //    routeTemplate: "api/{controller}/{id}",10             //    defaults: new { id = RouteParameter.Optional }
11             //);
12 
13             // Web API 支援namespaces
14             config.Services.Replace(typeof(IHttpControllerSelector),new NamespaceHttpControllerSelector(config));
15             config.Routes.MapHttpRoute(
16                 name: "DefaultApi",17                 routeTemplate: "api/{controller}/{action}/{id}",18                 defaults: new { id = RouteParameter.Optional },19                 namespaces: new string[] { "xxxx.Controllers" }
20             );
21         }
22     }
View Code

鑑於上述程式碼的基礎來源於網路多處,雖然由本人重新設計後實現了namespaces引數的支援,但不能算原創本人只能算是搬運工,即:取於網路,還於網路,如果能幫到您、我便很開心了。