• MVC5为WebAPI添加命名空间的支持


    前言

    默认情况下,微软提供的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控制器。

  • 相关阅读:
    C#继承
    正则表达式
    C#笔记
    斐波那契数
    out参数
    重载和重写
    数组元素交换位置
    Win10图标显示不正常解决办法
    Linux添加sftp用户并限制其访问目录
    pclzip 解压的文件去掉文件夹
  • 原文地址:https://www.cnblogs.com/amylis_chen/p/7048651.html
Copyright © 2020-2023  润新知