Fallback to SPA in ASP.NET Core Web API

45 views Asked by At

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:

  1. When I make a request to /api/books, it should be routed to the API (server-side routing).
  2. When I make a request to /books, it should be routed to the SPA (client-side routing).
  3. 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

  1. None of my controllers have the /api prefix. 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 /api prefix to all the API controllers. This can be achieved using a UsePathBase and UseRouting calls (Relevant link). However, this poses another problem. AFAIK, UsePathBase extracts the prefix api from the Path and postpends it to the PathBase. This will lead to routing conflicts as after the UseRouting call both /books and /api/books will be routed to BooksController.
  2. 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

  1. To try and resolve point 2 in Problems, I tried moving app.MapFallbackToFile("/index.html") to immediately after UseCors call. Didn't work.
  2. I could manually add the api prefix to all controllers or add a common BaseController which has the route prefix using a custom route attribute. Not tried yet.

References and useful links

  1. Andrew Lock's Blog on PathBase
  2. Kristian Hellang's SpaFallback middleware
0

There are 0 answers