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

简介: 原文:WebAPI增加Area以支持无限层级同名Controller 微软的WebAPI默认实现逻辑 默认实现中不支持同名Controller,否则在访问时会报HttpError,在网上找到了各种路由自实现,如 给ASP.
原文: 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));


目录
相关文章
|
5月前
|
Kubernetes 负载均衡 网络协议
k8s学习-Service(概念、模板、创建、外部代理、删除等)
k8s学习-Service(概念、模板、创建、外部代理、删除等)
159 0
|
6月前
|
网络架构
ES6中新增的rest剩余参数在函数内部的使用问题
ES6 中引入了 rest 参数(...变量名),用于获取函数内不确定的多余参数,注意只能放在所有参数的最后一个
29 0
多model项目下,某个项目引用了公共lib下的service, 其他模块想不受影响的启动解决办法
多model项目下,某个项目引用了公共lib下的service, 其他模块想不受影响的启动解决办法
58 0
|
开发框架 JSON 前端开发
【C#】.net core2.1,自定义全局类对API接口和视图页面产生的异常统一处理
在开发一个网站项目时,异常处理和过滤功能是最基础的模块 本篇文章就来讲讲,如何自定义全局异常类来统一处理
209 0
|
缓存
读源码长知识 | 动态扩展类并绑定生命周期的新方式
在阅读viewModelScope源码时,发现了一种新的方式。 协程需隶属于某 CoroutineScope ,以实现structured-concurrency,而 CoroutineScope 应
137 0
VS Code项目中共享自定义的代码片段方案
VS Code项目中共享自定义的代码片段方案
|
存储 开发框架 前端开发
C#特性 System.ComponentModel命名名空间属性方法大全,Syst em.ComponentModel命名空间的特性
目录: System.ComponentModel 特性命名空间与常用类 System.ComponentModel.DataAnnotations ComponentModel - Classes 类 ComponentModel - Structs 结构体 ComponentModel - Interfaces 界面 ComponentModel - Enums 枚举 ComponentModel - Delegates 委托
241 0
C#特性 System.ComponentModel命名名空间属性方法大全,Syst em.ComponentModel命名空间的特性
|
Android开发
【JetPack】为现有 Android 项目配置视图绑定 ( ViewBinding ) 模块 ( 视图绑定不影响传统布局操作 | 视图绑定类关联 Activity | 视图绑定类本质 )(二)
【JetPack】为现有 Android 项目配置视图绑定 ( ViewBinding ) 模块 ( 视图绑定不影响传统布局操作 | 视图绑定类关联 Activity | 视图绑定类本质 )(二)
161 0
|
Java Android开发
【JetPack】为现有 Android 项目配置视图绑定 ( ViewBinding ) 模块 ( 视图绑定不影响传统布局操作 | 视图绑定类关联 Activity | 视图绑定类本质 )(一)
【JetPack】为现有 Android 项目配置视图绑定 ( ViewBinding ) 模块 ( 视图绑定不影响传统布局操作 | 视图绑定类关联 Activity | 视图绑定类本质 )(一)
185 0
【JetPack】为现有 Android 项目配置视图绑定 ( ViewBinding ) 模块 ( 视图绑定不影响传统布局操作 | 视图绑定类关联 Activity | 视图绑定类本质 )(一)
如何使用配置的方式修改SAP C4C UI的字段标签,以及背后的工作原理
I was asked by one partner that it is expected to adapt the label of “New” button into “Add”, and change the text of first menu item from “Add” to “From Contact”.
如何使用配置的方式修改SAP C4C UI的字段标签,以及背后的工作原理