14

After switching from .net core 2.2 to 3.0 and then 3.1 locally we switched to endpoint routing. I have the following routes :

app.UseEndpoints(endpoints =>
        {
            // Using this for asp.net core identity
            endpoints.MapRazorPages();
            // Mapping 2 routes, one private, one public but that we don't want localized so in both cases was simpler to create an area
            endpoints.MapAreaControllerRoute("Back", "Back", "back/{controller=Home}/{action=Index}/{id?}");
            endpoints.MapAreaControllerRoute("Photo", "Photo", "photo/{controller=Photo}/{action=Index}/{id?}");
            // The default mapping for our front office, this works just fine in iis express
            endpoints.MapControllerRoute("default", "{lang:lang}/{controller=Home}/{action=Index}/{id?}");
            // Fallback is used mainly to redirect you from / to /defaultlanguage
            endpoints.MapFallbackToController("WrongEndpoint","Home");
        });

I've used the following blog's code to check if i had the same endpoints in both environments : https://dev.to/buhakmeh/endpoint-debugging-in-asp-net-core-3-applications-2b45

In both cases i have the expected behavior (i tested it by putting that controller in an area which works) : the route i expect to take "default" is in priority 3 while the fallback is 2147483647 so it's clearly being skipped over.

Typing the full name to ignore optional components like mydomain/fr/Home/Index still redirects me to the fallback controller under IIS.

I have no idea why it would behave differently under IIS than IIS Express. Also note that both razor pages and the area controllers works just fine, it's only the default route that fails and falls back to well, the fallback.

Removing the fallback doesn't make the default controller work either.

EDIT 1: as requested in comments the full startup.cs bellow

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using FranceMontgolfieres.Models;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Routing;

namespace FranceMontgolfieres
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
            Utilities.ConfigurationUtils.Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton<IConfiguration>(Configuration);

            services
                .Configure<CookiePolicyOptions>(options =>
                {
                    // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                    options.CheckConsentNeeded = context => true;
                    options.MinimumSameSitePolicy = SameSiteMode.None;
                });

            services
                .AddDbContext<FMContext>(options => options
                    .UseLazyLoadingProxies(true)
                    .EnableSensitiveDataLogging()
                    .UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

            services
                .AddDefaultIdentity<IdentityUser>()
                .AddRoles<IdentityRole>()
                .AddEntityFrameworkStores<FMContext>();

            services
                .AddMemoryCache();

            services.AddDistributedSqlServerCache(options =>
            {
                options.ConnectionString = Configuration.GetConnectionString("SessionConnection");
                options.SchemaName = "dbo";
                options.TableName = "SessionCache";
            });

            services.AddHttpContextAccessor();

            services
                .AddSession(options => options.IdleTimeout = TimeSpan.FromMinutes(30));

            services
                .AddControllersWithViews()
                .AddRazorRuntimeCompilation();
            services.AddRazorPages();
            services.Configure<RouteOptions>(options =>
            {
                options.ConstraintMap.Add("lang", typeof(LanguageRouteConstraint));
            });

        }


        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Called by asp.net core by convention")]
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseDatabaseErrorPage();
            }
            else
            {
                // TODO CRITICAL Remove before production
                app.UseDeveloperExceptionPage();
                app.UseDatabaseErrorPage();

                app.UseExceptionHandler("/Home/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                // TODO CRITICAL ENABLE BEFORE PRODUCTION
                // app.UseHsts();
            }

            //app.UseHttpsRedirection();
            app.UseRouting();
            app.UseStaticFiles();
            app.UseCookiePolicy();
            app.UseAuthentication();
            app.UseAuthorization();
            app.UseSession(); 

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapRazorPages();
                endpoints.MapAreaControllerRoute("Back", "Back", "back/{controller=Home}/{action=Index}/{id?}");
                endpoints.MapAreaControllerRoute("Photo", "Photo", "photo/{controller=Photo}/{action=Index}/{id?}");
                endpoints.MapControllerRoute("default", "{lang:lang}/{controller=Home}/{action=Index}/{id?}");
                endpoints.MapFallbackToController("WrongEndpoint","Home");
            });
        }
    }
}

EDIT 2 : Added web config on deployed server following comment by Lex Li

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <location path="." inheritInChildApplications="false">
    <system.webServer>
      <handlers>
        <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
      </handlers>
      <aspNetCore processPath="dotnet" arguments=".\PROJECTNAMEEDITEDOUT.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="inprocess" />
    </system.webServer>
  </location>
</configuration>
<!--ProjectGuid: THERE IS A GUID HERE I REMOVED-->
Manoj Choudhari
  • 5,277
  • 2
  • 26
  • 37
Ronan Thibaudau
  • 3,413
  • 3
  • 29
  • 78
  • can you post your full startup.cs? – salli Feb 01 '20 at 14:59
  • @salli Added it at the end of the post – Ronan Thibaudau Feb 01 '20 at 15:50
  • When you said "it would behave differently under IIS than IIS Express", did you deploy the same binaries to IIS and IIS Express? If you deployed the bits generated by `dotnet publish` to IIS, while launching IIS Express directly from within VS, then those two scenarios are completely different (such as different web.config) due to the VS magic. – Lex Li Feb 01 '20 at 15:54
  • @LexLi I'm talking about launching it through visual studio vs publishing it via azure pipelines. However i'm not sure how any change in the web config could change the endpoint behavior this way given that the website still partially works (everything except the default route) and that endpoints are only defined in code. I also looked at the web config and don't see anything related to endpoint (editing my answer to add the deployed web config but i don't think it's relevant) – Ronan Thibaudau Feb 01 '20 at 16:42
  • Are you sure you have .net core 3.1 hosting bundles installed on IIS? If you have .net 3.0 hosting bundle the site may work but things like authentication and routing might behave differently. – salli Feb 01 '20 at 17:25
  • @salli yes i’m positive i have .net core 3.1 hosting bundle installed. – Ronan Thibaudau Feb 01 '20 at 18:14
  • Then test both IIS/IIS Express with the same bits from `dotnet publish` and the results should tell more. There are also tons of ways to generate more log entries for analysis, like https://learn.microsoft.com/en-us/aspnet/core/test/troubleshoot?view=aspnetcore-2.2#obtain-data-from-an-app – Lex Li Feb 01 '20 at 19:18
  • @LexLi I’ve never had to run iis express manually, is there a doc for that? – Ronan Thibaudau Feb 01 '20 at 21:02
  • Well, for troubleshooting sometime you have to. https://learn.microsoft.com/en-us/iis/extensions/using-iis-express/running-iis-express-from-the-command-line has the steps. You might also try tools such as Jexus Manager (written by me). – Lex Li Feb 01 '20 at 21:04
  • @LexLi I just took the folder as it is deployed on the server under IIS, copied it to my local machine, started IIS express manually giving it only the path to that folder and a port, and it works just fine that way. So it doesn't seem like a good track (and it may be my lack of knowledge on kestrel/.net core but i really don't understand why the server or any configuration file could interfere with the statically programatically declared endpoints, especially if they show as registered the same way both in the working and non working scenario)? – Ronan Thibaudau Feb 02 '20 at 01:13

0 Answers0