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.