C# 根据路由值(命名空间)选择ASP.NET Web API控制器
我正在尝试向我的MVC网站添加各种Web API插件。通过复制和粘贴API文件的DLL,我可以使用默认路由(hostname/API/{controller}/{id})调用适当的服务方法,但是当我有同名但位于不同位置(DLL、命名空间)的控制器时,它会抛出一个错误。错误消息如下所示(这是正常的): 找到多个与名为“Names”的控制器匹配的类型。如果为该请求提供服务的路由('api/{controller}/{id}')发现多个控制器定义的名称相同但名称空间不同,则可能发生这种情况,这是不受支持的。“名称”请求已找到以下匹配的控制器:ApiExt.NamesController ApiExt1.NamesController 我在不同的DLL(名称空间)中有相同的“名称”控制器ApiExt,ApiExt1 我已经找到了一个类似的主题来根据API版本选择控制器,但这并不是我所需要的。我需要根据路由值选择控制器(名称空间),如下所示: 主机名/api/{namespace}/{controller}/{id} 我相信这是绝对可能的,但我不熟悉重写MVC控制器选择器(实现IHttpControllerSelector) 有什么建议吗C# 根据路由值(命名空间)选择ASP.NET Web API控制器,c#,asp.net,asp.net-mvc,asp.net-mvc-4,asp.net-web-api,C#,Asp.net,Asp.net Mvc,Asp.net Mvc 4,Asp.net Web Api,我正在尝试向我的MVC网站添加各种Web API插件。通过复制和粘贴API文件的DLL,我可以使用默认路由(hostname/API/{controller}/{id})调用适当的服务方法,但是当我有同名但位于不同位置(DLL、命名空间)的控制器时,它会抛出一个错误。错误消息如下所示(这是正常的): 找到多个与名为“Names”的控制器匹配的类型。如果为该请求提供服务的路由('api/{controller}/{id}')发现多个控制器定义的名称相同但名称空间不同,则可能发生这种情况,这是不受支
谢谢。你一定能做到这一点。您需要通过实现
IHttpControllerSelector
来编写自己的控制器选择器。请参阅链接以获得详细的分步说明。您完全可以做到这一点。您需要通过实现IHttpControllerSelector
来编写自己的控制器选择器。请参阅链接以获得详细的分步解释。描述解决方案的博客文章不包含完整的代码,并且有一个错误的链接指向用于提供该解决方案的内容
这是一个提供类的blob,来自Umbraco
,
完整列表(如果已删除):
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; }
}
}
}
}
通过缓存,它更适合于生产。描述解决方案的博客文章没有包含完整的代码,并且有一个错误的链接指向用于提供该解决方案的内容 这是一个提供类的blob,来自Umbraco , 完整列表(如果已删除):
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; }
}
}
}
}
缓存也为生产做好了更多准备。发布您的WebAPiConfig或RouteConfig。我不确定我是否喜欢在URL中使用名称空间的想法。为什么不让你的插件注册他们自己的路由呢?我的插件应该有类似的行为,所以没有必要编写自定义路由。名称空间名称与模块(插件)名称匹配,因此问题现在得到解决。谢谢。发布你的WebAPiConfig或RouteConfig。我不确定我是否喜欢在你的URL中使用名称空间。为什么不让你的插件注册他们自己的路由呢?我的插件应该有类似的行为,所以没有必要编写自定义路由。名称空间名称与模块(插件)名称匹配,因此问题现在得到解决。谢谢,谢谢。这真的很有帮助。请注意,您提供的链接中有一个小语法错误,因此更深层次的链接提供了一个完整的解决方法。非常感谢。这真的很有帮助。请注意,您提供的链接中有一个小语法错误,因此更深层次的链接提供了一个完整的解决方法。