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(概念、模板、创建、外部代理、删除等)
152 0
|
6月前
|
网络架构
ES6中新增的rest剩余参数在函数内部的使用问题
ES6 中引入了 rest 参数(...变量名),用于获取函数内不确定的多余参数,注意只能放在所有参数的最后一个
29 0
定义一个事件需要单独新建一个文件吗?底层原理是什么?
定义一个事件需要单独新建一个文件吗?底层原理是什么?
|
缓存
读源码长知识 | 动态扩展类并绑定生命周期的新方式
在阅读viewModelScope源码时,发现了一种新的方式。 协程需隶属于某 CoroutineScope ,以实现structured-concurrency,而 CoroutineScope 应
136 0
|
PHP 数据库
【laravel项目】@13 解决访问器与数据填充冲突(1)
【laravel项目】@13 解决访问器与数据填充冲突
85 0
【laravel项目】@13 解决访问器与数据填充冲突(1)
|
PHP 数据库
【laravel项目】@13 解决访问器与数据填充冲突(2)
【laravel项目】@13 解决访问器与数据填充冲突
99 0
【laravel项目】@13 解决访问器与数据填充冲突(2)
|
PHP
【laravel项目】@13 解决访问器与数据填充冲突(3)
【laravel项目】@13 解决访问器与数据填充冲突
69 0
【laravel项目】@13 解决访问器与数据填充冲突(3)
|
存储 开发框架 前端开发
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命名空间的特性
如何使用配置的方式修改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的字段标签,以及背后的工作原理
Cloud for Customer custom BO创建时间随着行项目数量增加而增加的关系
Cloud for Customer custom BO创建时间随着行项目数量增加而增加的关系
66 0