【问题标题】:Select ASP.NET Web API Controller depending on route value (namespace)根据路由值(命名空间)选择 ASP.NET Web API 控制器
【发布时间】:2016-04-05 08:54:40
【问题描述】:

我正在尝试将各种 Web API 插件添加到我的 MVC 网站。复制和粘贴 API 文件的 DLL 允许我使用默认路由 - 主机名/api/{controller}/{id} 调用适当的服务方法,但是当我有同名但位于不同位置的控制器时,它会引发错误( DLL、命名空间)。错误信息是这样的(这是正常的):

找到了多个与名为“Names”的控制器匹配的类型。如果为该请求提供服务的路由 ('api/{controller}/{id}') 发现定义了多个具有相同名称但名称空间不同的控制器,则可能会发生这种情况,这是不受支持的。 'Names' 的请求找到了以下匹配的控制器:ApiExt.NamesController ApiExt1.NamesController

我在不同的 DLL(命名空间)ApiExt、ApiExt1 中有相同的“名称”控制器。

我已经找到了一个类似的主题来根据 API 版本选择控制器 - http://shazwazza.com/post/multiple-webapi-controllers-with-the-same-name-but-different-namespaces/,但这并不是我所需要的。我需要根据路由值选择控制器(命名空间),如下所示:

主机名/api/{namespace}/{controller}/{id}

我相信这是绝对可能的,但我不熟悉覆盖 MVC 控制器选择器(实现 IHttpControllerSelector)。

有什么建议吗?

谢谢。

【问题讨论】:

  • 发布您的 WebAPiConfig 或 RouteConfig。
  • 不确定我是否喜欢在您的 URL 中使用命名空间的想法。为什么不让你的插件注册自己的路由?
  • 我的插件应该表现类似,所以没有必要编写自定义路由。命名空间名称与模块(插件)名称匹配,因此问题现在解决了。谢谢。

标签: c# asp.net asp.net-mvc asp.net-mvc-4 asp.net-web-api


【解决方案1】:

你绝对可以做到这一点。您需要通过实现IHttpControllerSelector 来编写自己的控制器选择器。请参考this链接了解详细的分步说明。

【讨论】:

【解决方案2】:

描述解决方案的博客文章https://blogs.msdn.microsoft.com/webdev/2013/03/07/asp-net-web-api-using-namespaces-to-version-web-apis/ 不包含完整的代码,并且与提供它的内容的链接错误。

这是一个提供类的 blob,来自 Umbraco

https://github.com/WebApiContrib/WebAPIContrib/blob/master/src/WebApiContrib/Selectors/NamespaceHttpControllerSelector.cs,

完整列表以防被删除:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Dispatcher;

namespace WebApiContrib.Selectors
{
    //originally created for Umbraco https://github.com/umbraco/Umbraco-CMS/blob/7.2.0/src/Umbraco.Web/WebApi/NamespaceHttpControllerSelector.cs
    //adapted from there, does not recreate HttpControllerDescriptors, instead caches them
    public class NamespaceHttpControllerSelector : DefaultHttpControllerSelector
    {
        private const string ControllerKey = "controller";
        private readonly HttpConfiguration _configuration;
        private readonly Lazy<HashSet<NamespacedHttpControllerMetadata>> _duplicateControllerTypes;

        public NamespaceHttpControllerSelector(HttpConfiguration configuration)
            : base(configuration)
        {
            _configuration = configuration;
            _duplicateControllerTypes = new Lazy<HashSet<NamespacedHttpControllerMetadata>>(InitializeNamespacedHttpControllerMetadata);
        }

        public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
        {
            var routeData = request.GetRouteData();
            if (routeData == null || routeData.Route == null || routeData.Route.DataTokens["Namespaces"] == null)
                return base.SelectController(request);

            // Look up controller in route data
            object controllerName;
            routeData.Values.TryGetValue(ControllerKey, out controllerName);
            var controllerNameAsString = controllerName as string;
            if (controllerNameAsString == null)
                return base.SelectController(request);

            //get the currently cached default controllers - this will not contain duplicate controllers found so if
            // this controller is found in the underlying cache we don't need to do anything
            var map = base.GetControllerMapping();
            if (map.ContainsKey(controllerNameAsString))
                return base.SelectController(request);

            //the cache does not contain this controller because it's most likely a duplicate, 
            // so we need to sort this out ourselves and we can only do that if the namespace token
            // is formatted correctly.
            var namespaces = routeData.Route.DataTokens["Namespaces"] as IEnumerable<string>;
            if (namespaces == null)
                return base.SelectController(request);

            //see if this is in our cache
            var found = _duplicateControllerTypes.Value.FirstOrDefault(x => string.Equals(x.ControllerName, controllerNameAsString, StringComparison.OrdinalIgnoreCase) && namespaces.Contains(x.ControllerNamespace));
            if (found == null)
                return base.SelectController(request);

            return found.Descriptor;
        }

        private HashSet<NamespacedHttpControllerMetadata> InitializeNamespacedHttpControllerMetadata()
        {
            var assembliesResolver = _configuration.Services.GetAssembliesResolver();
            var controllersResolver = _configuration.Services.GetHttpControllerTypeResolver();
            var controllerTypes = controllersResolver.GetControllerTypes(assembliesResolver);

            var groupedByName = controllerTypes.GroupBy(
                t => t.Name.Substring(0, t.Name.Length - ControllerSuffix.Length),
                StringComparer.OrdinalIgnoreCase).Where(x => x.Count() > 1);

            var duplicateControllers = groupedByName.ToDictionary(
                g => g.Key,
                g => g.ToLookup(t => t.Namespace ?? String.Empty, StringComparer.OrdinalIgnoreCase),
                StringComparer.OrdinalIgnoreCase);

            var result = new HashSet<NamespacedHttpControllerMetadata>();

            foreach (var controllerTypeGroup in duplicateControllers)
            {
                foreach (var controllerType in controllerTypeGroup.Value.SelectMany(controllerTypesGrouping => controllerTypesGrouping))
                {
                    result.Add(new NamespacedHttpControllerMetadata(controllerTypeGroup.Key, controllerType.Namespace,
                        new HttpControllerDescriptor(_configuration, controllerTypeGroup.Key, controllerType)));
                }
            }

            return result;
        }

        private class NamespacedHttpControllerMetadata
        {
            private readonly string _controllerName;
            private readonly string _controllerNamespace;
            private readonly HttpControllerDescriptor _descriptor;

            public NamespacedHttpControllerMetadata(string controllerName, string controllerNamespace, HttpControllerDescriptor descriptor)
            {
                _controllerName = controllerName;
                _controllerNamespace = controllerNamespace;
                _descriptor = descriptor;
            }

            public string ControllerName
            {
                get { return _controllerName; }
            }

            public string ControllerNamespace
            {
                get { return _controllerNamespace; }
            }

            public HttpControllerDescriptor Descriptor
            {
                get { return _descriptor; }
            }
        }
    }
}

然后只需将命名空间令牌添加到路由中

route.DataTokens["Namespaces"] = new string[] {"Foo.Controllers"};

缓存也为生产做好了更多准备。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-04-15
    • 2023-03-16
    • 2014-12-13
    • 1970-01-01
    • 2015-07-05
    • 1970-01-01
    • 2012-03-07
    • 1970-01-01
    相关资源
    最近更新 更多