Skip to content

UsePathBase being ignored #38448

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
reinux opened this issue Nov 16, 2021 · 19 comments · Fixed by #42876
Closed

UsePathBase being ignored #38448

reinux opened this issue Nov 16, 2021 · 19 comments · Fixed by #42876
Assignees
Labels
area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions bug This issue describes a behavior which is not expected - a bug.
Milestone

Comments

@reinux
Copy link

reinux commented Nov 16, 2021

Describe the bug

UsePathBase seems to get ignored. I'm not sure what the condition is. In my other project, it works with the Swagger endpoints, but not with the controllers. In this repro, it works with neither. In both cases, it's the first call I make after I build the app.

To Reproduce

  • Create a new Web API project
  • Call app.UsePathBase() immediately after var app = builder.Build()
  • Run the project, open root (non-UsePathBase) URL in browser

https://github.com/reinux/UsePathBaseTest

Exceptions (if any)

Further technical details

  • ASP.NET Core version: 6.0.0.0
  • The IDE (VS / VS Code/ VS4Mac) you're running on, and its version: VS2022
  • Include the output of dotnet --info:
dotnet --info Output
.NET SDK (reflecting any global.json):
 Version:   6.0.100
 Commit:    9e8b04bbff

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.22000
 OS Platform: Windows
 RID:         win10-x64
 Base Path:   C:\Program Files\dotnet\sdk\6.0.100\

Host (useful for support):
  Version: 6.0.0
  Commit:  4822e3c3aa

.NET SDKs installed:
  3.1.415 [C:\Program Files\dotnet\sdk]
  5.0.103 [C:\Program Files\dotnet\sdk]
  5.0.104 [C:\Program Files\dotnet\sdk]
  5.0.209 [C:\Program Files\dotnet\sdk]
  5.0.303 [C:\Program Files\dotnet\sdk]
  5.0.402 [C:\Program Files\dotnet\sdk]
  5.0.403 [C:\Program Files\dotnet\sdk]
  6.0.100 [C:\Program Files\dotnet\sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.All 2.1.30 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.App 2.1.30 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 3.1.20 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 3.1.21 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 5.0.9 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 5.0.11 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 5.0.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 6.0.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 2.1.30 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 3.1.20 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 3.1.21 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 5.0.0-rc.2.20475.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 5.0.8 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 5.0.9 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 5.0.11 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 5.0.12 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 6.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.WindowsDesktop.App 3.1.19 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 3.1.20 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 3.1.21 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 5.0.4 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 5.0.9 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 5.0.10 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 5.0.11 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 5.0.12 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 6.0.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

To install additional .NET runtimes or SDKs:
  https://aka.ms/dotnet-download

@Tratcher
Copy link
Member

@reinux, is that github repo public? I can't access it.

@reinux
Copy link
Author

reinux commented Nov 16, 2021

@reinux, is that github repo public? I can't access it.

LOL sorry about that.

It's public now.

@Tratcher
Copy link
Member

Repro:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

app.UsePathBase("/SomeBasePath");

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
  app.UseSwagger();
  app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

I think the issue is the implicit call to UseRouting that WebApplication added at the start of the pipeline (@davidfowl @halter73). Routing happens before UsePathBase so that would explain why UsePathBase doesn't apply. Adding UseRouting explicitly should fix it.

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

app.UsePathBase("/SomeBasePath");

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
  app.UseSwagger();
  app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseRouting();

app.UseAuthorization();

app.MapControllers();

app.UseEndpoints(endpoints =>
{
});

app.Run();

If that fixes it then we should add an analyzer to warn about this case.

@halter73
Copy link
Member

I think an analyzer is a good idea here. FWIW, you should be able to just add .UseRouting() without .UseEndpoints() to change where the route matching happens.

@reinux
Copy link
Author

reinux commented Nov 17, 2021

Adding UseRouting explicitly should fix it.

It seems to accept both / and /SomeBasePath now (specifically /swagger/index.html). I'm assuming that's not the intended behavior, is it?


var app = builder.Build();

app.UsePathBase("/SomeBasePath");

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
  app.UseSwagger();
  app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseRouting();

app.UseAuthorization();

app.MapControllers();

app.Run();

@davidfowl
Copy link
Member

Something we can document for now and then we can do what we did for the other middleware that rewrites the path.

cc @BrennanConroy

@adityamandaleeka adityamandaleeka added this to the .NET 7 Planning milestone Nov 17, 2021
@adityamandaleeka adityamandaleeka added the bug This issue describes a behavior which is not expected - a bug. label Nov 17, 2021
@penihel
Copy link

penihel commented Dec 31, 2021

In my case, the controllers just worked in this order

var app = builder.Build();

app.UsePathBase("/api");

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}



app.UseRouting();


app.UseAuthorization();




app.UseEndpoints(endpoints =>
{
});

app.MapControllers();


app.Run();

@davidfowl
Copy link
Member

@penihel you dont need the UseEndpoints call

@sdykae
Copy link

sdykae commented Jan 6, 2022

app.UseRouting(); works, but don't know how to disable the other paths, also swagger does not update to new path.

Nice milestone ;)

waiting for .net 7

@Xyotic
Copy link

Xyotic commented Mar 4, 2022

app.UseRouting(); does not seem to work for the new minimal api

@khayes
Copy link

khayes commented Mar 24, 2022

I hit on this today, I was very confused as to what was happening even when inspecting the HttpContext before and after the UsePathBase.

I have an application that is behind a YARP proxy and my swagger UI paths were working, but the API calls into the controllers were not. Adding the explicit call to UseRouting fixed the issue.

@RoroTitiFR
Copy link

RoroTitiFR commented Apr 3, 2022

I'm seconding the issue, I have it too. Also confirming that the .UseRouting() workaround helps sorting things out.

@reinux
Copy link
Author

reinux commented Apr 4, 2022 via email

@akamud
Copy link

akamud commented Apr 28, 2022

I hit this today and I couldn't find it documented anywhere yet. Is there an issue tracking this for the docs? I tried looking at the Minimal APIs page and any page referencing PathBase

@halter73
Copy link
Member

I hit this today and I couldn't find it documented anywhere yet. Is there an issue tracking this for the docs? I tried looking at the Minimal APIs page and any page referencing PathBase

I submitted a docs PR at dotnet/AspNetCore.Docs#25769 to add notes mentioning the need to call UseRouting() where we mention UsePathBase. Hopefully we can remove these notes in .NET 7 once we fix this issue.

@marcelo-g-simas
Copy link

marcelo-g-simas commented May 25, 2022

I've hit this issue yesterday and the proposed workaround is not working for me.

This was a .net 3.1 mvc that was migrated to use the new web app builder but I retained the StartUp class.

Here's the code from Program where the builder and StartUp are created:

            var builder = WebApplication.CreateBuilder(new WebApplicationOptions
            {
                ApplicationName = typeof(Program).Assembly.FullName,
                Args = args,
                EnvironmentName = environment,
            });

            builder.Configuration.AddConfiguration(config);
            builder.Host.UseSerilog();
            builder.WebHost.UseUrls("http://*:58471");
            builder.WebHost.ConfigureKestrel(serverOptions =>
            {
                serverOptions.Limits.MinRequestBodyDataRate = new MinDataRate(
                    bytesPerSecond: 80, gracePeriod: TimeSpan.FromSeconds(20));
                serverOptions.Limits.MinResponseDataRate = new MinDataRate(
                    bytesPerSecond: 80, gracePeriod: TimeSpan.FromSeconds(20));
            });
            var startup = new Startup(config);
            startup.ConfigureServices(builder.Services);
            var app = builder.Build();
            startup.Configure(app, environment);

This is the method in StartUp that is called by Program to initialize the app after it is created:

        public void Configure(IApplicationBuilder app, string environment)
        {
            Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;
            // The request path has to be set if the application is running in a
            // container and being exposed on a path other than /.  In k8s we set
            // SiteConfig__PathBase to /sms in most projects.
            if (!string.IsNullOrEmpty(Cfg.SiteConfig.PathBase))
            {
                Log.Information("Setting PathBase to {PathBase}.", Cfg.SiteConfig.PathBase);
                app.UsePathBase(Cfg.SiteConfig.PathBase);
                app.UseStaticFiles(new StaticFileOptions
                {
                    RequestPath = Cfg.SiteConfig.PathBase
                });
                app.Use((context, next) =>
                {
                    if (context.Request.Path.StartsWithSegments(Cfg.SiteConfig.PathBase, out var remainder))
                    {
                        context.Request.Path = remainder;
                        context.Request.PathBase = new PathString(Cfg.SiteConfig.PathBase);
                    }
                    return next();
                });
            }
            else
            {
                app.UseStaticFiles();
            }

            if (environment == "Development")
            {
                app.UseDeveloperExceptionPage();
                // If you want saving views to not require a recompile,
                // see https://kmatyaszek.github.io/2020/02/29/how-to-enable-browser-link-in-aspnetcore3-app.html
                app.UseBrowserLink();
            }
            else
            {
                app.UseExceptionHandler("/Error");
            }
            
            
            app.UseHttpsRedirection();
            app.UseRouting();

            app.UseCookiePolicy();
            app.UseSerilogRequestLogging();
            // Must call UseAuthentication between UserRouting and UseEndpoints or will throw an error page
            app.UseAuthentication();
            app.UseAuthorization();
            if (Cfg.SiteConfig.EnableHangfire)
            {
                Log.Information("Setting up Hangfire dashboard.");
                app.UseHangfireDashboard("/hangfire", new DashboardOptions
                {
                    Authorization = new[] { new Extensions.HangfireAuthorizationFilter() },
                    AppPath = environment == "Development" ? "/" : "/sms"
                });
                app.UseHangfireDashboard();

                if (!string.IsNullOrEmpty(Cfg.SiteConfig.RefreshTargetsCron))
                    RecurringJob.AddOrUpdate<HangfireHelper>("refresh_targets",
                        h => h.RefreshTargets(), Cfg.SiteConfig.RefreshTargetsCron);
                else
                    RecurringJob.RemoveIfExists("refresh_targets");
            }

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapReverseProxy();
                endpoints.MapHub<InboundCallHub>("/hubs/callHub");
                endpoints.MapHub<InboundCallHub>("/hubs/inboundCallHub");
                endpoints.MapHub<MonitoringHub>("/hubs/monitoringHub");
                endpoints.MapHub<OutboundCallHub>($"/hubs/outboundCallHub");
                endpoints.MapControllerRoute("default", "{controller=Account}/{action=Index}/{id?}");
                endpoints.MapControllerRoute("home", "{controller=Home}/{action=Index}/{id?}");
            });

            app.UseMvc(routes => {
                routes.MapRoute(
                    name: "Default",
                    template: "{controller}/{action}/{id?}",
                    defaults: new { controller = "Home", action = "Index" }
                );
                routes.MapRoute(
                    name: "Error404",
                    template: "{*url}",
                    defaults: new { controller = "Error", action = "Handle404" }
                );
            });
        }
    }

@marcelo-g-simas
Copy link

marcelo-g-simas commented May 25, 2022

Here's the log entry from the container showing that it is receiving the configuration value for PathBase:

May 24 22:27:57.871 | k8webdemo-xxx-xxx-sms-649858d9c-gcr9j | [01:27:57 INF] Setting PathBase to /sms.

When I try to navigate to /sms the app redirects to a path off the root and loses the /sms prefix:

/Account/Login?ReturnUrl=%2F

Where it should have redirected to

/sms/Account/Login?ReturnUrl=%2F

What I am trying to deal with is an app that is hosted in a container which is exposed via an ingress in a k8s cluster under a path other than root (/sms in this case). This code around UsePathBase() used to work under .NET 3.1 when we validated our plans for going to k8s. We are now trying to actually migrate under .NET 6 and the new web builder and can't get this to work.

if I just run this locally with the PathBase configuration it all seems to work ok. But in the k8s cluster it falls back into redirecting to the root of the website.

Should we go back to using the older style builder from .NET 5 or .NET 3.1? I've already burned up a whole day with this and would like to know what would be the better course of action here.

@marcelo-g-simas
Copy link

I think I figured out the issue. it was a rewrite rule in the ingress which was removing the subfolders. Once I took it all things behaved as expected, more testing is needed but this is encouraging.
Screen Shot 2022-05-25 at 11 22 03 AM

@marcelo-g-simas
Copy link

marcelo-g-simas commented May 25, 2022

I also had to simplify how I was handling static files and this is how it looks like now:

            if (!string.IsNullOrEmpty(Cfg.SiteConfig.PathBase))
            {
                Log.Information("Setting PathBase to {PathBase}.", Cfg.SiteConfig.PathBase);
                app.UsePathBase(Cfg.SiteConfig.PathBase);
            }
            app.UseStaticFiles();
            app.UseHttpsRedirection();
            app.UseRouting();

Applied this fix to three containers and all is working and happy again.

@ghost ghost locked as resolved and limited conversation to collaborators Sep 11, 2022
@amcasey amcasey added area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions and removed area-runtime labels Aug 24, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions bug This issue describes a behavior which is not expected - a bug.
Projects
None yet
Development

Successfully merging a pull request may close this issue.