How to add global route prefix in asp.net core 3?
Asked Answered
B

5

34

Legacy .net core frameworks used UseMvc() for adding global route prefix. How to make it for asp.net core 3 without UseMvc() ?

Brunhilda answered 11/10, 2019 at 12:8 Comment(1)
Asp.net core 3.0 uses app.UseEndpoints(), what global route prefix do you want to implement?Wreckfish
W
41

You could refer to below demo in asp.net core 3.0 to set global route prefix with api version.You could set any prefix as you like by changing services.AddControllersWithViews(o => { o.UseGeneralRoutePrefix("api/v{version:apiVersion}"); });

1.Create a custom MvcOptionsExtensions

public static class MvcOptionsExtensions
{
    public static void UseGeneralRoutePrefix(this MvcOptions opts, IRouteTemplateProvider routeAttribute)
    {
        opts.Conventions.Add(new RoutePrefixConvention(routeAttribute));
    }

    public static void UseGeneralRoutePrefix(this MvcOptions opts, string 
    prefix)
    {
        opts.UseGeneralRoutePrefix(new RouteAttribute(prefix));
    }
}

public class RoutePrefixConvention : IApplicationModelConvention
{
    private readonly AttributeRouteModel _routePrefix;

    public RoutePrefixConvention(IRouteTemplateProvider route)
    {
        _routePrefix = new AttributeRouteModel(route);
    }

    public void Apply(ApplicationModel application)
    {
        foreach (var selector in application.Controllers.SelectMany(c => c.Selectors))
        {
            if (selector.AttributeRouteModel != null)
            {
                selector.AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel(_routePrefix, selector.AttributeRouteModel);
            }
            else
            {
                selector.AttributeRouteModel = _routePrefix;
            }
        }
    }
}

2.Register in Startup.cs( you need to install package Microsoft.AspNetCore.Mvc.Versioning ,current version for 3.0 is 4.0.0-preview8.19405.7)

public void ConfigureServices(IServiceCollection services) {
    //MVC service registration
    //https://learn.microsoft.com/en-us/aspnet/core/migration/22-to-30?view=aspnetcore-3.0&tabs=visual-studio#mvc-service-registration
    services.AddControllersWithViews(o = >{
        o.UseGeneralRoutePrefix("api/v{version:apiVersion}");
    });

    services.AddApiVersioning(o = >o.ReportApiVersions = true);
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env) {

    if (env.IsDevelopment()) {
        app.UseDeveloperExceptionPage();
    }

    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints = >{
        endpoints.MapControllerRoute(
        name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");
        endpoints.MapRazorPages();
    });
}

3.Controller:

[ApiVersion("1")]
[ApiVersion("2")]
[Route("test")]
[ApiController]
public class TestController : ControllerBase
{
    [HttpGet("version"), MapToApiVersion("1")]
    public IActionResult GetV1()
    {
        return new OkObjectResult("Version One");
    }
}

4.Result

Calling /api/v1/test/version results in "Version One".

Wreckfish answered 16/10, 2019 at 5:40 Comment(0)
B
21

I solved this in 3.1 with just the below in my startup Configure():

app.UsePathBase(new PathString("/api"));
Bandicoot answered 30/12, 2020 at 9:33 Comment(4)
This didn't work for me. I found a few variations on this here... forums.servicestack.net/t/net-core-3-1-custom-api-path-metadata/… but those didn't work for me either.Windup
Because it's a middleware the order you add it matters. It works for me in core 3.1 if I add it right before the UseRouting() middleware. If added afterwards it will not work.Stovepipe
This won't work if there's no explicit call to UseRouting() because then it's executed at the start of the pipeline, at least that's the case in .NET 6. Please refer to this issue for the right order of calls for this solution to work github.com/dotnet/aspnetcore/issues/…Bandstand
While this might seemingly be achieving the same behaviour, it is not actually adding a prefix to the endpoints, and you end up with duplicate endpoints. See https://mcmap.net/q/451238/-how-to-prepend-a-common-prefix-to-all-urls-before-the-controller-in-asp-net-apiRemy
F
7

As @alastairtree answered you can use app.UsePathBase to achieve this.

You need to place this call before registering the middleware that needs to be prefixed.

If you need to register other middleware after that, that should not be prefixed, you can reset the prefix to /.

Full example:

app.UsePathBase(new PathString("/api"));
app.UseRouting();
app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller}/{action=Index}/{id?}");
});

app.UsePathBase(new PathString("/"));
app.UseSpa(spa =>
{
    spa.Options.SourcePath = "ClientApp";

    if (env.IsDevelopment())
    {
        spa.UseAngularCliServer(npmScript: "start");
    }
});
Fennec answered 30/6, 2021 at 7:33 Comment(1)
At this moment I'm actually not sure whether this works correctly. In any case a downside of UsePathBase seems to be that both the prefixed and non-prefixed paths result in the endpoint. This may or may not be a problem, depending on your use case. Personally I switched to adding the prefix to every controller: [Route("api/[controller]")]Fennec
B
2

How to add global route prefix in asp.net core 6 Still works:

app.UsePathBase(new PathString("/api/service"));
app.UseRouting();
Bassarisk answered 5/5, 2022 at 19:48 Comment(0)
R
0

The commonly suggested UsePathBase option does not actually add a prefix to your routes, rather, it is ignoring(effectively) the specified path base by modifying requests, so that they still end up hitting the controllers that don't have /api in their route; those routes without /api will still exist and be valid, resulting in duplicate endpoints.

The intended purpose of this middleware is to remove an optional prefix from a request. I find using it to achieve what we want here bastardising it.

Better options are either:

  1. Set a global conventional controller route template:

    app.MapControllerRoute(name: "default", pattern: "api/{controller}/{action}");
    

    Note: for conventional routing to work, your controllers cannot have the ApiController attribute, which enforces attribute routing (second option below).

  • Use a base controller class with your route pattern, and inherit it in all your controllers:

    [ApiController]
    [Route("api/[controller]")]
    public class BaseApiController : ControllerBase
    { }
    
    //This will have the route "api/WeatherForecast"
    public class WeatherForecastController: BaseApiController
    {
         ....
    }
    

    I found this option to be the most flexible, especially if you're using Swagger, as it doesn't support conventional route templates.

Remy answered 18/7 at 5:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.