很多年前就知道我软的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, string name, string routeTemplate, object defaults, 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 { namespaces = namespaces }); 17 18 var route = routes.CreateRoute(routeTemplate, dvd, cvd, nvd, handler); 19 routes.Add(name, route); 20 return route; 21 } 22 }
第二个文件:NamespaceHttpControllerSelector,控制器的命名空间选择器,用于代替默认的选择器
1 public class NamespaceHttpControllerSelector : IHttpControllerSelector 2 { 3 private const string NamespaceKey = "namespaces"; 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 }
第三个文件: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 }
鉴于上述代码的基础来源于网络多处,虽然由本人重新设计后实现了namespaces参数的支持,但不能算原创本人只能算是搬运工,即:取于网络,还于网络,若能帮到您、我便很开心了。
特别注意:在Asp.Net Web API项目里不要创建名称相同的Controller文件,如A文件夹里有TestController,B文件夹里也有TestController,这种操作是不被允许的