Как сверху правильно переопределить IUrlHelper при использовании Endpoint-Routing в ASP.Net Core 2.2 MVC? - PullRequest
3 голосов
/ 04 ноября 2019

У меня есть необходимость переопределить реализацию UrlHelper в проекте ASP.NET Core 2.2.

Я создал класс с именем 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);
    }
}

Наконец, я попытался заменить реализацию в контейнере DI, добавив следующую строку в методе Startup.ConfigureServices() после строки services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2).

Но это выдает следующееошибка

Произошло необработанное исключение при обработке запроса. 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)

Однако, если я отключу функцию маршрутизации конечной точки врамки, я не получаю ошибок. Но я хочу использовать Endpoint-Route. Как правильно переопределить реализацию UrlHelper без отключения маршрутизации конечной точки?

Вот как я отключил маршрутизацию конечной точки

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

1 Ответ

2 голосов
/ 05 ноября 2019

Реализация Microsoft UrlHelperFactory говорит мне, что есть другой класс url-helper для Endpoint-Routing.

Если вы хотите переопределить 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, используя наши собственные url-помощники

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()));

Надеюсь, это поможет

...