Skip to content

Add note to call UseRouting after UsePathBase #25769

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

Merged
merged 2 commits into from
May 3, 2022

Conversation

halter73
Copy link
Member

We plan to fix UsePathBase() in .NET 7 so it's unnecessary to call UseRouting() afterwards like we did for UseExceptionHandler(), UseStatusCodePagesWithReExecute() and UseRewriter() in .NET 6. dotnet/aspnetcore#35426

For now, we want to document that explicitly calling UseRouting() is necessary for UsePathBase() to work with WebApplication.

Partially addresses: dotnet/aspnetcore#38448

Copy link
Collaborator

@guardrex guardrex left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a couple of nits and bits.

@Rick-Anderson Rick-Anderson merged commit 4472eec into main May 3, 2022
@Rick-Anderson Rick-Anderson deleted the halter73/route-pathbase branch May 3, 2022 20:17
@marcelo-g-simas
Copy link

I've hit this issue today 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");

                if (!string.IsNullOrEmpty(Cfg.SiteConfig.SendRemindersCron))
                    RecurringJob.AddOrUpdate<HangfireHelper>("reminders",
                        r => r.SendReminders(), Cfg.SiteConfig.SendRemindersCron);
                else
                    RecurringJob.RemoveIfExists("reminders");

                if (!string.IsNullOrEmpty(Cfg.SiteConfig.MakeIvrCron))
                    RecurringJob.AddOrUpdate<HangfireHelper>("ivr_reminders",
                    h => h.RunIvr(), Cfg.SiteConfig.MakeIvrCron);
                else
                    RecurringJob.RemoveIfExists("ivr_reminders");

                if (!string.IsNullOrEmpty(Cfg.SiteConfig.TwilioTasksCron))
                    RecurringJob.AddOrUpdate<HangfireHelper>("twilio_tasks",
                    h => h.RunTwilioTasks(), Cfg.SiteConfig.TwilioTasksCron);
                else
                    RecurringJob.RemoveIfExists("twilio_tasks");

                if (!string.IsNullOrEmpty(Cfg.SiteConfig.TextInviteCron))
                    RecurringJob.AddOrUpdate<HangfireHelper>("text_invitation",
                    h => h.SendTextInvitations(), Cfg.SiteConfig.TextInviteCron);
                else
                    RecurringJob.RemoveIfExists("text_invitation");

                if (!string.IsNullOrEmpty(Cfg.SiteConfig.RefreshReportsCron))
                    RecurringJob.AddOrUpdate<HangfireHelper>("refresh_reports",
                    h => h.RefreshReports(), Cfg.SiteConfig.RefreshReportsCron);
                else
                    RecurringJob.RemoveIfExists("refresh_reports");
            }

            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

@halter73
Copy link
Member Author

halter73 commented May 25, 2022

Can you do this?

var builder = WebApplication.CreateBuilder(new WebApplicationOptions
{
    ApplicationName = typeof(Program).Assembly.FullName,
    Args = args,
    EnvironmentName = environment,
    ContentRootPath = !string.IsNullOrEmpty(config.SiteConfig.PathBase) ? config.SiteConfig.PathBase : null,
});

@marcelo-g-simas
Copy link

marcelo-g-simas commented May 25, 2022

I don't see how this would help as the docs indicate that ContentRootPath is the physical local path to the application files (i.e., config json. dlls, etc.). The property config.SiteConfig.PathBase is populated with /sms, which is the path under the website that this application is supposed to show up as.

@marcelo-g-simas
Copy link

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 builder and realizing that the new style builder has this regression in it.

@marcelo-g-simas
Copy link

Anyways, I tested locally that and the page did not redirect back to the root. But all the static resources failed to load:
Screen Shot 2022-05-25 at 9 30 25 AM

@marcelo-g-simas
Copy link

So it looks like this solves part of the issue, but the little dance I was around UseStaticFiles may not be working. Going to deploy this to the cluster to see what happens.

@marcelo-g-simas
Copy link

Actually, that was a false hit. I called dotnet run but forgot to hit save on the Program.cs file (using VS Code). Once I actually had the code change in place and tried to run I got this (as I'd originally expected):

Unhandled exception. System.IO.DirectoryNotFoundException: /sms/
   at Microsoft.Extensions.FileProviders.PhysicalFileProvider..ctor(String root, ExclusionFilters filters)
   at Microsoft.Extensions.FileProviders.PhysicalFileProvider..ctor(String root)
   at Microsoft.AspNetCore.Hosting.BootstrapHostBuilder.RunDefaultCallbacks(ConfigurationManager configuration, HostBuilder innerBuilder)
   at Microsoft.AspNetCore.Builder.WebApplicationBuilder..ctor(WebApplicationOptions options, Action`1 configureDefaults)
   at Microsoft.AspNetCore.Builder.WebApplication.CreateBuilder(WebApplicationOptions options)
   at SurveyManagementSystem.Program.CreateWebApplication(String[] args) in /Users/marcelosimas/Git/xxxx_hts/sms/SurveyManagementSystem/Program.cs:line 87
   at SurveyManagementSystem.Program.Main(String[] args) in /Users/marcelosimas/Git/xxxx_hts/sms/SurveyManagementSystem/Program.cs:line 21

@marcelo-g-simas
Copy link

I am going to post this on dotnet/aspnetcore#38448.

@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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants