【以前技术归档】让WebApi路由配置像Mvc一样支持namespaces参数

很多年前就知道我软的WebApi的路由模板配置方法不支持namespaces参数的事,所以也追随我软的脚步在程序中不再造namespaces参数的设计与实现,最近小组里有人思维不够开源,想着使用namespaces参数把启动项目和Api具体实现分成两个项目,目的大概是为了保护源码,我极度排斥这种老旧思想,不过既然有人还惦念namespaces的事儿,也不妨从技术的角度出发去玩一玩。于是乎一顿大搜索后,经过学习、分析、理解、总结、设计、试验后,最终实现了让WebApi的路由配置方法像Mvc一样支持namespaces参数,具体代码如下:

第一个文件:HttpRouteCollectionExtensions,通过扩展方法的形式实现MapHttpRoute支持namespaces参数

public static class HttpRouteCollectionExtensions
    {
        public static IHttpRoute MapHttpRoute(this HttpRouteCollection routes, string name, string routeTemplate, object defaults, string[] namespaces)
        {
            return routes.MapHttpRoute(name, routeTemplate, defaults, null, null, namespaces);
        }
        public static IHttpRoute MapHttpRoute(this HttpRouteCollection routes, string name, string routeTemplate, object defaults, object constraints, HttpMessageHandler handler, string[] namespaces)
        {
            if (routes == null)
            {
                throw new ArgumentNullException("routes");
            }

            var dvd = new HttpRouteValueDictionary(defaults);
            var cvd = new HttpRouteValueDictionary(constraints);
            var nvd = new HttpRouteValueDictionary(new { namespaces = namespaces });

            var route = routes.CreateRoute(routeTemplate, dvd, cvd, nvd, handler);
            routes.Add(name, route);
            return route;
        }
    }

第二个文件:NamespaceHttpControllerSelector,控制器的命名空间选择器,用于代替默认的选择器

public class NamespaceHttpControllerSelector : IHttpControllerSelector
    {
        private const string NamespaceKey = "namespaces";
        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)
            {
                // For the dictionary key, strip "Controller" from the end of the type name.
                var controllerName = "";
                var cItem = t.GetCustomAttribute(typeof(RoutePrefixAttribute));
                if (cItem != null)
                {
                    var segments = ((RoutePrefixAttribute)cItem).Prefix.Split('/');
                    controllerName = segments[segments.Length - 1];
                }
                else
                {
                    // This matches the behavior of DefaultHttpControllerSelector.
                    controllerName = t.Name.Remove(t.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length);
                }

                var key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", t.Namespace, controllerName);

                // Check for duplicate keys.
                if (dictionary.Keys.Contains(key))
                {
                    _duplicates.Add(key);
                }
                else
                {
                    dictionary[key] = new HttpControllerDescriptor(_configuration, controllerName, 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;
        }
        private T GetRouteVariable<T>(IHttpRouteData routeData, string name)
        {
            object result = null;
            if (routeData.Values.TryGetValue(name, out result))
            {
                return (T)result;
            }
            return default(T);
        }
        private string GetControllerKey(IHttpRouteData routeData, string controllerName)
        {
            var keys = _controllers.Value.Keys.ToList();
            object namespaceName = null;
            if (routeData.Route.DataTokens == null || !routeData.Route.DataTokens.TryGetValue(NamespaceKey, out namespaceName))
            {
                return keys.FirstOrDefault(o => o.ToLower().EndsWith(controllerName.ToLower()));
            }

            //get the defined namespace
            string[] namespaces = (string[])namespaceName;
            for (int i = 0; i < namespaces.Length; i++)
            {
                var nss = keys.Where(o => o.StartsWith(namespaces[i])).ToList();
                if (nss != null && nss.Count() > 0)
                {
                    return nss.FirstOrDefault(o => o.ToLower().EndsWith(controllerName.ToLower()));
                }
            }
            return null;
        }

        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 controllerName = GetRouteVariable<string>(routeData, ControllerKey);
            if (controllerName == null)
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }

            // Find a matching controller.
            string key = GetControllerKey(routeData, controllerName);
            if (key == null)
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }

            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;
        }
    }

第三个文件:App_Start/WebApiConfig,不再使用默认的路由模板配置,而使用自定义实现

public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
             Web API 路由
            //config.MapHttpAttributeRoutes();
            //config.Routes.MapHttpRoute(
            //    name: "DefaultApi",
            //    routeTemplate: "api/{controller}/{id}",
            //    defaults: new { id = RouteParameter.Optional }
            //);

            // Web API 支持namespaces
            config.Services.Replace(typeof(IHttpControllerSelector), new NamespaceHttpControllerSelector(config));
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{action}/{id}",
                defaults: new { id = RouteParameter.Optional },
                namespaces: new string[] { "xxxx.Controllers" }
            );
        }
    }

鉴于上述代码的基础来源于网络多处,虽然由本人重新设计后实现了namespaces参数的支持,但不能算原创本人只能算是搬运工,即:取于网络,还于网络,若能帮到您、我便很开心了。

特别注意:在Asp.Net Web API项目里不要创建名称相同的Controller文件,如A文件夹里有TestController,B文件夹里也有TestController,这种操作是不被允许的

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值