Multiproject Blazor WASM (hosted) multi-tenant strategy

135 Views Asked by At

I have a multiproject hosted Blazor WASM where an AspNetCore WebApi hosts two Blazor WASM SPA and I am looking at implementing a good multi-tenant strategy.

Here is my source code in a public repository with instructions

I will also have a single OIDC provider (out of scope) which will receive interactive client redirections, so that the tenantId will be passed as additional parameters and the users will authenticate with their credentials against a specific tenant without the user knowing it.

The goal is: Depending on which URL and subdomain the UI is accessed (e.g: one.foo.com, two.foo.com, etc.) I want to grab this subdomain (e.g: one, two, etc) as they will be the tenant Id.

I can easily do that on any request to the AspNetCore WebApi which serves my Blazor WASM SPA, so that's not a problem.

The problem is, I want to serve the required Blazor WASM with the Tenant Id information already available by the time the Program.cs of the Blazor WASM loads in the browser.

So, in other words, I am looking for a way to let my Blazor WASM's Program.cs which tenant are they being executed for, and I am stuck.

This is my AspNetCore WebApi Program.cs file.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseWebAssemblyDebugging();
}
else
{
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.Use((context, next) =>
{
    var host = context.Request.Host.Host;
    var parts = host.Split('.');

    if (parts.Length > 2)
    {
        var tenantId = parts[0];
        context.Items["TenantId"] = tenantId;
    }

    return next();
});

app.MapWhen(
    ctx => ctx.Request.Path.StartsWithSegments("/ClientAdmin"), 
    appBuilder =>
{
    ConfigureTenantApp(appBuilder, "ClientAdmin");
});

app.MapWhen(
    ctx => ctx.Request.Path.StartsWithSegments("/Client"), 
    appBuilder =>
    {
        ConfigureTenantApp(appBuilder, "Client");
    });

app.UseStaticFiles();

app.UseRouting();

app.MapRazorPages();
app.MapControllers();

void ConfigureTenantApp(IApplicationBuilder appBuilder, string appName, string? tenantId = default)
{
    // TODO, how do I pass tenantId to the Blazor WASM clients so that tenantId is available at DI registration time?
    appBuilder.UseBlazorFrameworkFiles($"/{appName}");
    appBuilder.UseStaticFiles();
    appBuilder.UseStaticFiles($"/{appName}");
    appBuilder.UseRouting();

    appBuilder.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
        endpoints.MapFallbackToFile($"/{appName}" + "/{*path:nonfile}", 
            $"{appName}/index.html");
    });
}

app.Run();

I can easily grab the tenant Id from the request, but when I load one Blazor WASM's index.html or the another (e.g: when I access http://localhost:5010/Client or http://localhost:5010/ClientAdmin), their respective Program.cs get executed in the browser. It's at this point where I would like to have the tenantId available.

How to achieve that? How to pass the tenant Id to the requested SPA?

PS: This is the Blazor WASM's Program.cs at the client side where I would need to read the TenantId

using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Sasw.Client;

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

builder.Services.AddHttpClient("Sasw.ServerAPI",
    client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress));

// Supply HttpClient instances that include access tokens when making requests to the server project
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("Sasw.ServerAPI"));

await builder.Build().RunAsync();
0

There are 0 best solutions below