BlazorServerFunctions 0.10.0
dotnet add package BlazorServerFunctions --version 0.10.0
NuGet\Install-Package BlazorServerFunctions -Version 0.10.0
<PackageReference Include="BlazorServerFunctions" Version="0.10.0" />
<PackageVersion Include="BlazorServerFunctions" Version="0.10.0" />
<PackageReference Include="BlazorServerFunctions" />
paket add BlazorServerFunctions --version 0.10.0
#r "nuget: BlazorServerFunctions, 0.10.0"
#:package BlazorServerFunctions@0.10.0
#addin nuget:?package=BlazorServerFunctions&version=0.10.0
#tool nuget:?package=BlazorServerFunctions&version=0.10.0
BlazorServerFunctions
Zero-boilerplate HTTP client proxies and ASP.NET Core minimal API endpoints — generated at compile time from a single C# interface.
Annotate an interface once. BlazorServerFunctions generates the HttpClient proxy, the server-side minimal API endpoints, and the DI wiring for both. Works with Blazor Server, WASM, and Auto render modes.
Installation
dotnet add package BlazorServerFunctions
The package is a Roslyn source generator — no runtime dependency, no reflection, no middleware.
Quick Start
1. Define a shared interface (e.g. in a .Shared project)
using BlazorServerFunctions.Abstractions;
[ServerFunctionCollection(RoutePrefix = "api/weather")]
public interface IWeatherService
{
[ServerFunction(HttpMethod = "GET")]
Task<WeatherForecast[]> GetForecastAsync(string city, CancellationToken ct = default);
[ServerFunction(HttpMethod = "POST")]
Task<WeatherForecast> CreateForecastAsync(WeatherForecast forecast, CancellationToken ct = default);
}
2. Implement it on the server
public class WeatherService : IWeatherService
{
public Task<WeatherForecast[]> GetForecastAsync(string city, CancellationToken ct) { ... }
public Task<WeatherForecast> CreateForecastAsync(WeatherForecast forecast, CancellationToken ct) { ... }
}
3. Register on the server (Program.cs)
builder.Services.AddScoped<IWeatherService, WeatherService>();
var app = builder.Build();
app.MapServerFunctionEndpoints(); // generated — maps all endpoints
4. Register on the client (Program.cs — WASM or Blazor Server)
builder.Services.AddServerFunctionClients(
baseAddress: new Uri("https://localhost:5001"));
5. Inject and use in a Blazor component
@inject IWeatherService WeatherService
@code {
WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await WeatherService.GetForecastAsync("London");
}
}
The generator produces a WeatherServiceClient that implements IWeatherService — your component code never changes between render modes.
Configuration
For advanced scenarios, subclass ServerFunctionConfiguration to share settings across multiple interfaces.
// Define once — can be shared across interfaces via inheritance
public class MyApiConfig : ServerFunctionConfiguration
{
public MyApiConfig()
{
BaseRoute = "api/v1";
RouteNaming = RouteNaming.KebabCase;
}
}
// Apply to an interface
[ServerFunctionCollection(Configuration = typeof(MyApiConfig))]
public interface IUserService
{
[ServerFunction(HttpMethod = "GET")]
Task<User[]> GetUsersAsync(); // → GET /api/v1/userservice/get-users-async
}
Available settings
| Setting | Type | Default | Description |
|---|---|---|---|
BaseRoute |
string |
"api/functions" |
Route prefix for all endpoints in the collection |
RouteNaming |
RouteNaming |
PascalCase |
Route segment casing: PascalCase, CamelCase, KebabCase, SnakeCase |
DefaultHttpMethod |
string? |
null |
Default HTTP method when [ServerFunction] doesn't specify one (suppresses BSF013) |
GenerateProblemDetails |
bool |
true |
Emit Problem Details error responses from server endpoints |
Nullable |
bool |
true |
Emit #nullable enable at the top of generated files |
CustomHttpClientType |
Type? |
null |
Use a custom HttpClient subclass in generated proxy constructors |
ApiType |
ApiType |
REST |
Transport protocol (REST or GRPC) |
CacheSeconds |
int |
0 |
Default output-cache duration (seconds) for all GET endpoints; 0 = disabled; overridable per method |
RateLimitPolicy |
string? |
null |
Default rate-limiting policy name for all endpoints; null = none; overridable per method |
Policy |
string? |
null |
Default named authorization policy for all endpoints; null = none; overridable per method ("" = opt out) |
Configuration priority (highest wins):
[ServerFunction(...)] attribute property ← highest
[ServerFunctionCollection(Configuration = typeof(...))]
Generator defaults ← lowest
Attribute Reference
[ServerFunctionCollection]
Applied to an interface. Controls route prefix, authorization, and optional configuration for all methods in the collection.
| Property | Type | Default | Description |
|---|---|---|---|
RoutePrefix |
string? |
null |
Route prefix prepended to all method routes (e.g. "api/users") |
RequireAuthorization |
bool |
false |
Calls .RequireAuthorization() on the generated route group |
CorsPolicy |
string? |
null |
Named CORS policy applied via group.RequireCors("name") on the route group. null = no CORS. Empty string is an error (BSF022). Requires AddCors(...) + UseCors() in the server pipeline |
Configuration |
Type? |
null |
A ServerFunctionConfiguration subclass that controls code generation settings |
[ServerFunction]
Applied to a method. Controls the HTTP method, route, authorization, caching, and rate limiting.
| Property | Type | Default | Description |
|---|---|---|---|
HttpMethod |
string |
(required) | "GET", "POST", "PUT", "PATCH", or "DELETE" |
Route |
string? |
Method name | Route segment appended to the collection's prefix; supports {param} placeholders |
RequireAuthorization |
bool |
false |
Calls .RequireAuthorization() on this specific endpoint |
CacheSeconds |
int |
-1 (inherit) |
Seconds to cache via .CacheOutput(...); -1 = inherit from config, 0 = disable. Only valid on GET endpoints. Requires AddOutputCache() + UseOutputCache() in the server pipeline |
RateLimitPolicy |
string? |
null (inherit) |
Named rate-limiting policy applied via .RequireRateLimiting("name"); null = inherit from config, "" = disable. Requires AddRateLimiter(...) + UseRateLimiter() in the server pipeline |
Policy |
string? |
null (inherit) |
Named authorization policy applied via .RequireAuthorization("name"); null = inherit from config, "" = disable. Does not affect the boolean RequireAuthorization setting |
Roles |
string? |
null |
Comma-separated role names applied via .RequireAuthorization(new AuthorizeAttribute { Roles = "..." }); null = no restriction. Can be combined with Policy and RequireAuthorization. Empty string is an error (BSF021). |
RequireAntiForgery |
bool |
false |
Adds .WithMetadata(new RequireAntiforgeryTokenAttribute()) to the endpoint. Requires AddAntiforgery() + UseAntiforgery() in the server pipeline. |
Filters |
Type[]? |
null |
One or more IEndpointFilter types applied via .AddEndpointFilter<T>() in declaration order. Example: Filters = new[] { typeof(MyFilter) } |
DI Setup
Server-side (Program.cs)
// Register your service implementations as usual
builder.Services.AddScoped<IWeatherService, WeatherService>();
builder.Services.AddScoped<IUserService, UserService>();
var app = builder.Build();
// One call maps all generated endpoints
app.MapServerFunctionEndpoints();
Client-side (Program.cs)
// Simple — base address only
builder.Services.AddServerFunctionClients(
baseAddress: new Uri(builder.HostEnvironment.BaseAddress));
// Advanced — customise each IHttpClientBuilder (auth handlers, resilience, etc.)
builder.Services.AddServerFunctionClients(
baseAddress: new Uri(builder.HostEnvironment.BaseAddress),
configureClient: builder => builder.AddHttpMessageHandler<AuthHandler>());
configureClient is called for every registered service client, so it's the right place for cross-cutting concerns like JWT bearer tokens, cookie forwarding, resilience pipelines, or logging handlers.
Authentication & JWT Example
Server — protect the whole collection:
[ServerFunctionCollection(RoutePrefix = "api/admin", RequireAuthorization = true)]
public interface IAdminService
{
[ServerFunction(HttpMethod = "GET")]
Task<AdminStats> GetStatsAsync();
}
Or protect individual methods:
[ServerFunctionCollection(RoutePrefix = "api/users")]
public interface IUserService
{
[ServerFunction(HttpMethod = "GET")]
Task<UserProfile[]> GetAllAsync(); // public
[ServerFunction(HttpMethod = "DELETE", RequireAuthorization = true)]
Task DeleteUserAsync(Guid id); // protected
}
Client — attach a JWT bearer delegating handler:
public class JwtBearerHandler : DelegatingHandler
{
private readonly ITokenService _tokens;
public JwtBearerHandler(ITokenService tokens) => _tokens = tokens;
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken ct)
{
var token = await _tokens.GetAccessTokenAsync(ct);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
return await base.SendAsync(request, ct);
}
}
// Registration
builder.Services.AddTransient<JwtBearerHandler>();
builder.Services.AddServerFunctionClients(
baseAddress: new Uri(builder.HostEnvironment.BaseAddress),
configureClient: b => b.AddHttpMessageHandler<JwtBearerHandler>());
Error handling — when the server returns a non-success status code, the generated client throws HttpRequestException. If the server returned a Problem Details body (RFC 9457), its detail field is forwarded as the exception message.
Route / Path Parameters
Use {param} placeholders in Route to bind URL segments:
[ServerFunction(HttpMethod = "GET", Route = "users/{id}")]
Task<User> GetUserAsync(Guid id);
[ServerFunction(HttpMethod = "DELETE", Route = "users/{id}")]
Task DeleteUserAsync(Guid id);
The generator binds route parameters from the URL on the server and interpolates them into the request URL on the client — no manual wiring needed.
Streaming (IAsyncEnumerable<T>)
Return IAsyncEnumerable<T> for chunked server-sent streaming:
[ServerFunction(HttpMethod = "GET")]
IAsyncEnumerable<WeatherForecast> StreamForecastsAsync(CancellationToken ct = default);
The server endpoint returns the stream directly (ASP.NET Core handles chunked JSON). The client proxy reads the stream incrementally via ReadFromJsonAsAsyncEnumerable<T>() with HttpCompletionOption.ResponseHeadersRead.
gRPC Quick-Start (code-first, no .proto files)
BlazorServerFunctions supports code-first gRPC via protobuf-net.Grpc. Set ApiType = ApiType.GRPC and the generator produces a gRPC service class and a matching client proxy — no .proto files, no manual contract maintenance.
1. Add NuGet references
Shared project (where the interface lives):
<PackageReference Include="protobuf-net.Grpc" Version="1.2.*" />
<PackageReference Include="System.ServiceModel.Primitives" Version="8.1.*" />
Server project:
<PackageReference Include="protobuf-net.Grpc.AspNetCore" Version="1.2.*" />
Client project (WASM / Blazor Server):
<PackageReference Include="Grpc.Net.Client" Version="2.*" />
2. Declare a gRPC interface in the shared project
using BlazorServerFunctions.Abstractions;
[ServerFunctionCollection(ApiType = ApiType.GRPC)]
public interface IGrpcDemoService
{
Task<string> EchoAsync(string message, CancellationToken ct = default);
// IAsyncEnumerable<T> maps to a gRPC server-streaming method automatically
IAsyncEnumerable<string> CountdownAsync(int from, CancellationToken ct = default);
}
HttpMethod,CacheSeconds, andRequireAntiForgeryhave no meaning on gRPC interfaces — the generator reports diagnostics BSF023/BSF024/BSF025 for those.
3. Implement the service on the server
public class GrpcDemoService : IGrpcDemoService
{
public Task<string> EchoAsync(string message, CancellationToken ct)
=> Task.FromResult($"gRPC echo: {message}");
public async IAsyncEnumerable<string> CountdownAsync(int from, [EnumeratorCancellation] CancellationToken ct)
{
for (var i = from; i >= 0; i--)
{
yield return i.ToString();
await Task.Delay(100, ct);
}
}
}
4. Register on the server (Program.cs)
using ProtoBuf.Grpc.Server; // for AddCodeFirstGrpc()
builder.Services.AddCodeFirstGrpc();
builder.Services.AddScoped<IGrpcDemoService, GrpcDemoService>();
var app = builder.Build();
app.MapServerFunctionEndpoints(); // also calls MapGrpcService<GrpcDemoServiceGrpcService>()
5. Register on the client (Program.cs)
// baseAddress is required when any gRPC interfaces are registered
builder.Services.AddServerFunctionClients(
baseAddress: new Uri("https://localhost:5001"));
What the generator produces
For a gRPC interface the generator emits:
| Generated file | Content |
|---|---|
{Interface}GrpcClient.g.cs (shared) |
I{Service}GrpcContract (wire contract with [ServiceContract]) + {Service}GrpcClient : IXxxService (calls the contract via GrpcChannel.CreateGrpcService<T>()) + [ProtoContract] request wrapper types |
{Interface}GrpcService.g.cs (server) |
{Service}GrpcService : I{Service}GrpcContract — the server-side implementation that delegates to your injected IXxxService |
ServerFunctionClientsRegistration.g.cs |
Registers GrpcChannel as a singleton and {Service}GrpcClient as transient |
ServerFunctionEndpointsRegistration.g.cs |
Calls endpoints.MapGrpcService<{Service}GrpcService>() |
Output Caching
Cache GET responses with a single attribute property:
// Per-method
[ServerFunction(HttpMethod = "GET", CacheSeconds = 30)]
Task<int> GetCountAsync();
// Or set a collection-level default and override per method
public class CachedConfig : ServerFunctionConfiguration
{
public CachedConfig() { CacheSeconds = 60; }
}
[ServerFunctionCollection(Configuration = typeof(CachedConfig))]
public interface IProductService
{
[ServerFunction(HttpMethod = "GET")]
Task<Product[]> GetAllAsync(); // cached 60 s (from config)
[ServerFunction(HttpMethod = "GET", CacheSeconds = 0)]
Task<Product> GetByIdAsync(Guid id); // explicitly disabled
}
Requires builder.Services.AddOutputCache() and app.UseOutputCache() in the server pipeline.
Rate Limiting
Reference any named rate-limiting policy you've already registered:
// Define the policy in Program.cs
builder.Services.AddRateLimiter(options =>
options.AddFixedWindowLimiter("api", limiter =>
{
limiter.PermitLimit = 100;
limiter.Window = TimeSpan.FromMinutes(1);
}));
app.UseRateLimiter();
// Apply via attribute (any HTTP method, including streaming)
[ServerFunction(HttpMethod = "POST", RateLimitPolicy = "api")]
Task<Order> CreateOrderAsync(Order order);
// Or set a collection-level default
public class RateLimitedConfig : ServerFunctionConfiguration
{
public RateLimitedConfig() { RateLimitPolicy = "api"; }
}
The generator emits .RequireRateLimiting("api") in the fluent endpoint chain. Per-method RateLimitPolicy = "" (empty string) explicitly opts a single method out of the collection-level default.
Authorization Policies
Reference any named authorization policy you've already registered:
// Apply via attribute
[ServerFunction(HttpMethod = "GET", Policy = "AdminOnly")]
Task<AdminStats> GetStatsAsync();
// Or set a collection-level default
public class AdminConfig : ServerFunctionConfiguration
{
public AdminConfig() { Policy = "AdminOnly"; }
}
The generator emits .RequireAuthorization("AdminOnly") in the fluent endpoint chain. Per-method Policy = "" (empty string) explicitly opts a single method out of the collection-level default.
This can be combined with the boolean RequireAuthorization — a route group can have .RequireAuthorization() (from [ServerFunctionCollection(RequireAuthorization = true)]) while individual methods apply a more specific named policy on top.
Role-based auth
Apply role restrictions directly on a method:
[ServerFunction(HttpMethod = "DELETE", Roles = "Admin,Manager")]
Task DeleteUserAsync(Guid id);
The generator emits .RequireAuthorization(new AuthorizeAttribute { Roles = "Admin,Manager" }). Multiple roles are comma-separated (OR logic within the list — a user in any one of the roles passes).
Roles can be combined with Policy and RequireAuthorization on the same method — ASP.NET Core ANDs all authorization requirements together:
// Must satisfy "PremiumPolicy" AND be in "Admin" or "Manager" role
[ServerFunction(HttpMethod = "GET", Policy = "PremiumPolicy", Roles = "Admin,Manager")]
Task<AdminStats> GetStatsAsync();
CORS per interface
Apply a named CORS policy to all endpoints in a collection via the CorsPolicy attribute:
[ServerFunctionCollection(RoutePrefix = "api/data", CorsPolicy = "AllowedOrigins")]
public interface IDataService
{
[ServerFunction(HttpMethod = "GET")]
Task<string[]> GetItemsAsync();
}
The generator emits group.RequireCors("AllowedOrigins") on the route group. You must register the policy and enable the middleware in the server:
builder.Services.AddCors(options =>
options.AddPolicy("AllowedOrigins", policy =>
policy.WithOrigins("https://example.com").AllowAnyHeader().AllowAnyMethod()));
app.UseCors(); // before UseAuthorization
A collection-level default can also be set via ServerFunctionConfiguration.CorsPolicy; the attribute value overrides the config default.
Anti-forgery
Apply RequireAntiforgeryTokenAttribute metadata to individual endpoints via RequireAntiForgery = true:
[ServerFunctionCollection(RoutePrefix = "api/forms")]
public interface IFormService
{
[ServerFunction(HttpMethod = "POST", RequireAntiForgery = true)]
Task<string> SubmitFormAsync(string data);
}
Register antiforgery services and middleware in your server pipeline:
builder.Services.AddAntiforgery();
// ...
app.UseAntiforgery();
Endpoint filters
Apply one or more IEndpointFilter types to a method via Filters = new[] { typeof(...) }:
// Implement the filter in a project that is accessible where the interface is declared
public sealed class LoggingFilter : IEndpointFilter
{
public ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
{
// Pre-handler logic here
return next(context);
}
}
[ServerFunctionCollection]
public interface IOrderService
{
// Single filter
[ServerFunction(HttpMethod = "POST", Filters = new[] { typeof(LoggingFilter) })]
Task<OrderDto> CreateOrderAsync(OrderDto order);
}
Multiple filters are applied in declaration order:
[ServerFunction(HttpMethod = "POST", Filters = new[] { typeof(AuthFilter), typeof(LoggingFilter) })]
Task<OrderDto> CreateOrderAsync(OrderDto order);
// Generated: .AddEndpointFilter<AuthFilter>().AddEndpointFilter<LoggingFilter>()
How It Works
The generator inspects every [ServerFunctionCollection] interface at compile time and produces four files:
| Generated file | Content |
|---|---|
{Interface}Client.g.cs |
HttpClient-based implementation of the interface |
{Interface}ServerExtensions.g.cs |
Minimal API endpoint mappings |
ServerFunctionClientsRegistration.g.cs |
AddServerFunctionClients(...) extension method |
ServerFunctionEndpointsRegistration.g.cs |
MapServerFunctionEndpoints() extension method |
The generator detects project type automatically:
- Server project (references
IEndpointRouteBuilder) → generates all four files - WASM / Library project → generates client proxy + client registration only
Sample App
See samples/ for a complete Blazor app demonstrating all four render modes (Server, WASM, Auto, and a plain HTTP client) with Aspire orchestration.
License
MIT — see LICENSE.
Learn more about Target Frameworks and .NET Standard.
-
.NETStandard 2.0
- Microsoft.CodeAnalysis.CSharp (>= 4.12.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.10.0 | 111 | 3/24/2026 |
See CHANGELOG.md at https://github.com/ncigula/BlazorServerFunctions/blob/master/CHANGELOG.md