BlazorServerFunctions 0.10.0

dotnet add package BlazorServerFunctions --version 0.10.0
                    
NuGet\Install-Package BlazorServerFunctions -Version 0.10.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="BlazorServerFunctions" Version="0.10.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="BlazorServerFunctions" Version="0.10.0" />
                    
Directory.Packages.props
<PackageReference Include="BlazorServerFunctions" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add BlazorServerFunctions --version 0.10.0
                    
#r "nuget: BlazorServerFunctions, 0.10.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package BlazorServerFunctions@0.10.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=BlazorServerFunctions&version=0.10.0
                    
Install as a Cake Addin
#tool nuget:?package=BlazorServerFunctions&version=0.10.0
                    
Install as a Cake Tool

BlazorServerFunctions

NuGet License: MIT Benchmarks

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, and RequireAntiForgery have 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.

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

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