標籤:
這個問題來源於我想在 Web API 中使用相同的控制器名稱(Controller)在不同的命名空間下,但是 Web API 的預設 路由(Route) 機制是會忽略命名空間的不同的,如果這樣做,會看到以下提示:
找到多個與名為“XXX”的控制器匹配的類型。如果為此請求(“{namespace}/{controller}/{action}”)提供服務的路由找到多個控制器,並且這些控制器是使用相同的名稱但不同的命名空間定義的(這不受支援),則會發生這種情況。
在 ASP.NET MVC 中,可以通過建立 地區(Area) 來解決這種問題,但 Web API 並沒有地區這種東西。
不過官方早已給出瞭解決方案,只是並沒有作為 Web API 的一部分直接整合。不知道是出於什麼考慮。
原文連結:http://blogs.msdn.com/b/webdev/archive/2013/03/08/using-namespaces-to-version-web-apis.aspx
主要思路就是自己重新實現一個可以識別命名空間的路由選取器,然後替換掉系統預設的路由選取器即可。這個命名空間選取器官方也已經幫我們實現,並且提供了完整的項目示範樣本。
代碼連結:http://aspnet.codeplex.com/SourceControl/changeset/view/dd207952fa86#Samples/WebApi/NamespaceControllerSelector/NamespaceHttpControllerSelector.cs
將此類的實現加入到項目中,並在初始化 Web API 路由時進行替換,在設定路由模板的時候,加入相應的 {namespace} 參數即可:
public static class WebApiConfig{ public static void Register(HttpConfiguration config) { config.MapHttpAttributeRoutes(); config.Services.Replace(typeof(IHttpControllerSelector), new NamespaceHttpControllerSelector(config)); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "{namespace}/{controller}/{action}" ); }}
最後,附上官方 NamespaceHttpControllerSelector 類的實現代碼(出處請見上述連結):
public class NamespaceHttpControllerSelector : IHttpControllerSelector{ private const string NamespaceKey = "namespace"; private const string ControllerKey = "controller"; private readonly HttpConfiguration _configuration; private readonly Lazy<Dictionary<string, HttpControllerDescriptor>> _controllers; private readonly HashSet<string> _duplicates; public NamespaceHttpControllerSelector(HttpConfiguration config) { _configuration = config; _duplicates = new HashSet<string>(StringComparer.OrdinalIgnoreCase); _controllers = new Lazy<Dictionary<string, HttpControllerDescriptor>>(InitializeControllerDictionary); } private Dictionary<string, HttpControllerDescriptor> InitializeControllerDictionary() { var dictionary = new Dictionary<string, HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase); // Create a lookup table where key is "namespace.controller". The value of "namespace" is the last // segment of the full namespace. For example: // MyApplication.Controllers.V1.ProductsController => "V1.Products" IAssembliesResolver assembliesResolver = _configuration.Services.GetAssembliesResolver(); IHttpControllerTypeResolver controllersResolver = _configuration.Services.GetHttpControllerTypeResolver(); ICollection<Type> controllerTypes = controllersResolver.GetControllerTypes(assembliesResolver); foreach (Type t in controllerTypes) { var segments = t.Namespace.Split(Type.Delimiter); // For the dictionary key, strip "Controller" from the end of the type name. // This matches the behavior of DefaultHttpControllerSelector. var controllerName = t.Name.Remove(t.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length); var key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", segments[segments.Length - 1], controllerName); // Check for duplicate keys. if (dictionary.Keys.Contains(key)) { _duplicates.Add(key); } else { dictionary[key] = new HttpControllerDescriptor(_configuration, t.Name, t); } } // Remove any duplicates from the dictionary, because these create ambiguous matches. // For example, "Foo.V1.ProductsController" and "Bar.V1.ProductsController" both map to "v1.products". foreach (string s in _duplicates) { dictionary.Remove(s); } return dictionary; } // Get a value from the route data, if present. private static T GetRouteVariable<T>(IHttpRouteData routeData, string name) { object result = null; if (routeData.Values.TryGetValue(name, out result)) { return (T)result; } return default(T); } public HttpControllerDescriptor SelectController(HttpRequestMessage request) { IHttpRouteData routeData = request.GetRouteData(); if (routeData == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } // Get the namespace and controller variables from the route data. string namespaceName = GetRouteVariable<string>(routeData, NamespaceKey); if (namespaceName == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } string controllerName = GetRouteVariable<string>(routeData, ControllerKey); if (controllerName == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } // Find a matching controller. string key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", namespaceName, controllerName); HttpControllerDescriptor controllerDescriptor; if (_controllers.Value.TryGetValue(key, out controllerDescriptor)) { return controllerDescriptor; } else if (_duplicates.Contains(key)) { throw new HttpResponseException( request.CreateErrorResponse(HttpStatusCode.InternalServerError, "Multiple controllers were found that match this request.")); } else { throw new HttpResponseException(HttpStatusCode.NotFound); } } public IDictionary<string, HttpControllerDescriptor> GetControllerMapping() { return _controllers.Value; }}
在 ASP.NET Web API 中,使用 命名空間(namespace) 來作為路由的參數