13

How can you mount your app on a different base path?

For example, my controller's route is /api/keywords, but when running the web server I want the basepath to be /development, so my controller route would be /development/api/keywords. I would rather not have to modify my controllers. In old Web API versions you could mount an OWIN app in a different path so I'm looking to do something similar.

Tseng
  • 61,549
  • 15
  • 193
  • 205
ryudice
  • 36,476
  • 32
  • 115
  • 163

3 Answers3

7

There's a new method called UsePathBase that can do this easily. https://github.com/aspnet/HttpAbstractions/blob/bfa183747f6fb528087554c3d6ec58ef05f1c10a/src/Microsoft.AspNetCore.Http.Abstractions/Extensions/UsePathBaseExtensions.cs

Tratcher
  • 5,929
  • 34
  • 44
  • 1
    Link is broken. – Hammerite Aug 16 '18 at 14:54
  • 1
    Fixed the link. – Tratcher Aug 16 '18 at 18:08
  • 2
    As the linked repo has been archived, here's the new link https://github.com/aspnet/AspNetCore/blob/master/src/Http/Http.Abstractions/src/Extensions/UsePathBaseExtensions.cs. Just use `app.UsePathBase(new PathString("/path"))` to the `Startup.Configure` method. – Livven Jul 19 '19 at 12:20
-1

You can view the original great article here

First create a class that inherits from IApplicationModelConvention interface

public class EnvironmentRouteConvention : IApplicationModelConvention
{
    private readonly AttributeRouteModel _centralPrefix;

    public EnvironmentRouteConvention(IRouteTemplateProvider routeTemplateProvider)
    {
        _centralPrefix = new AttributeRouteModel(routeTemplateProvider);
    }

    public void Apply(ApplicationModel application)
    {
         foreach (var controller in application.Controllers)
        {
            var matchedSelectors = controller.Selectors.Where(x => x.AttributeRouteModel != null).ToList();
            if (matchedSelectors.Any())
            {
                foreach (var selectorModel in matchedSelectors)
                {
                    //This will apply only to your API controllers. You may change that depending of your needs
                    if (selectorModel.AttributeRouteModel.Template.StartsWith("api"))
                    {
                        selectorModel.AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel(_centralPrefix, selectorModel.AttributeRouteModel);
                    }
                }
            }
        }
    }

Then create a class just for the purpose of easier and cleaner use.

public static class MvcOptionsExtensions
{
    public static void UseEnvironmentPrefix(this MvcOptions opts, IRouteTemplateProvider routeAttribute)
    {
        opts.Conventions.Insert(0, new EnvironmentRouteConvention(routeAttribute));
    }
}

Now to use it, first very common, save your environment in a property of your Startup class

private IHostingEnvironment _env;

public Startup(IHostingEnvironment env)
{
    _env = env;
}

And then all you need to do is to call your static extention class

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
    {
        options.UseEnvironmentPrefix(new RouteAttribute(_env.EnvironmentName));
    });
}

But there is one last thing to care about. Whatever client you have that consume your API, you certainly don't want to change all URLs of the HTTP requests you send. So the trick is to create a middleware which will modify the Path of your request to include your environment name. (source)

public class EnvironmentUrlRewritingMiddleware
{
    private readonly RequestDelegate _next;

    public EnvironmentUrlRewritingMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context, IHostingEnvironment env)
    {
        var path = context.Request.Path.ToUriComponent();
        //Again this depends of your need, whether to activate this to your API controllers only or not
        if (!path.StartsWith("/" + env.EnvironmentName) && path.StartsWith("/api"))
        {
            var newPath = context.Request.Path.ToString().Insert(0, "/" + env.EnvironmentName);
            context.Request.Path = newPath;
        }
        await _next.Invoke(context);
    }
}

and your ConfigureServices method in your Startup class becomes

public void ConfigureServices(IServiceCollection services)
{
    app.UseMiddleware<EnvironmentUrlRewritingMiddleware>();
    services.AddMvc(options =>
    {
        options.UseEnvironmentPrefix(new RouteAttribute(_env.EnvironmentName));
    });
}

The only drawback is that it doesn't change your URL, so if you hit your API with your browser, you won't see the URL with your environment included. response.Redirect always sends a GET request even if the original request is a POST. I didn't find yet the ultimate solution to this to reflect the Path to the URL.

Community
  • 1
  • 1
Jérôme MEVEL
  • 7,031
  • 6
  • 46
  • 78
-2

Take a look at this:

public class Program
{
   public static void Main(string[] args)
   {
        var contentRoot = Directory.GetCurrentDirectory();

        var config = new ConfigurationBuilder()
           .SetBasePath(contentRoot)
           .Build();

        var hostBuilder = new WebHostBuilder()

          //Server
           .UseKestrel()

           //Content root - in this example it will be our current directory
           .UseContentRoot(contentRoot)

           //Web root - by the default it's wwwroot but here is the place where you can change it
           .UseWebRoot("wwwroot")

           //Startup
           .UseStartup<Startup>();


        var host = hostBuilder.Build();

        host.Run();
    } 
}

There are two extension methods - UseWebRoot() and UseContentRoot() - which can be used to configure web and content roots.

Dawid Rutkowski
  • 2,658
  • 1
  • 29
  • 36