繁体   English   中英

如何在 asp.net 核心中找到具有特殊命名空间的 controller

[英]How can I find controller with special namespace in asp.net core

我制作了一个具有默认 controller 的库。(例如HomeControllerAccountController

然后我创建一个 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 个控制器-动作对被认为是重复的,并且在运行时选择动作时会导致歧义,如果它们:

  • 具有相同的 controller 名称(不区分大小写)
  • 具有相同的操作名称(不区分大小写)
  • 支持一种相同的 http 方法(get、post、...)

操作方法的参数无关紧要(例如: 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM