dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.45k stars 10.03k forks source link

Make it easy to configure a host/scheme for absolute URLs with LinkGenerator #14192

Open RehanSaeed opened 5 years ago

RehanSaeed commented 5 years ago

Is your feature request related to a problem? Please describe.

LinkGenerator

LinkGenerator requires the HttpContext to generate a full absolute URL which also contains the scheme, hostname and optional port. Outside of the controller, this means I have to also inject the IHttpContextAccessor into my constructor and pass it in like so:

this.linkGenerator.GetUriByRouteValues(
    this.httpContextAccessor.HttpContext,
    "GetPage",
    new() { First = 1, Last = 10 });

This requires a lot of ceremony and additional noise.

IUrlHelper

IUrlHelper does not require the HttpContext if you add this code to the Startup. We use the IActionContextAccessor and IUrlHelperFactory to register IUrlHelper into the IoC container.

services
    .AddSingleton<IActionContextAccessor, ActionContextAccessor>()
    .AddScoped(x => x
        .GetRequiredService<IUrlHelperFactory>()
        .GetUrlHelper(x.GetRequiredService<IActionContextAccessor>().ActionContext))

Then if you use the following extension method to create your URL's:

public static string AbsoluteRouteUrl(
    this IUrlHelper urlHelper,
    string routeName,
    object routeValues = null) =>
    urlHelper.RouteUrl(routeName, routeValues, urlHelper.ActionContext.HttpContext.Request.Scheme);

Here is the usage:

this.urlHelper.AbsoluteRouteUrl("GetPage", new() { First = 1, Last = 10 });

I think you'll agree this is much nicer.

Describe the solution you'd like

I would like a way to generate URL's without having to inject the IHttpContextAccessor and LinkGenerator just to generate a single URL. Or a way to use IUrlHelper without all the setup code to get it working nicely.

FilipVanBouwel commented 5 years ago

I agree. This should be less cumbersome. I don't care if it's IUrlHelper or IUrlGenerator, as long as it is fully setup by the DI system as part of ASP.NET.

rynowak commented 5 years ago

LinkGenerator requires the HttpContext to generate a URL.

This is not true. LinkGenerator is specifically designed to work without an HTTP context.

See: https://github.com/aspnet/AspNetCore/blob/master/src/Mvc/Mvc.Core/src/Routing/ControllerLinkGeneratorExtensions.cs#L84 and similar methods.

RehanSaeed commented 5 years ago

As far as I can work out this is not true for absolute URL's containing the scheme, host and port. Using this code:

this.linkGenerator.GetPathByRouteValues("GetPage", new { First = 1, Last = 10 });

Outputs:

/foo?first=1&last=10

rynowak commented 5 years ago

@RehanSaeed - can you provide some more information? I'm not sure how I'm supposed to draw a conclusion from that.

RehanSaeed commented 5 years ago

I want to generate absolute URL's with the full scheme, host and optionally a port i.e. https://example.com/foo?bar=1. Why? Well, one reason is that in API's a full URL is much easier to consume from JavaScript.

Today, if you want to do this, you have to have the HttpContext and pass it into the LinkGenerator or IUrlHelper as I've shown in my code samples above. This is used to pull the scheme and host from the current request and stick it into the URL.

That's all well and good in a controller where you have easy access to the HttpContext. However, if I'm outside of the controller and want to generate a URL, then if I use LinkGenerator, then I am forced to also inject IHttpContextAccessor or if I'm using IUrlHelper, it's possible to avoid injecting IHttpContextAccessor but you have to add some additional setup in Startup and use an extension method. All code is shown above.

Both of these solutions are noisy and require extra work, just to get an absolute URL. Ideally, I'd want a method that does all of that for me and allows me to generate URL's without all the extra noise and ceremony.

Does that clear things up?

FilipVanBouwel commented 5 years ago

I agree with RehanSaeed. I had the need to generate absolute urls in a module that sends out emails to clients (which is outside the web project). I only want the IUrlHelper or LinkGenerator as dependency and don't want to do all the IHttpContextAccessor setup as described above.

rynowak commented 5 years ago

I want to generate absolute URL's with the full scheme, host and optionally a port i.e. https://example.com/foo?bar=1. Why? Well, one reason is that in API's a full URL is much easier to consume from JavaScript.

This is something we can work with. Do you actually care about the current request? Or do you just want it to be easier to generate absolute URLs?

I can imagine a feature where you can centrally configure a default scheme and host that will be used in URLs - or it could fallback to the listening address if nothing is configured and there is only one listening address.


There reason I'm making this distinction is that passing in the "current request" does more than just set up scheme/host/pathbase - it also includes ambient route values. Trying to make a service that will magically use ambient values from the current request is a recipe for failure because it's going to do different things depending on which route reached the current thing.

RehanSaeed commented 5 years ago

I don't care about the current request at all, I'm just forced to care to generate a URL based on the current requests scheme, host and URL.

I can imagine a feature where you can centrally configure a default scheme and host that will be used in URLs - or it could fallback to the listening address if nothing is configured and there is only one listening address.

I could get behind such a feature. The fallback sounds like the correct default with the option to override it.

FilipVanBouwel commented 5 years ago

That sounds good to me as well!

rynowak commented 5 years ago

Great, I'm sticking this in the 5.0 milestone so it will get looked at.

mkArtakMSFT commented 4 years ago

We've moved this issue to the Backlog milestone. This means that it is not going to happen for the coming release. We will reassess the backlog following the current release and consider this item at that time. However, keep in mind that there are many other high priority features with which it will be competing for resources.

clawrenceks commented 4 years ago

I have a requirement to use the LinkGenerator to generate links outside of a Web Project, similar to the requirement mentioned by @FilipVanBouwel earlier in this issue.

My requirement is to use the LinkGenerator in a worker process that runs in its own process, with no web host configured.

The only way I have been able to achieve this so far is as follows. Are there any issues with using this method? Is there a simpler approach that I am overlooking?

1) In the ConfigureServices of the application, register the controllers from the web project with MVC

var apiAssembly = Assembly.GetAssembly(typeof(RouteNames));

services.AddMvcCore()
        .AddApplicationPart(apiAssembly)
            .AddControllersAsServices();

2) At the end of ConfigureServices I create an application builder and use this to map the controllers with Endpoint Routing. My application does not build a web host, so the endpoints below should never be accessible. I only perform this step as without it I don't believe that the LinkGenerator will be aware of the MVC Routes.

// Map the controllers as endpoints to allow the LinkGenerator to link to controller routes.

var appBuilder = new ApplicationBuilder(serviceProvider);

appBuilder.UseRouting();

appBuilder.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
});

3) I can then inject a LinkGenerator into my HostedService and generator links as follows:

var path = _linkGenerator.GetPathByName("MyRouteName", new { });
ghost commented 2 years ago

Thanks for contacting us.

We're moving this issue to the .NET 8 Planning milestone for future evaluation / consideration. We would like to keep this around to collect more feedback, which can help us with prioritizing this work. We will re-evaluate this issue, during our next planning meeting(s). If we later determine, that the issue has no community involvement, or it's very rare and low-impact issue, we will close it - so that the team can focus on more important and high impact issues. To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.