简体   繁体   中英

How top correctly override the IUrlHelper while using Endpoint-Routing in ASP.Net Core 2.2 MVC?

I have a need to override the UrlHelper implementation in an ASP.NET Core 2.2 project.

I created a class called MyUrlHelper that overrides the UrlHelper class like so

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

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

        return base.Content(contentPath);
    }
}

Then, I created a class called MyUrlHelperFactory which implements the IUrlHelperFactory interface like so

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

Finally, I tried to replace the implementation in the DI container by adding the following line in the Startup.ConfigureServices() method after services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2) line.

But that throws the following error

An unhandled exception occurred while processing the request. ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.

Here is the stack-trace

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)

However, if I disable the Endpoint-Routing feature in the framework, I get no errors. But, I want to use Endpoint-Route. How can I correctly override the implementation of the UrlHelper without disabling the Endpoint-Routing?

Here is how I disabled the Endpoint-Routing

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

Microsoft's implementation of the UrlHelperFactory tells me that there is a different url-helper class for the Endpoint-Routing.

If you only want to override the UrlHelper when using Endpoint routing, you'll need to override EndpointRoutingUrlHelper class not UrlHelper . You may want to override both classes to be safe.

Unfortunately, the AspNet team declared the EndpointRoutingUrlHelper class as internal so you can't directly override it. That does not mean we can't copy the source code:)

Anyhow, to override the UrlHelper, first lets create our own version of the endpoint routing helpers (ie, 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);
    }
}

Now, lets implement IUrlHelperFactory using our own url-helpers

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;
    }
}

Finally, in the Startup.ConfigureServices(...) method after the line services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

Add this line to override the registered IUrlHelperFactory implementation.

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

I hope this helps

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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