1. 程式人生 > >MVC5為WebAPI添加命名空間的支持

MVC5為WebAPI添加命名空間的支持

defaults ont data on() itl repl new pan lte

前言

默認情況下,微軟提供的MVC框架模板中,WebAPI路由是不支持Namespace參數的。這導致一些比較大型的項目,無法把WebApi分離到單獨的類庫中。

本文將提供解決該問題的方案。

微軟官方曾經給出過一個關於WebAPI支持Namespace的擴展,其主要內容就是自定義實現了IHttpControllerSelector接口,通過路由配置時替換掉MVC中自帶的DefaultHttpControllerSelector達到WebAPI支持Namespace的目的。但是經過我的測試,效果並不好。這就就不貼微軟官方的源碼了。

解決方案

下面我介紹一下我的解決方案。

首先看一下我自定義的目錄結構,如下圖:

技術分享

首先定義一個類,名字可以隨意,我這裏命名為ZhuSirNamespaceHttpControllerSelector,他繼承自MVC框架默認的DefaultHttpControllerSelector類,該繼承類主要目的是在請求URI到達WebAPI路由時檢索我們指定的命名空間的WebAPI控制器。下面是ZhuSirNamespaceHttpControllerSelector類的源代碼,可直接復制到你自己的項目中就可以用:

技術分享
/// <summary>
    /// 擴展自DefaultHttpControllerSelector類的控制器選擇器,目前在用
    /// </summary>
    public class ZhuSirNamespaceHttpControllerSelector : DefaultHttpControllerSelector
    {
        private const string NamespaceRouteVariableName = "namespaces";
        private readonly HttpConfiguration _configuration;
        private readonly Lazy<ConcurrentDictionary<string, Type>> _apiControllerCache;

        public ZhuSirNamespaceHttpControllerSelector(HttpConfiguration configuration)
            : base(configuration)
        {
            _configuration = configuration;
            _apiControllerCache = new Lazy<ConcurrentDictionary<string, Type>>(
                new Func<ConcurrentDictionary<string, Type>>(InitializeApiControllerCache));
        }

        private ConcurrentDictionary<string, Type> InitializeApiControllerCache()
        {
            IAssembliesResolver assembliesResolver = this._configuration.Services.GetAssembliesResolver();
            var types = this._configuration.Services.GetHttpControllerTypeResolver()
                .GetControllerTypes(assembliesResolver).ToDictionary(t => t.FullName, t => t);

            return new ConcurrentDictionary<string, Type>(types);
        }

        public IEnumerable<string> GetControllerFullName(HttpRequestMessage request, string controllerName)
        {
            object namespaceName;
            var data = request.GetRouteData();
            IEnumerable<string> keys = _apiControllerCache.Value.ToDictionary<KeyValuePair<string, Type>, string, Type>(t => t.Key,
                    t => t.Value, StringComparer.CurrentCultureIgnoreCase).Keys.ToList();

            if (!data.Values.TryGetValue(NamespaceRouteVariableName, out namespaceName))
            {
                return from k in keys
                       where k.EndsWith(string.Format(".{0}{1}", controllerName,
                       DefaultHttpControllerSelector.ControllerSuffix), StringComparison.CurrentCultureIgnoreCase)
                       select k;
            }

            string[] namespaces = (string[])namespaceName;
            return from n in namespaces
                   join k in keys on string.Format("{0}.{1}{2}", n, controllerName,
                   DefaultHttpControllerSelector.ControllerSuffix).ToLower() equals k.ToLower()
                   select k;
        }

        public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
        {
            Type type;
            if (request == null)
            {
                throw new ArgumentNullException("request");
            }
            string controllerName = this.GetControllerName(request);
            if (string.IsNullOrEmpty(controllerName))
            {
                throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.NotFound,
                    string.Format("無法通過API路由匹配到您所請求的URI ‘{0}‘",
                    new object[] { request.RequestUri })));
            }
            IEnumerable<string> fullNames = GetControllerFullName(request, controllerName);
            if (fullNames.Count() == 0)
            {
                throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.NotFound,
                        string.Format("無法通過API路由匹配到您所請求的URI ‘{0}‘",
                        new object[] { request.RequestUri })));
            }

            if (this._apiControllerCache.Value.TryGetValue(fullNames.First(), out type))
            {
                return new HttpControllerDescriptor(_configuration, controllerName, type);
            }
            throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.NotFound,
                string.Format("無法通過API路由匹配到您所請求的URI ‘{0}‘",
                new object[] { request.RequestUri })));
        }
    }
技術分享
   第二步,需要我們配置WebAPI路由設置,添加{ namespaces }片段變量,同時也可以直接為其設置默認值,然後替換掉原MVC框架中的DefaultHttpControllerSelector選額器為我們之前擴展的ZhuSirNamespaceHttpControllerSelector選額器。這裏需要註意片段變量的變量名namespaces一定要與我們ZhuSirNamespaceHttpControllerSelector中定義的NamespaceRouteVariableName字符串常量的值一致。下面貼出WebApiConfig的源碼:
技術分享
public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            //配置檢查Api控制後綴為ApiController
            //var suffix = typeof(MicrosoftNamespaceHttpControllerSelector)
            //    .GetField("ControllerSuffix", BindingFlags.Static | BindingFlags.Public);
            //if (suffix != null)
            //{
            //    suffix.SetValue(null, "ApiController");
            //}

            // Web API 配置和服務
            // 將 Web API 配置為僅使用不記名令牌身份驗證。
            config.SuppressDefaultHostAuthentication();
            config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

            // 對 JSON 數據使用混合大小寫。
            config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();

            // Web API 路由
            config.MapHttpAttributeRoutes();


            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}/{namespaces}",
                defaults: new { id = RouteParameter.Optional ,namespaces = new[] { "ZhuSir.HMS.WebApi.ApiControllers" }  }
            );

            config.Services.Replace(typeof(IHttpControllerSelector), new ZhuSirNamespaceHttpControllerSelector(config));
        }
    }
技術分享

如上源碼。我們替換了原MVC框架的DefaultHttpControllerSelector為ZhuSirNamespaceHttpControllerSelector,並且指定了默認的namespaces為ZhuSir.HMS.WebApi.ApiControllers。大體意思就是當URL為 http://XXX/api/testApi時,WebApi路由將在ZhuSir.HMS.WebApi.ApiControllers命名空間下尋找名稱為testApi的WebAPI控制器的Get方法。

當然,WebAPI控制器除了集成字ApiController意外還要註意命名規範,都需要以Controller結尾,為了不讓API控制器與MVC控制器重名,我都以ApiController結尾。下面貼出testAPI的源碼:

技術分享
namespace ZhuSir.HMS.WebApi.ApiControllers
{
    public class TestApiController : ApiController
    {
        [HttpGet]
        public string Gettest()
        {
            return "測試數據";
        }
    }
}
技術分享

測試

程序Debug,錄入URL: http://localhost:4541/api/TestApi,得到如下結果:

技術分享

可以看出,WebAPI路由成功訪問到了其他類庫中的WebAPI控制器。

MVC5為WebAPI添加命名空間的支持