Inject ICryptoService in Blazor WASM Non-Component Class
Learn to register Blazor.SubtleCrypto's ICryptoService in Program.cs and inject it via constructor into non-component classes in Blazor WebAssembly. Step-by-step guide for AES-GCM encryption/decryption with DI best practices.
How to inject and use Blazor.SubtleCrypto (ICryptoService) in a non-component class in Blazor WebAssembly?
In a Blazor WASM project, injecting ICryptoService in components using @inject ICryptoService Crypto works fine. However, this approach does not work in non-component classes.
How can I register Blazor.SubtleCrypto and use it in a non-component class?
In Blazor WebAssembly, you register Blazor.SubtleCrypto’s ICryptoService in Program.cs with builder.Services.AddSubtleCrypto(), then inject it directly into non-component classes like services using constructor dependency injection—no @inject needed there. This taps into .NET’s built-in DI container, letting you call EncryptAsync or DecryptAsync for secure AES-GCM ops from anywhere in your app. Why does this matter? Components get the easy @inject shortcut, but plain classes demand the constructor pattern to stay decoupled and testable.
Contents
- What is Blazor.SubtleCrypto and ICryptoService?
- Installing Blazor.SubtleCrypto in Your Blazor WebAssembly Project
- Registering ICryptoService in Program.cs
- Dependency Injection: Components vs. Non-Component Classes
- Step-by-Step: Injecting ICryptoService in a Non-Component
- Full Example: Encrypt and Decrypt in a Custom Service
- Common Pitfalls and Fixes
- Best Practices for Blazor WebAssembly Crypto
- Sources
- Conclusion
What is Blazor.SubtleCrypto and ICryptoService?
Blazor.SubtleCrypto wraps the browser’s Web Crypto API right into your Blazor WebAssembly apps, handling heavy lifting like AES-GCM encryption without raw JSInterop headaches. At its core sits ICryptoService—an interface exposing async methods for generateKey, encrypt, decrypt, and more. Think of it as your go-to for client-side crypto: secure key management, no server roundtrips.
But here’s the catch—it’s WASM-only. Server-side Blazor? Forget it; the underlying JSInterop demands a browser context. The library shines in standalone WASM hosted apps, pumping out CryptoResult objects packed with ciphertext, IVs, and secret keys. NuGet’s latest at version 9.0.0 keeps it fresh for .NET 8+.
Why bother? Raw IJSRuntime calls get messy fast. This library abstracts that, returning strongly-typed results you can await like any .NET service.
Installing Blazor.SubtleCrypto in Your Blazor WebAssembly Project
Fire up your terminal in the project root. Run this:
dotnet add package Blazor.SubtleCrypto
That’s it—no fuss. Restore packages with dotnet restore if you’re picky. The package pulls in everything: ICryptoService interface, JSInterop glue, and those handy CryptoInput/CryptoResult types.
For components, you’d toss @using Blazor.SubtleCrypto at the top of your .razor file. But we’re not here for that. Non-components? Keep reading—they lean on DI registration instead.
Check the Blazor.SubtleCrypto NuGet page for changelogs. Recent updates fixed .NET 8 render-mode quirks.
Registering ICryptoService in Program.cs
Blazor WebAssembly’s DI container lives in Program.cs. Without registration, no injection—period. Here’s the magic line:
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
// Key setup: dynamic (random per session) or fixed (your secret)
builder.Services.AddSubtleCrypto(options =>
{
options.Mode = CryptoMode.DynamicKey; // Or FixedKey with options.Key = "your-base64-secret"
});
await builder.Build().RunAsync();
DynamicKey generates fresh AES-256-GCM keys on demand—perfect for ephemeral sessions. FixedKey? Base64-encode your secret ahead of time for persistent use. The official GitHub repo spells out options like key length and algorithm tweaks.
Scoped by default, it fits WASM’s single-page nature. Boom—ICryptoService is now resolvable anywhere DI flows.
Dependency Injection: Components vs. Non-Component Classes
Blazor’s @inject directive? Component-exclusive. Slap it in a .razor file:
@inject ICryptoService Crypto
@code {
var result = await Crypto.EncryptAsync("hello");
}
Sweet for UI logic. But non-components—say, a DataService or Utility class? No Razor context, no @inject. Enter constructor injection, straight from .NET’s playbook.
Microsoft’s docs nail it: register services in Program.cs, then constructors pull them in. Blazor DI fundamentals confirm Scoped works across the app lifetime in WASM.
What if you’re in a static method or factory? Grab IServiceProvider from the host. But constructors keep things clean.
Step-by-Step: Injecting ICryptoService in a Non-Component
Ready to wire it up? Let’s build a simple AuthService.
- Create your non-component class (Services/AuthService.cs):
using Blazor.SubtleCrypto;
public class AuthService
{
private readonly ICryptoService _crypto;
// Constructor injection—DI magic happens here
public AuthService(ICryptoService crypto)
{
_crypto = crypto;
}
public async Task<string> EncryptTokenAsync(string token)
{
var result = await _crypto.EncryptAsync(token);
return Convert.ToBase64String(result.Value); // Ciphertext ready to store/send
}
}
- Register it in Program.cs (after AddSubtleCrypto):
builder.Services.AddScoped<AuthService>();
- Inject AuthService into a component (or chain further):
@inject AuthService Auth
@code {
var encrypted = await Auth.EncryptTokenAsync("my-jwt");
}
See? No hacks. A StackOverflow thread details this exact pattern, proving it works beyond components.
Test it—hit F5, encrypt something. Smooth.
Full Example: Encrypt and Decrypt in a Custom Service
Let’s go end-to-end. Say SecureStorageService handles user data.
using Blazor.SubtleCrypto;
public class SecureStorageService
{
private readonly ICryptoService _crypto;
public SecureStorageService(ICryptoService crypto)
{
_crypto = crypto;
}
public async Task<byte[]> EncryptAsync(string plaintext)
{
var encryptResult = await _crypto.EncryptAsync(plaintext);
return encryptResult.Value; // byte[] ciphertext
}
public async Task<string> DecryptAsync(byte[] ciphertext, CryptoResult encryptResult)
{
var input = new CryptoInput
{
Data = ciphertext,
Secret = encryptResult.Secret // Reuse key/IV from encrypt
};
var decryptResult = await _crypto.DecryptAsync(input);
return decryptResult.ValueAsString; // Back to plaintext
}
}
Register: builder.Services.AddScoped<SecureStorageService>();
Usage in a page:
@page "/secure"
@inject SecureStorageService Storage
@inject IJSRuntime JS // Optional, for localStorage
<h3>Secure Storage Demo</h3>
<button @onclick="Demo">Encrypt/Decrypt</button>
<p>@message</p>
@code {
private string message = "";
private async Task Demo()
{
var plaintext = "super-secret-data";
var encryptRes = await _crypto.EncryptAsync(plaintext); // Wait, need to store encryptRes.Secret!
// Save encryptRes to localStorage via JS, or pass it along
var ciphertext = encryptRes.Value;
await JS.InvokeVoidAsync("localStorage.setItem", "cipher", Convert.ToBase64String(ciphertext));
await JS.InvokeVoidAsync("localStorage.setItem", "secret", JsonSerializer.Serialize(encryptRes.Secret));
// Later: decrypt
var ctBytes = Convert.FromBase64String(await JS.InvokeAsync<string>("localStorage.getItem", "cipher"));
var secretJson = await JS.InvokeAsync<string>("localStorage.getItem", "secret");
var secret = JsonSerializer.Deserialize<CryptoSecret>(secretJson);
var input = new CryptoInput { Data = ctBytes, Secret = secret };
var decrypted = await _crypto.DecryptAsync(input);
message = decrypted.ValueAsString;
}
}
Pro tip: Serialize CryptoResult.Secret for roundtrips—it’s got Key and IV as byte[].
Common Pitfalls and Fixes
Hit a wall? PlatformNotSupportedException screams “not in browser!”—double-check WASM hosting. .NET 8+ interactive render modes? Add @rendermode InteractiveWebAssembly or tweak JSInterop timing, per the README.
Decrypt failing? Mismatch CryptoInput.Secret from the original encrypt. Another SO post covers decrypt woes—always pair key/IV.
Singleton vs Scoped? Stick to Scoped; WASM reloads nuke Singletons anyway.
No crypto post-SSR? WASM-only—patience for JSInterop warmup.
Best Practices for Blazor WebAssembly Crypto
Dynamic keys rock for sessions, but fixed for app-wide needs. Always await fully—JSInterop’s async under the hood.
Chain services: ICryptoService into DataService into Pages. Lifetimes matter—Scoped for user sessions, Transient for one-offs.
Alternatives? Direct IJSRuntime for subtle.encrypt() if you crave control, but why reinvent? Or libs like Blazored.LocalStorage with crypto wrappers.
Store secrets client-side? Risky—pair with secure tokens. Test in incognito; clear storage often.
Reddit threads echo this: inject services properly, no inheritance hacks needed.
Sources
- Blazor.SubtleCrypto GitHub — Official repo with registration, examples, and non-component DI: https://github.com/memd24/Blazor.SubtleCrypto
- Blazor.SubtleCrypto NuGet — Package details, API reference, and constructor injection samples: https://www.nuget.org/packages/Blazor.SubtleCrypto
- Stack Overflow: Using Blazor.SubtleCrypto in Non-Component — Constructor injection walkthrough for services: https://stackoverflow.com/questions/79867084/how-to-use-blazor-subtlecrypto-in-a-non-component-related-class
- Microsoft Blazor DI Docs — Fundamentals of dependency injection in Blazor WASM: https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/dependency-injection?view=aspnetcore-8.0
- Blazor.SubtleCrypto README — .NET 8+ render mode fixes and advanced config: https://github.com/memd24/Blazor.SubtleCrypto/blob/master/README.md
- Stack Overflow: Decrypt with Blazor.SubtleCrypto — Troubleshooting JSInterop and CryptoInput issues: https://stackoverflow.com/questions/72018234/how-decrypt-data-from-blazor-subtlecrypto
Conclusion
Mastering Blazor.SubtleCrypto in non-components boils down to Program.cs registration and constructor injection—simple, powerful, and fully DI-native for Blazor WebAssembly. You’ll encrypt, decrypt, and secure data seamlessly across services, dodging @inject limits entirely. Experiment with the full example; tweak keys as needed. Secure coding awaits—what’s your first payload?