Search code examples
c#asp.net-coreurl-routingrazor-pages

After migrating my app to ASP.NET Core 3.0, the previously valid Index URL returns 404


An app started in ASP.NET Core 2.0 (I think), then migrated to 2.1, then to 2.2, now I'm trying and failing to migrate it to 3.0...

I read and tried to apply instructions in the official migration docs, according to which I was supposed to (among others) replace services.AddMvc() with services.AddRazorPages() and app.UseMvc() with app.UseEndpoints(endpoints => {endpoints.MapRazorPages();}) if I was using Razor Pages. Since as far as I'm aware I was always using Razor Pages and never full-blown MVC, this is what I did.

Now previously working URLs return HTTP 404 instead of any content...

For instance, the / or /Index route does this, even though in the project directory there is a Pages/Index.cshtml file as well as a Pages/Index.cshtml.cs file. (Although oddly: maybe it is only the Index url that is failing - I just tried pointing my browser to /Error and it worked!)

Pages/Index.cshtml.cs content (unchanged from the working version):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Mon.Battle;

namespace mon.Pages
{
    public class IndexModel : PageModel
    {
        public IndexModel(IBattleManager battleManager)
        {
            // I hope I don't have to lock this dict here, I'm only reading
            configurationSerialized = battleManager.configurationSerialized;
        }

        public ConfigurationSerializedFormat configurationSerialized;

        public void OnGet()
        {

        }
    }
}

Pages/Index.cshtml also contains some content, but it is too long and too messy to post it here as a whole... But it should definitely return something and it was returning something before migrating to 3.0.

Page directives from the top of Index.cshtml are short enough however:

@* TODO: The site becomes ugly :( Should I start using Bootstrap, instead of trying to handcraft CSS? *@
@* Hey... I actually start to like how the site looks :) *@

@page

@using System.Text.Encodings.Web
@using Microsoft.Extensions.Configuration
@inject JavaScriptEncoder jsencoder
@inject IConfiguration conf
@using static System.Text.Json.JsonSerializer
@model IndexModel
@{
    Layout = null;
}

These, unfortunately, had to change a bit from the pre-migration version: namely since 3.0 removed Newtonsoft.JSON, I had to replace it with System.Text.Json.

My current Startup.cs (I thought I applied instructions from the aforementioned docs precisely):

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.EntityFrameworkCore;
using mon.Data;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Mon.Chat;
using Mon.MatchMaker;
using Mon.Battle;
using Mon.Player;

namespace mon
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            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.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.Configure<IdentityOptions>(options =>
            {
                options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
            });

            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(
                    Configuration.GetConnectionString("DefaultConnection")));
            services.AddDefaultIdentity<ApplicationUser>().AddRoles<IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>();

            services.AddRazorPages();

            services.AddSignalR();

            services.AddSingleton<IBattleManager, BattleManager>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseDatabaseErrorPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseRouting();

            app.UseAuthentication();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapHub<ChatHub>("/chathub");
                endpoints.MapHub<MatchMakerHub>("/mmrhub");
                endpoints.MapHub<BattleHub>("/battlehub");
                endpoints.MapRazorPages();
            });
        }
    }
}

Previous Startup.cs:

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 mon.Data;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Mon.Chat;
using Mon.MatchMaker;
using Mon.Battle;
using Newtonsoft.Json.Serialization;
using Mon.Player;

namespace mon
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            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.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.Configure<IdentityOptions>(options =>
            {
                options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
            });

            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(
                    Configuration.GetConnectionString("DefaultConnection")));
            services.AddDefaultIdentity<ApplicationUser>().AddRoles<IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>();

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

            services
                .AddSignalR()
                .AddJsonProtocol(options =>
                {
                    options.PayloadSerializerSettings.ContractResolver = new DefaultContractResolver();
                });

            services.AddSingleton<IBattleManager, BattleManager>();
        }

        // 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.UseDatabaseErrorPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseCookiePolicy();

            app.UseAuthentication();

            app.UseSignalR(routes =>
            {
                routes.MapHub<ChatHub>("/chathub");
                routes.MapHub<MatchMakerHub>("/mmrhub");
                routes.MapHub<BattleHub>("/battlehub");
            });

            app.UseMvc();
        }
    }
}

What am I doing wrong? What did I miss? Why does /Index return HTTP 404?

If you ask for any more info I'll provide.


Solution

  • Once I had the issue with my .csproj file. Make sure your file(s) are not listed like:

    <ItemGroup>
       <Content Remove="Views\Extractor\Insert.cshtml" />
       <Content Remove="Views\_ViewImports.cshtml" />
    </ItemGroup>
    

    This might happen when we copy paste the file/ change the build action etc.