首页 > 代码库 > WebAPI增加Area以支持无限层级同名Controller

WebAPI增加Area以支持无限层级同名Controller

原文:WebAPI增加Area以支持无限层级同名Controller

微软的WebAPI默认实现逻辑

默认实现中不支持同名Controller,否则在访问时会报HttpError,在网上找到了各种路由自实现,如

给ASP.net Web API的Controller分类

搭建MVC及WebAPI项目框架时碰到的问题集合

在上述地址的帮助下,根据需求,重新编写了AreaHttpControllerSelector,路由原理与上述地址大同小异,均是通过路由匹配拼接FullName,然后匹配最接近的ApiController,而所谓的最接近,就是指如果根据拼接的Name获取到了多个匹配项,则获取命名空间节点数最少的那个ApiController,以保证在多次注册路由规则时,能够按照从繁到简的方式匹配出相应的Controller(需要注意的是AreaHttpControllerSelector是以controller作为结束分割点的),举例如下

假定注册了以下路由匹配规则(controller、action均为WebAPI的路由占用字符)

           config.Routes.MapHttpRoute(                name: "DefaultAreaApi",                routeTemplate: "api/{area}/{controller}/{action}/{id}",                defaults: new { id = RouteParameter.Optional }            );            config.Routes.MapHttpRoute(                name: "DefaultApi",                routeTemplate: "api/{controller}/{action}/{id}",                defaults: new { id = RouteParameter.Optional }            );
在Controller目录下存在多层同名且不同层级的Controller,如:

Controller/Area/SameController,对应的命名空间为Controller.Area.SameController
Controller/SameController,对应的命名空间为Controller.SameController

通过api/Area/Same/Get将匹配到Controller/Area/SameController
通过api/Same/Get将匹配到Controller/SameController

相比于参考网址,重新编写的AreaHttpControllerSelector可以支持无限层级的区域,只要命名空间支持,比如

"api/{area1}/{area1}/{area2}/{area3}/{controller}/{action}/{id}"


以下是具体的AreaHttpControllerSelector代码

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Web;using System.Web.Http.Dispatcher;using System.Net.Http;using System.Web.Http;using System.Web.Http.Controllers;using System.Net;namespace WebAPI{    /// <summary>    /// Represents a area System.Web.Http.Dispatcher.IHttpControllerSelector instance    /// </summary>    public class AreaHttpControllerSelector : DefaultHttpControllerSelector    {        private readonly HttpConfiguration _configuration;        /// <summary>        /// Lazy 当前程序集中包含的所有IHttpController反射集合,TKey为小写的Controller        /// </summary>        private readonly Lazy<ILookup<string, Type>> _apiControllerTypes;        private ILookup<string, Type> ApiControllerTypes        {            get            {                return this._apiControllerTypes.Value;            }        }        /// <summary>        /// Initializes a new instance of the AreaHttpControllerSelector class        /// </summary>        /// <param name="configuration"></param>        public AreaHttpControllerSelector(HttpConfiguration configuration)            : base(configuration)        {            this._configuration = configuration;            this._apiControllerTypes = new Lazy<ILookup<string, Type>>(this.GetApiControllerTypes);        }        /// <summary>        /// 获取当前程序集中 IHttpController反射集合        /// </summary>        /// <returns></returns>        private ILookup<string, Type> GetApiControllerTypes()        {            IAssembliesResolver assembliesResolver = this._configuration.Services.GetAssembliesResolver();            return this._configuration.Services.GetHttpControllerTypeResolver()                .GetControllerTypes(assembliesResolver)                .ToLookup(t => t.Name.ToLower().Substring(0, t.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length), t => t);        }        /// <summary>        /// Selects a System.Web.Http.Controllers.HttpControllerDescriptor for the given System.Net.Http.HttpRequestMessage.        /// </summary>        /// <param name="request"></param>        /// <returns></returns>        public override HttpControllerDescriptor SelectController(HttpRequestMessage request)        {            HttpControllerDescriptor des = null;            string controllerName = this.GetControllerName(request);            if (!string.IsNullOrWhiteSpace(controllerName))            {                var groups = this.ApiControllerTypes[controllerName.ToLower()];                if (groups != null && groups.Any())                {                    string endString;                    var routeDic = request.GetRouteData().Values;//存在controllerName的话必定能取到IHttpRouteData                    if (routeDic.Count > 1)                    {                        StringBuilder tmp = new StringBuilder();                        foreach (var key in routeDic.Keys)                        {                            tmp.Append('.');                            tmp.Append(routeDic[key]);                            if (key.Equals(DefaultHttpControllerSelector.ControllerSuffix, StringComparison.CurrentCultureIgnoreCase))                            {//如果是control,则代表命名空间结束                                break;                            }                        }                        tmp.Append(DefaultHttpControllerSelector.ControllerSuffix);                        endString = tmp.ToString();                    }                    else                    {                        endString = string.Format(".{0}{1}", controllerName, DefaultHttpControllerSelector.ControllerSuffix);                    }                    //取NameSpace节点数最少的Type                    var type = groups.Where(t => t.FullName.EndsWith(endString, StringComparison.CurrentCultureIgnoreCase))                        .OrderBy(t => t.FullName.Count(s => s == '.')).FirstOrDefault();//默认返回命名空间节点数最少的第一项                    if (type != null)                    {                        des = new HttpControllerDescriptor(this._configuration, controllerName, type);                    }                }            }            if (des == null)            {                throw new HttpResponseException(request.CreateErrorResponse(HttpStatusCode.NotFound,                    string.Format("No route providing a controller name was found to match request URI '{0}'", request.RequestUri)));            }            return des;        }    }}

而用法就是在Global文件的Application_Start方法中替换注册

GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector),                                                   new AreaHttpControllerSelector(                                                       GlobalConfiguration.Configuration));


WebAPI增加Area以支持无限层级同名Controller