Skip to content

Bootstrappers

Thanh Nguyen edited this page May 26, 2025 · 8 revisions

Table of Contents

Introduction

The Blazor Admin Template uses a bootstrapper pattern to manage application initialization and configuration. Setup logic is encapsulated in bootstrappers which are classes marked with the [Bootstrapper] attribute. These classes can be located anywhere within the assembly and are automatically discovered at runtime.

This document explains how bootstrappers work in the Blazor Admin Template, along with implementation details and usage examples.

For better code organization, consider placing all bootstrappers under the Bootstrap namespace.

Bootstrappers are standard classes annotated with the [Bootstrapper] attribute. They define specific methods used to configure and initialize the application. The template automatically discovers these classes at runtime and executes their setup logic in a defined order.

An example of a bootstrapper is shown below:

using MyApp.Shared.Bootstrap;

namespace MyApp.Api.Bootstrap;

[Bootstrapper(Priority = 1000)]
public class ControllersBootstrapper
{
	public static void ConfiguresBuilder(WebApplicationBuilder appBuilder)
	{
		appBuilder.Services.AddControllers();
	}

	public static void DecoratesApp(WebApplication app)
	{
		app.MapControllers();
	}
}

Bootstrapper's priority: Set the Priority property on the [Bootstrapper] attribute to control execution order; lower values execute first. The default value is 1000, and if two bootstrappers have the same priority, their order is undefined.

Bootstrapper's methods: The application looks for specific methods in bootstrappers for execution. These methods are categorized as either configuration of the application builder, service setup or application configuration. See the Bootstrapper's methods section for details.

Bootstrapper's methods

Bootstrapper methods fall into two categories: application builder/service setup and application configuration. During startup, all builder/service setup methods are executed first, ordered by the bootstrapper's Priority value. Configuration methods are then executed, also in order of Priority.

Application Builder / Service Setup Methods

  • Methods must be public, and can be static or instance methods.
  • Methods name and signatures:
    • void ConfiguresBuilder(WebApplicationBuilder): used in server-side applications (e.g., Blazor.Server, Api).
    • void ConfigureWasmBuilder(WebAssemblyHostBuilder): used in client-side (WASM) applications (e.g., Blazor.Client).
    • void ConfigureServices(IServiceCollection): can be used on both server and client, typically in shared setup (e.g., Blazor.App).
  • Async version of the methods are also supported (method names must end with Async), e.g., async Task ConfiguresBuilderAsync(WebApplicationBuilder).
  • Accepted method name variants:
    • ConfigureServices, ConfiguresServices, ConfigureService, ConfiguresService, and their async versions.
    • ConfigureBuilder, ConfiguresBuilder, and their async versions.
    • ConfigureWasmBuilder, ConfiguresWasmBuilder, and their async versions.

Application Configuration Methods

  • Methods must be public, and can be static or instance methods.
  • Methods name and signatures:
    • void DecoratesApplication(WebApplication): used in server-side applications (e.g., Blazor.Server, Api).
    • void DecoratesWasmApplication(WebAssemblyHost): used in client-side (WASM) applications (e.g., Blazor.Client).
    • void InitializesServices(IServiceProvider): can be used on both server and client, typically in shared setup (e.g., Blazor.App).
  • Async version of the methods are also supported (method names must end with Async), e.g., async Task DecoratesApplicationAsync(WebApplication).
  • Accepted method name variants:
    • InitializeServices, InitializesServices, InitializeService, InitializesService, and their async versions.
    • DecorateApp, DecoratesApp, DecorateApplication, DecoratesApplication, and their async versions.
    • DecorateWasmApp, DecoratesWasmApp, DecorateWasmApplication, DecoratesWasmApplication, and their async versions.

Rules and conventions:

  • A single bootstrapper can implement both types of methods (builder/service setup and application configuration). Execution is determined by the environment: server or client.
  • Async methods take precedence over non-async ones. If both are present, only the async version is executed.
  • If multiple name variants of the same method are present (e.g., ConfigureServices(IServiceCollection) and ConfigureService(IServiceCollection)), only one will be executed. However, the selection is undefined, so it's strongly recommended to define only one variant per bootstrapper.

The template uses Blazor's interactive auto-rendering mode by default. This means a single bootstrapper can target both server-side and client-side rendering environments:

Service Setup Phase:

  • In server-side apps: Both ConfiguresBuilder(WebApplicationBuilder) and ConfigureServices(IServiceCollection) (if defined) will be executed.
  • In client-side apps (WASM): Both ConfigureWasmBuilder(WebAssemblyHostBuilder) and ConfigureServices(IServiceCollection) (if defined) will be executed.

Application Configuration Phase:

  • In server-side apps: Both DecoratesApplication(WebApplication) and InitializesServices(IServiceProvider) (if defined) will be executed.
  • In client-side apps (WASM): Both DecoratesWasmApplication(WebAssemblyHost) and InitializesServices(IServiceProvider) (if defined) will be executed.

Make sure to reference the assembly containing your bootstrappers in the appropriate projects (e.g., Blazor.Server, Blazor.Client, Api) so they can be discovered and executed at runtime. See Adding Your Code section for details.

Bootstrapper Examples

A bootstrapper that registers and configures controllers for handling API requests in a server-side application:

using MyApp.Shared.Api;
using MyApp.Shared.Bootstrap;
using Microsoft.AspNetCore.Mvc;

namespace MyApp.Api.Bootstrap;

[Bootstrapper]
public class ControllersBootstrapper
{

	// If you only need access to IServiceCollection, you can implement
    // ConfigureServices(IServiceCollection services) instead of ConfigureBuilder(WebApplicationBuilder builder).

	// This method is executed during the service setup phase.
	public static void ConfigureBuilder(WebApplicationBuilder appBuilder)
	{
		appBuilder.Services.AddControllers()
			.ConfigureApiBehaviorOptions(options =>
			{
				// configure custom response for invalid model state (usually input validation failed)
				options.InvalidModelStateResponseFactory = context =>
				{
					var errors = context.ModelState.Values.SelectMany(v => v.Errors.Select(e => e.ErrorMessage));
					return new BadRequestObjectResult(new ApiResp
					{
						Status = 400,
						Message = $"Bad request: {string.Join(", ", errors)}",
						Extras = errors
					});
				};
			});
	}

	// If you only need access to IServiceProvider, you can implement
    // InitializesServices(IServiceProvider services) instead of DecorateApp(WebApplication app).

	// This method is executed during the application configuration phase.
	public static void DecorateApp(WebApplication app)
	{
		app.MapControllers();
	}
}

This bootstrapper sets up Blazor components, executed at server-side:

using MyApp.Shared.Bootstrap;

namespace MyApp.Blazor.Bootstrap;

[Bootstrapper]
public class BlazorBootstrapper
{

	// If you need access to WebApplicationBuilder, use ConfigureBuilder(WebApplicationBuilder builder) instead of ConfigureServices(IServiceCollection services)

	// This method is executed during the service setup phase.
	public static void ConfigureServices(IServiceCollection services)
	{
		services.AddRazorComponents()
			.AddInteractiveServerComponents()
			.AddInteractiveWebAssemblyComponents();
	}

	// If you only need access to IServiceProvider, you can implement
    // InitializesServices(IServiceProvider services) instead of DecorateApp(WebApplication app).

	// This method is executed during the application configuration phase.
	public static void DecorateApp(WebApplication app)
	{
		if (app.Environment.IsDevelopment())
		{
			app.UseWebAssemblyDebugging();
		}
		else
		{
			app.UseExceptionHandler("/Error", createScopeForErrors: true);
			// 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.UseStaticFiles();
		//app.UseRouting();
		app.UseAntiforgery();

		app.MapRazorComponents<Components.App>()
			.AddInteractiveServerRenderMode()
			.AddInteractiveWebAssemblyRenderMode()
			.AddAdditionalAssemblies(typeof(Client._Imports).Assembly, typeof(App._Imports).Assembly);
	}
}

A bootstraper that configures authorization policies for both server-side and client-side rendering modes:

using MyApp.Blazor.App.Services;
using MyApp.Shared.Bootstrap;
using MyApp.Shared.Identity;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.DependencyInjection;

namespace MyApp.Blazor.App.Bootstrap;

[Bootstrapper]
public class AuthBootstrapper
{
	// ConfigureServices(IServiceCollection services) is the common method signature for both server-side and client-side rendering modes,
	// and will be executed in both server-side and client-side environments.
	public static void ConfigureServices(IServiceCollection services)
	{
		// set up authorization
		services.AddAuthorizationCore(c =>
		{
			// Configurate authorization policies
			c.AddPolicy(BuiltinPolicies.POLICY_NAME_ADMIN_ROLE_OR_USER_MANAGER, BuiltinPolicies.POLICY_ADMIN_ROLE_OR_USER_MANAGER);
			c.AddPolicy(BuiltinPolicies.POLICY_NAME_ADMIN_ROLE_OR_APPLICATION_MANAGER, BuiltinPolicies.POLICY_ADMIN_ROLE_OR_APPLICATION_MANAGER);
			c.AddPolicy(BuiltinPolicies.POLICY_NAME_ADMIN_ROLE_OR_CREATE_USER_PERM, BuiltinPolicies.POLICY_ADMIN_ROLE_OR_CREATE_USER_PERM);
			c.AddPolicy(BuiltinPolicies.POLICY_NAME_ADMIN_ROLE_OR_MODIFY_USER_PERM, BuiltinPolicies.POLICY_ADMIN_ROLE_OR_MODIFY_USER_PERM);
			c.AddPolicy(BuiltinPolicies.POLICY_NAME_ADMIN_ROLE_OR_DELETE_USER_PERM, BuiltinPolicies.POLICY_ADMIN_ROLE_OR_DELETE_USER_PERM);
			c.AddPolicy(BuiltinPolicies.POLICY_NAME_ADMIN_ROLE_OR_CREATE_ROLE_PERM, BuiltinPolicies.POLICY_ADMIN_ROLE_OR_CREATE_ROLE_PERM);
			c.AddPolicy(BuiltinPolicies.POLICY_NAME_ADMIN_ROLE_OR_MODIFY_ROLE_PERM, BuiltinPolicies.POLICY_ADMIN_ROLE_OR_MODIFY_ROLE_PERM);
			c.AddPolicy(BuiltinPolicies.POLICY_NAME_ADMIN_ROLE_OR_DELETE_ROLE_PERM, BuiltinPolicies.POLICY_ADMIN_ROLE_OR_DELETE_ROLE_PERM);
			c.AddPolicy(BuiltinPolicies.POLICY_NAME_ADMIN_ROLE_OR_CREATE_APP_PERM, BuiltinPolicies.POLICY_ADMIN_ROLE_OR_CREATE_APP_PERM);
			c.AddPolicy(BuiltinPolicies.POLICY_NAME_ADMIN_ROLE_OR_MODIFY_APP_PERM, BuiltinPolicies.POLICY_ADMIN_ROLE_OR_MODIFY_APP_PERM);
			c.AddPolicy(BuiltinPolicies.POLICY_NAME_ADMIN_ROLE_OR_DELETE_APP_PERM, BuiltinPolicies.POLICY_ADMIN_ROLE_OR_DELETE_APP_PERM);
		});

		// register the custom state provider
		services.AddScoped<AuthenticationStateProvider, JwtAuthenticationStateProvider>();
	}
}

See also