![](/img/trans.png)
[英]ASP.Net MVC can't find a namespace that is not included in the controller
[英]How can I find controller with special namespace in asp.net core
我制作了一个具有默认 controller 的库。(例如HomeController
, AccountController
)
然后我创建一个 asp.net 核心网络应用程序,并导入该库。
当我需要更改操作( Home/Index
)时,我创建一个HomeController
。然后运行,它抛出AmbiguousMatchException
。
找了一个idea IActionConstraint
,测试项目在Github
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class ControllerNamespaceConstraintAttribute : Attribute, IActionConstraint
{
public int Order => 1;
/// <inheritdoc />
public bool Accept(ActionConstraintContext context)
{
return IsValidForRequest(context.RouteContext, context.CurrentCandidate.Action);
}
public bool IsValidForRequest(RouteContext routeContext, ActionDescriptor action)
{
var actionNamespace = ((ControllerActionDescriptor)action).MethodInfo.DeclaringType.Namespace;
Console.WriteLine("IsValidForRequest:" + actionNamespace);
Console.WriteLine("routeContext.RouteData.DataTokens:" + routeContext.RouteData.DataTokens.Count());
if (routeContext.RouteData.DataTokens.ContainsKey("Namespace"))
{
var dataTokenNamespace = (string)routeContext.RouteData.DataTokens.FirstOrDefault(dt => dt.Key == "Namespace").Value;
return dataTokenNamespace == actionNamespace;
}
return true;
}
}
.......startup.cs
app.UseEndpoints(endpoints =>
{
var dataTokens = new RouteValueDictionary();
var ns = new[] { "TestMultipleController.Controllers" };
dataTokens["Namespaces"] = ns;
endpoints.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}",
defaults: null,
constraints: null,
dataTokens: dataTokens);
});
..... HomeController.cs
[ControllerNamespaceConstraint]
public class HomeController : Controller
{
public IActionResult Index()
{
return Content(RouteData.DataTokens.Count.ToString());
}
}
但它也抛出同样的异常。
调试器,我发现它无法获取DataTokens
中的IActionConstraint
那我该怎么办?
据我了解,您希望当前项目(入口项目)中声明的控制器操作对覆盖 class 库中声明的控制器操作对。
这有点棘手,需要您的特定设计来处理至少 2 个控制器操作对(对于传统路由)之间的这种歧义。 在设计方面,很难说出所有细节,所以我假设 2 个控制器-动作对被认为是重复的,并且在运行时选择动作时会导致歧义,如果它们:
操作方法的参数无关紧要(例如: Get
将匹配Get(int id)
,...)。 实际上,这就是默认动作匹配在找到最佳匹配动作时使用的逻辑。 我不太确定是否有其他标准,但这里的重点是向您展示如何通过选择最匹配的操作(在当前执行程序集中定义的操作而不是在 class 库中定义的操作)来解决这种歧义。
所以这里的想法是创建您的自定义IApplicationModelConvention
。 自定义约定可以帮助您应用一些自定义约定来影响加载的控制器、动作、路由...通过该约定,我们可以访问所有ControllerModels
,每个 ControllerModels 都公开了自己的一组有效ActionModels
。 因此,通过删除不需要的ActionModel
,您可以解决歧义问题(因为只剩下一个ActionModel
)。
这是详细信息:
public class DuplicateActionsRemovalApplicationModelConvention : IApplicationModelConvention
{
public void Apply(ApplicationModel application)
{
var currentExecutingAsm = Assembly.GetExecutingAssembly();
var overriddenActions = application.Controllers.GroupBy(e => e.ControllerName)
//select only duplicate controllers
.Where(g => g.Count() > 1)
.SelectMany(g => g.SelectMany(e => e.Actions)
.GroupBy(o => o, ActionModelMatchingEqualityComparer.Instance)
//select only duplicate actions
.Where(k => k.Count() > 1)
.SelectMany(e => e.OrderByDescending(x => x.Controller.ControllerType.Assembly == currentExecutingAsm)
//select all except the action defined in the current executing assembly
.Skip(1)))
.Select(e => (controller: e.Controller, action: e));
//for each overridden action, just remove it from the owner controller
//this effectively means that the removed action will be overridden
//by the only remaining one (defined in the current executing assembly)
foreach(var overriddenAction in overriddenActions)
{
overriddenAction.controller.Actions.Remove(overriddenAction.action);
}
}
}
这是上面代码中使用的ActionModel
的自定义IEqualityComparer
。 它有助于通过我们在开头描述的逻辑匹配 2 个动作。
public class ActionModelMatchingEqualityComparer : IEqualityComparer<ActionModel>
{
static readonly Lazy<ActionModelMatchingEqualityComparer> _instanceLazy =
new Lazy<ActionModelMatchingEqualityComparer>(() => new ActionModelMatchingEqualityComparer());
public static ActionModelMatchingEqualityComparer Instance => _instanceLazy.Value;
public bool Equals(ActionModel x, ActionModel y)
{
return string.Equals(x.ActionName, y.ActionName, StringComparison.OrdinalIgnoreCase) &&
x.Attributes.OfType<IActionHttpMethodProvider>().SelectMany(e => e.HttpMethods).DefaultIfEmpty("GET")
.Intersect(y.Attributes.OfType<IActionHttpMethodProvider>().SelectMany(e => e.HttpMethods).DefaultIfEmpty("GET"),
StringComparer.OrdinalIgnoreCase).Any();
}
public int GetHashCode(ActionModel obj)
{
return (obj.ActionName?.GetHashCode() ?? 0) ^
obj.Attributes.OfType<IActionHttpMethodProvider>()
.SelectMany(e => e.HttpMethods.Select(o => o.ToLower())).Distinct()
.OrderBy(e => e).Aggregate(0, (c, e) => (c * 13) ^ e.GetHashCode());
}
}
在上面的代码中,我们使用由所有HttpMethodAttribute
(例如: HttpMethodAttribute
本身、 HttpPostAttribute
等)实现的类型IActionHttpMethodProvider
来检查这 2 个操作是否至少支持一个相同的 http 方法。
最后,您可以在全局注册您的自定义IApplicationModelConvention
,如下所示(.net 核心 2.2 的代码):
//code in ConfigureServices method in Startup.cs
services.AddMvc(o => {
//...
o.Conventions.Add(new DuplicateActionsRemovalApplicationModelConvention());
//...
});
现在,当您运行项目时,引用的 class 库中的所有重复操作都将被删除并被当前执行程序集中定义的操作(被认为与被覆盖的操作匹配)覆盖。 请注意,当前正在执行的程序集也是包含Startup.cs
(主要/入口项目)的程序集,因为这是您添加自定义约定DuplicateActionsRemovalApplicationModelConvention
的地方
这是另一种使用IActionSelector
的方法,它更简单。 当谈到选择一些东西时,我的另一个答案删除了我们 select 中的东西。然而,这种方法不会删除东西,而是改变我们 select 的方式。 所以基本上这两种方法在想法上是完全不同的。 我相信这个IActionSelector
可能会更好:
public class CustomActionSelector : ActionSelector
{
public CustomActionSelector(IActionDescriptorCollectionProvider actionDescriptorCollectionProvider,
ActionConstraintCache actionConstraintCache, ILoggerFactory loggerFactory) : base(actionDescriptorCollectionProvider, actionConstraintCache, loggerFactory)
{
}
protected override IReadOnlyList<ActionDescriptor> SelectBestActions(IReadOnlyList<ActionDescriptor> actions)
{
var executingAssembly = Assembly.GetExecutingAssembly();
var prioritizedActions = actions.GroupBy(e => e is ControllerActionDescriptor ca ? ca.ControllerName : "")
.SelectMany(g => g.Key == "" || g.Count() == 1 ? g :
g.GroupBy(e => (e as ControllerActionDescriptor).ActionName)
.SelectMany(o => o.Count() == 1 ? o :
o.OrderByDescending(e => (e as ControllerActionDescriptor).ControllerTypeInfo.Assembly == executingAssembly)
.Take(1))).ToList();
return base.SelectBestActions(prioritizedActions);
}
}
您需要像这样将自定义操作选择器注册为 singleton:
services.AddSingleton<IActionSelector, CustomActionSelector>();
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.