asp.net core with react in webroot 404s

Monte 21 Reputation points
2021-10-08T04:33:58.797+00:00

I have an aspnet core odata running in the root folder, and reactjs spa running in the webroot folder. It works swimmingly if I always go to index.html first...then take navigation over once its loaded....but if I try and go directly to any url other than index I get a 404. I am new to asp.net core and i'm thinking that some way to make this work.

ASP.NET Core
ASP.NET Core
A set of technologies in the .NET Framework for building web applications and XML web services.
4,386 questions
{count} votes

Accepted answer
  1. Bruce (SqlWork.com) 61,266 Reputation points
    2021-10-10T17:36:23.313+00:00

    The static file middleware has this feature builtin, MapFallBackToFile()

    https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.staticfilesendpointroutebuilderextensions.mapfallbacktofile?view=aspnetcore-5.0

    Example

    app.MapFallbackToFile(“index.html”);

    Be sure it’s the last route mapping

    0 comments No comments

2 additional answers

Sort by: Most helpful
  1. Monte 21 Reputation points
    2021-10-08T13:09:05.887+00:00

    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.AddDbContext<ApplicationDbContext>(options =>
               options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
                   b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName)));
    
            services.AddScoped<IApplicationDbContext>(provider => provider.GetService<ApplicationDbContext>());
    
            services.AddScoped((serviceProvider) =>
            {
                var config = serviceProvider.GetRequiredService<IConfiguration>();
                return new SmtpClient()
                {
    
                    Host = config.GetValue<String>("Email:Smtp:Host"),
                    Port = config.GetValue<int>("Email:Smtp:Port"),
                    Credentials = new NetworkCredential(
                            config.GetValue<String>("Email:Smtp:Username"),
                            config.GetValue<String>("Email:Smtp:Password")
                        )
                };
            });
    
            #region Swagger
    
            services.AddSwaggerGen(c =>
            {
                //c.IncludeXmlComments(string.Format(@"{0}\EFCore.CodeFirst.WebApi.xml", System.AppDomain.CurrentDomain.BaseDirectory));
                c.SwaggerDoc("v1", new OpenApiInfo
                {
                    Version = "v1",
                    Title = "xxxxx API",
                });
                var filePath = Path.Combine(AppContext.BaseDirectory, "xxx.xml");
                c.IncludeXmlComments(filePath);
            });
    
            #endregion
    
            services.AddCors();
            services.AddControllers(mvcOptions => mvcOptions.EnableEndpointRouting = false);
    
            services.AddOData();
    
            services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
    
            // configure DI for application services
            services.AddTransient<IUserService, UserService>();
            services.AddOptions();
            SetOutputFormatters(services);
    
        }
    
        // 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();
            }
    
            DefaultFilesOptions options = new DefaultFilesOptions();
            options.DefaultFileNames.Clear();
            options.DefaultFileNames.Add("index.html");
    
    
            app.UseDefaultFiles(options);
            app.UseStaticFiles();
    
            app.UseHttpsRedirection();
    
            app.UseRouting();
    
            app.UseAuthorization();
    
            // global cors policy
            app.UseCors(x => x
                .AllowAnyOrigin()
                .AllowAnyMethod()
                .AllowAnyHeader());
    
            // custom jwt auth middleware
            app.UseMiddleware<JwtMiddleware>();
    
            // localization
            //app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
            app.UseMvc(routeBuilder =>
            {
                routeBuilder.EnableDependencyInjection();
                routeBuilder.Select().Filter().OrderBy();
                routeBuilder.MapODataServiceRoute("api", "api", GetEdmModel());
    
            });
    
    
            app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
            #region Swagger
            // Enable middleware to serve generated Swagger as a JSON endpoint.
            app.UseSwagger();
            // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
            // specifying the Swagger JSON endpoint.
            app.UseSwaggerUI(c =>
            {
                c.SwaggerEndpoint("/swagger/v1/swagger.json", "EFCore.CodeFirst.WebApi");
            });
            #endregion
    
    
    
    
        }
        IEdmModel GetEdmModel()
        {
            var odataBuilder = new ODataConventionModelBuilder();
    
            odataBuilder.EntitySet<User>("Users");
            odataBuilder.EntitySet<UserProduct>("UserProducts");
            odataBuilder.EntitySet<Product>("Products");
    
    
    
            return odataBuilder.GetEdmModel();
        }
    
        private static void SetOutputFormatters(IServiceCollection services)
        {
            services.AddMvcCore(options =>
            {
    
                foreach (var outputFormatter in options.OutputFormatters.OfType<ODataOutputFormatter>().Where(_=> _.SupportedMediaTypes.Count == 0))
                {
                    outputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/odata"));
                }
    
                foreach (var inputFormatter in options.InputFormatters.OfType<ODataInputFormatter>().Where(_ => _.SupportedMediaTypes.Count == 0))
                {
                    inputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/odata"));
                }
            });
        }
    }
    
    0 comments No comments

  2. Bruce (SqlWork.com) 61,266 Reputation points
    2021-10-09T15:47:01.17+00:00

    The react navigation is supported by index.html file. So if you want to navigate to a route in the spa app, the server needs to return the index.html content in response to the request. Then the react code will inspect the url and execute the route.

    Assume your spa has the route info and you want

    https://my site.com/info

    To work. In your .net core app you need to map info to the index.html file content (don’t use a redirect).

    Note: you can use UseSpa middleware to do this for you.