u/screamcode-dev

How I embedded a Blazor Server admin portal into an ASP.NET Core app via NuGet
▲ 2 r/Blazor+1 crossposts

How I embedded a Blazor Server admin portal into an ASP.NET Core app via NuGet

Hey r/csharp,

I wanted to share an architectural approach I've been working on — embedding a full Blazor Server UI as a NuGet library into an existing ASP.NET Core application.

The idea: instead of building reporting UI from scratch in every project, package the entire Blazor portal as a library that drops into any ASP.NET Core app with minimal configuration.

**The core challenge** was making Blazor Server work as an embedded library rather than a standalone app. The key pieces:

**1. Serving static assets from the library**

Blazor's static files (JS, CSS) need to be served from the host app. This works via embedded resources and a custom middleware that intercepts requests to `/admin.css`, `/admin.js` etc:

```csharp

app.UseStaticFiles();

// Host app serves the library's embedded assets

```

**2. Routing — MapFallbackToPage**

The admin portal lives at `/reportadmin` without conflicting with the host app's routes:

```csharp

app.MapFallbackToPage("/reportadmin/{**path}", "/_Host");

app.MapFallbackToPage("/reportadmin", "/_Host");

```

**3. Data provider pattern**

Instead of hardcoding data access, consumers implement an interface:

```csharp

public interface IReportDataProvider

{

string EntityName { get; }

string DisplayName { get; }

IReadOnlyList<ReportColumnDefinition> AvailableColumns { get; }

IReadOnlyList<ReportFilterDefinition> AvailableFilters { get; }

Task<ReportTable> GetDataAsync(ReportBuilderRequest request, CancellationToken ct = default);

Task<int> GetCountAsync(ReportBuilderRequest request, CancellationToken ct = default);

}

```

**4. PDF generation in Blazor Server context**

Playwright's `CreateAsync()` throws a `NullReferenceException` when called directly from Blazor Server's synchronization context. The fix is wrapping in `Task.Run`:

```csharp

var pdfBytes = await Task.Run(async () =>

{

using var playwright = await Playwright.CreateAsync();

await using var browser = await playwright.Chromium.LaunchAsync(...);

// ...

}, ct);

```

**5. Offline license validation**

JWT tokens signed with RSA private key, validated against an embedded public key — no network calls, works air-gapped:

```csharp

var rsa = RSA.Create();

rsa.ImportFromPem(embeddedPublicKey);

var validationParams = new TokenValidationParameters

{

IssuerSigningKey = new RsaSecurityKey(rsa),

ValidateIssuerSigningKey = true,

// ...

};

```

The result is a library that adds a full reporting portal to any ASP.NET Core app — report builder, PDF/Excel export, scheduler, audit trail — without requiring a separate server or database beyond a local SQLite file.

Happy to go deeper on any of these pieces if anyone's interested.

**Live demo** if you want to see the end result: https://reporting.screamcode.com/reportadmin

u/screamcode-dev — 9 days ago