在ASP.Net Core 2.2 MVC中使用端点路由时,top如何正确覆盖IUrlHelper?

时间:2019-11-04 19:16:58

标签: c# asp.net-core asp.net-core-mvc asp.net-core-2.2

我需要重写ASP.NET Core 2.2项目中的UrlHelper实现。

我创建了一个名为MyUrlHelper的类,像这样覆盖了UrlHelper

public class MyUrlHelper : UrlHelper
{
    public MyUrlHelper(ActionContext actionContext)
        : base(actionContext)
    {
    }

    public override string Content(string contentPath)
    {
        // do something new...

        return base.Content(contentPath);
    }
}

然后,我创建了一个名为MyUrlHelperFactory的类,该类实现了IUrlHelperFactory接口,

public class MyUrlHelperFactory : IUrlHelperFactory
{
    public IUrlHelper GetUrlHelper(ActionContext context)
    {
        return new MyUrlHelper(context);
    }
}

最后,我尝试通过在Startup.ConfigureServices()行之后的services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2)方法中添加以下行来替换DI容器中的实现。

但这会引发以下错误

  

处理请求时发生未处理的异常。   ArgumentOutOfRangeException:索引超出范围。必须为非负数,并且小于集合的大小。

这是堆栈跟踪

ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index

    System.Collections.Generic.List<T>.get_Item(int index)
    Microsoft.AspNetCore.Mvc.Routing.UrlHelper.get_Router()
    Microsoft.AspNetCore.Mvc.Routing.UrlHelper.GetVirtualPathData(string routeName, RouteValueDictionary values)
    Microsoft.AspNetCore.Mvc.Routing.UrlHelper.Action(UrlActionContext actionContext)
    Microsoft.AspNetCore.Mvc.UrlHelperExtensions.Action(IUrlHelper helper, string action, string controller, object values, string protocol, string host, string fragment)
    Microsoft.AspNetCore.Mvc.ViewFeatures.DefaultHtmlGenerator.GenerateActionLink(ViewContext viewContext, string linkText, string actionName, string controllerName, string protocol, string hostname, string fragment, object routeValues, object htmlAttributes)
    Microsoft.AspNetCore.Mvc.TagHelpers.AnchorTagHelper.Process(TagHelperContext context, TagHelperOutput output)
    Microsoft.AspNetCore.Razor.TagHelpers.TagHelper.ProcessAsync(TagHelperContext context, TagHelperOutput output)
    Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner.RunAsync(TagHelperExecutionContext executionContext)
    AspNetCore.Views_Shared__Layout.<ExecuteAsync>b__44_1()
    Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext.SetOutputContentAsync()
    AspNetCore.Views_Shared__Layout.ExecuteAsync()
    Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context)
    Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync(IRazorPage page, ViewContext context, bool invokeViewStarts)
    Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderLayoutAsync(ViewContext context, ViewBufferTextWriter bodyWriter)
    Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync(ViewContext context)
    Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, string contentType, Nullable<int> statusCode)
    Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ActionContext actionContext, IView view, ViewDataDictionary viewData, ITempDataDictionary tempData, string contentType, Nullable<int> statusCode)
    Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor.ExecuteAsync(ActionContext context, ViewResult result)
    Microsoft.AspNetCore.Mvc.ViewResult.ExecuteResultAsync(ActionContext context)
    Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeResultAsync(IActionResult result)
    Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResultFilterAsync<TFilter, TFilterAsync>()
    Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResultExecutedContext context)
    Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.ResultNext<TFilter, TFilterAsync>(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
    Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeResultFilters()
    Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()
    Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
    Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
    Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()
    Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()
    Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
    Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.Invoke(HttpContext httpContext)
    Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
    Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

但是,如果禁用框架中的“端点路由”功能,则不会出现任何错误。但是,我想使用端点路由。如何在不禁用端点路由的情况下正确覆盖UrlHelper的实现?

这是我禁用端点路由的方式

services.AddMvc(options =>
{
    options.EnableEndpointRouting = false;
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

1 个答案:

答案 0 :(得分:2)

Microsoft对UrlHelperFactory的实现告诉我,端点路由有一个不同的url-helper类。

如果在使用端点路由时仅想覆盖UrlHelper,则需要覆盖EndpointRoutingUrlHelper类而不是UrlHelper。为了安全起见,您可能需要覆盖这两个类。

不幸的是,AspNet团队宣布EndpointRoutingUrlHelper类为内部类,因此您不能直接重写它。这并不意味着我们不能复制源代码:)

无论如何,要覆盖UrlHelper,首先要创建我们自己的端点路由助手版本(即MyEndpointRoutingUrlHelper

public class MyEndpointRoutingUrlHelper : UrlHelperBase
{
    private readonly LinkGenerator _linkGenerator;

    public MyEndpointRoutingUrlHelper(
        ActionContext actionContext,
        LinkGenerator linkGenerator)
        : base(actionContext)
    {
        _linkGenerator = linkGenerator ?? throw new ArgumentNullException(nameof(linkGenerator));
    }

    public override string Action(UrlActionContext urlActionContext)
    {
        if (urlActionContext == null)
        {
            throw new ArgumentNullException(nameof(urlActionContext));
        }

        var values = GetValuesDictionary(urlActionContext.Values);

        if (urlActionContext.Action != null)
        {
            values["action"] = urlActionContext.Action;

        }
        else if (!values.ContainsKey("action") && AmbientValues.TryGetValue("action", out var action))
        {
            values["action"] = action;
        }

        if (urlActionContext.Controller != null)
        {
            values["controller"] = urlActionContext.Controller;
        }
        else if (!values.ContainsKey("controller") && AmbientValues.TryGetValue("controller", out var controller))
        {
            values["controller"] = controller;
        }

        var path = _linkGenerator.GetPathByRouteValues(
            ActionContext.HttpContext,
            routeName: null,
            values,
            fragment: new FragmentString(urlActionContext.Fragment == null ? null : "#" + urlActionContext.Fragment));

        return GenerateUrl(urlActionContext.Protocol, urlActionContext.Host, path);
    }

    public override string RouteUrl(UrlRouteContext routeContext)
    {
        if (routeContext == null)
        {
            throw new ArgumentNullException(nameof(routeContext));
        }

        var path = _linkGenerator.GetPathByRouteValues(
            ActionContext.HttpContext,
            routeContext.RouteName,
            routeContext.Values,
            fragment: new FragmentString(routeContext.Fragment == null ? null : "#" + routeContext.Fragment));

        return GenerateUrl(routeContext.Protocol, routeContext.Host, path);
    }

    public override string Content(string contentPath)
    {
        // override this method how you see fit

        return base.Content(contentPath);
    }
}

现在,让我们使用自己的网址帮助器实现IUrlHelperFactory

public class MyUrlHelperFactory : IUrlHelperFactory
{
    public IUrlHelper GetUrlHelper(ActionContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        var httpContext = context.HttpContext;

        if (httpContext == null)
        {
            throw new ArgumentException(nameof(context.HttpContext));
        }

        if (httpContext.Items == null)
        {
            throw new ArgumentException(nameof(context.HttpContext.Items));
        }

        // Perf: Create only one UrlHelper per context
        if (httpContext.Items.TryGetValue(typeof(IUrlHelper), out var value) && value is IUrlHelper)
        {
            return (IUrlHelper)value;
        }

        IUrlHelper urlHelper;
        var endpointFeature = httpContext.Features.Get<IEndpointFeature>();
        if (endpointFeature?.Endpoint != null)
        {
            var services = httpContext.RequestServices;
            var linkGenerator = services.GetRequiredService<LinkGenerator>();

            urlHelper = new MyEndpointRoutingUrlHelper(context, linkGenerator);
        }
        else
        {
            urlHelper = new MyUrlHelper(context);
        }

        httpContext.Items[typeof(IUrlHelper)] = urlHelper;

        return urlHelper;
    }
}

最后,在Startup.ConfigureServices(...)行之后的services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);方法中

添加此行以覆盖已注册的IUrlHelperFactory实现。

services.Replace(new ServiceDescriptor(typeof(IUrlHelperFactory), new MyUrlHelperFactory()));

我希望这对您有帮助