Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Guidance for Blazor Server with API Gateway and Microservices

So far I have had no luck finding guidance on using Blazor Server (not WebAssembly) with an API Gateway and Microservices. Articles discussing these Blazor together with API gateways and microservices invariably refer to Blazor WebAssembly (Wasm). (Is it assumed that a Blazor Server app won't use microservices? Also, for what it's worth, the reason for choosing Blazor Server over Blazor WebAssembly is to better protect intellectual property.)

Anyway... What I'm wondering is whether the Blazor Server app should sit in front of the gateway, sending its internal API calls through the gateway to microservices behind the gateway, like so…

[Browser] ----(SignalR)--- [Blazor Server App] ----(https)---- [API Gateway] ----(http)---- [Microservices]

Or does it make more sense to put the app behind the gateway, having the SignalR connection tunnel through the gateway, like this…

[Browser] ----(SignalR)---- [API Gateway] ----(SignalR)---- [Blazor Server App] ----(http)---- [Microservices]

Keep in mind the initial load of the application in the browser prior to establishing the SignalR connection. Does that need to be handled separately? Does it impact the choice of options given above? Is there a better solution that I'm missing?

like image 407
MylesRip Avatar asked Oct 16 '22 03:10

MylesRip


1 Answers

We use Blazor server-side as the front-end to a collection of microservices running in Azure Service Fabric (itself a stateless ASP.NET Core service), so it's a perfectly legitimate scenario. Our whole application runs within Azure's cloud, so I'll use its products to describe our implementation.

In order to better separate concerns and more readily secure internet boundaries, we have two separate services for the public API and for SignalR. The API Gateway thus serves as a security boundary since it can handle HTTPS offloading, sit on a public-facing subnet and route to an internal service on the virtual network. The service providing SignalR access then runs on a public-facing subnet behind an Application Gateway instance (for the firewall + service routing functionality).

As such, we have the two services set up as follows:

SignalR Service

[Browser] -- (SignalR) -- [Azure Application Gateway] -- [Azure Service Fabric - Blazor Server-side App] -- [SF Service Remoting] -- [Microservices]

API Service

[Browser] -- (https) -- [Azure API Management] -- (http) -- [Azure Service Fabric - ASP.NET Core API] -- [SF Service Remoting] -- [Microservices]

Microservice Gotcha - Data Protection

Especially when supporting authentication, ASP.NET Core sets up some keys internally for encrypting state used in the Blazor session. In a microservices scenario, unless you've got sticky routing all the way through your networking stack (load balancers, gateways, routers, etc.) to ensure that all requests always hit the same instance (and that presumably your instance isn't rebuilt and moved elsewhere when you're not paying attention), you're going to run into vague and unhelpful errors about failing to unprotect the state.

The fix is quite simple - in the Startup.cs of your ASP.NET Core services, ensure you set up services.AddDataProtection(); and configure appropriately. Since we're using Service Fabric, there's a third-party library we use that works quite well for this purpose. To use, install the NuGet package SoCreate.AspNetCore.DatapProtection.ServiceFabric to your ASP.NET Core service(s) and simply put the following in your Startup.cs within the ConfigureServices() method:

public void ConfigureServices(IServiceCollection services)
{
  //...
  services.AddDataProtection().PersistKeysToServiceFabricDistributedCache(opt => {
    //Unique for this ASP.NET Core microservice, don't use a new GUID each time or you'll be back where you started without a single store for all the services
    opt.CacheStoreId = new Guid("b87d03b5-f8d1-456f-966d-11d2c4d9774d"); 

    //Specifically points to the service to use - if not set, it'll be automatically discovered
    opt.CacheStoreServiceUri = new Uri("fabric:/MyApplication/DataProtectionStore");   });
  //...
}

There are additional options you can specify on the latter method to identify the specific service it should use and a unique ID (so the service can be shared across applications), but if it's in the same application and you don't specify these options, it'll find it itself.

Then create up a stateful service instance, install SoCreate.Extensions.Caching.ServiceFabric from NuGet in the file reflecting the name of the service and replace its inheritance from StatefulService to instead have it inherit from DistributedCacheStoreService. Delete everything else in the service, but set up the updated constructor. In a service named "DataProtectionStore", your DataProtectionStore.cs file should look like the following:

using System.Fabric;
using SoCreate.Extensions.Caching.ServiceFabric

namespace DataProtectionStore
{
  internal sealed class DataProtectionStore : DistributedCacheStoreService
  {
    protected override int MaxCacheSizeInMegabytes => 500; //Optional

    public DataProtectionStore(StatefulServiceContext context) : base(context, message => ServiceEventSource.Current.ServiceMessage(context, message))
    {
    }
  }
}

You can find other helpful guidance specific to hosting ASP.NET Core in a "web farm" or microservices/distributed environment here.

like image 185
Whit Waldo Avatar answered Nov 15 '22 08:11

Whit Waldo