Brief
I have a web api written in .NET 7 and a React SPA. I am trying to host them in IIS under the same host name (as a single website). I want to achieve the following things:
- When I make a request to
/api/books, it should be routed to the API (server-side routing). - When I make a request to
/books, it should be routed to the SPA (client-side routing). - If the path doesn't match any of the routes or the API returns 404, it should fallback to the SPA.
Program.cs
var app = builder.Build();
app.UseProblemDetails();
app.UseSerilogRequestLogging();
app.UseHsts();
app.UseHttpsRedirection();
app.UseDefaultFiles();
app.UseStaticFiles();
app.UsePathBase(new PathString("/api"));
app.UseRouting();
app.UseCors("CorsPolicy");
var isSwaggerEnabled = Convert.ToBoolean(builder.Configuration["ApiDocumentationEnabled"]);
if (isSwaggerEnabled)
{
app.UseSwagger(c =>
{
c.RouteTemplate = "swagger/{documentName}/swagger.json";
c.PreSerializeFilters.Add((document, _) =>
{
document.Servers = new List<OpenApiServer> { new() { Url = "/api" } };
});
});
app.UseSwaggerUI(options => { options.DefaultModelsExpandDepth(-1); });
app.UseReDoc(options => { options.RoutePrefix = "redoc"; });
}
app.MapHealthChecks("/health",
new HealthCheckOptions
{
Predicate = r => r.Tags.Contains("core"),
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
app.MapHealthChecksUI();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapFallbackToFile("/index.html");
await app.RunAsync();
Problems
- None of my controllers have the
/apiprefix. If I take the BooksController as an example, it would have its route attribute as[Route("books")]. So first problem is that I want to add a global/apiprefix to all the API controllers. This can be achieved using aUsePathBaseandUseRoutingcalls (Relevant link). However, this poses another problem. AFAIK,UsePathBaseextracts the prefixapifrom the Path and postpends it to the PathBase. This will lead to routing conflicts as after theUseRoutingcall both/booksand/api/bookswill be routed to BooksController. - Moreover, even if there are no conflicts in API and SPA routes, it still doesn't fallback to SPA routing (serve the index.html file).
Explored directions and possible workarounds
- To try and resolve point 2 in Problems, I tried moving
app.MapFallbackToFile("/index.html")to immediately afterUseCorscall. Didn't work. - I could manually add the
apiprefix to all controllers or add a common BaseController which has the route prefix using a custom route attribute. Not tried yet.