Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to map fallback in ASP .NET Core Web API so that Blazor WASM app only intercepts requests that are not to the API

I have a Blazor WebAssembly solution with a client project, server project and shared project, based on the default solution template from Microsoft. I'm editing and debugging in Visual Studio 2019 preview with Google Chrome.

Out-of-the-box, the solution has a single start-up project, which is the server application. That server application has a project reference to the client application. You can set it to use HTTPS by checking "Enable SSL" in the server project properties and I have done that.

When you click on debug it works perfectly.

Now I want to change it so that the Blazor WASM app only responds to requests from https://localhost:44331 and not requests to https://localhost:44331/api. These requests should be dealt with by API Controller endpoints of the server application instead. So, if somebody visits https://localhost:44331/api/something, and no such API endpoint exists, they should receive a 404 error code from the API and not be routed to the usual Blazor page saying "Sorry, there's nothing at this address."

I want to use this extra "/api" portion of the URL to keep the requests to the API separate from requests for pages. I think this will be closer to how a normal setup would be in production. I hope it's clear what I'm trying to do.

Here is a sample Controller declaration with route attribute:

namespace BlazorApp2.Server.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        // Etc.

        [HttpGet]
        public IEnumerable<WeatherForecast> Get()
        {
            //etc.
        }
///etc.
    }
}

Here is what I have tried in my Startup.cs and it does not work. Can anybody suggest something that will please?

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // Etc.
    app.UseStatusCodePages();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
        endpoints.MapControllers();
        // The line commented out below is the out-of-the-box behaviour for a Blazor WASM app with ASP NET Core API. This is the line I want to replace.
        // endpoints.MapFallbackToFile("index.html");

        // The line below is my (failed) attempt to get the behaviour I want.
        endpoints.MapFallback(HandleFallback);
    });
}

private async Task HandleFallback(HttpContext context)
{
    var apiPathSegment = new PathString("/api"); // Find out from the request URL if this is a request to the API or just a web page on the Blazor WASM app.

    bool isApiRequest = context.Request.Path.StartsWithSegments(apiPathSegment);

    if (!isApiRequest)
    {
        context.Response.Redirect("index.html"); // This is a request for a web page so just do the normal out-of-the-box behaviour.
    }
    else
    {
        context.Response.StatusCode = StatusCodes.Status404NotFound; // This request had nothing to do with the Blazor app. This is just an API call that went wrong.
    }
}

Does anybody know how to get this working how I'd like, please?

like image 550
benjamin Avatar asked Apr 17 '20 09:04

benjamin


People also ask

How do I call a Blazor server from web API?

Blazor WebAssembly apps call web APIs using a preconfigured HttpClient service, which is focused on making requests back to the server of origin. Additional HttpClient service configurations for other web APIs can be created in developer code. Requests are composed using Blazor JSON helpers or with HttpRequestMessage.

How do you pass multiple parameters in Blazor?

The easiest way is to use Route parameters instead of QueryString: @page "/navigatetopage/{id:int}/{key}" @code { [Parameter] public int Id{get;set;} [Parameter] public string Key{get;set;} ... }

Does Blazor work with .NET core?

Blazor Server provides support for hosting Razor components on the server in an ASP.NET Core app. UI updates are handled over a SignalR connection. The runtime stays on the server and handles: Executing the app's C# code.


2 Answers

To recap the problem, when somebody makes a request to:

https://yourapp.com/api/someendpoint

and /api/someendpoint can't be found, they're taken to a Blazor page. This default behaviour is weird. For requests starting with /api, they were expecting an HTTP Status Code and probably a JSON object too, but instead, they got HTML. Maybe they don't even use your app. Maybe they're not even human (more likely they're a piece of software).

This is how you send them an HTTP Status Code instead. On your controllers:

[Route("api/[controller]")]
public class SampleController : ControllerBase
{
    // ...
}

In Startup.cs:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // ...
    app.UseStaticFiles();
    app.UseRouting();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
        endpoints.MapControllers();
        endpoints.Map("api/{**slug}", HandleApiFallback);
        endpoints.MapFallbackToFile("{**slug}", "index.html");
    });
}

private Task HandleApiFallback(HttpContext context)
{
    context.Response.StatusCode = StatusCodes.Status404NotFound;
    return Task.CompletedTask;
}
like image 192
benjamin Avatar answered Oct 03 '22 03:10

benjamin


You can fix this by explicitly mapping Blazor fallback only for paths that don't start with /api and, then only mapping api paths for those that do start with /api, like I mention in this answer to my owner question. This gives the benefit that instead of just returning a 404 if you try to POST to a GET api method, you will get the proper api response of 405, or whatever other error the api would normally return given the request.

//explicitly only use blazor when the path doesn't start with api
app.MapWhen(ctx => !ctx.Request.Path.StartsWithSegments("/api"), blazor =>
{
    blazor.UseBlazorFrameworkFiles();
    blazor.UseStaticFiles();

    blazor.UseRouting();
    blazor.UseEndpoints(endpoints =>
    {
        endpoints.MapFallbackToFile("index.html");
    });
});

//explicitly map api endpoints only when path starts with api
app.MapWhen(ctx => ctx.Request.Path.StartsWithSegments("/api"), api =>
{
    //if you are not using a blazor app, you can move these files out of this closure
    api.UseStaticFiles();
    api.UseRouting();
    api.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
});
like image 28
DLeh Avatar answered Oct 03 '22 03:10

DLeh