ServiceStack v8.1

Full integration with ASP .NET Core 8

We're excited to announce the most integrated release of ServiceStack ever which continues the trend from last year when we switched our newest .NET 8 templates to adopt ASP.NET Core Identity Auth that continues to replace ServiceStack's platform-agnostic abstractions to use ASP.NET Core's latest default implementations, built-in features and conventions.

This reduces friction for integrating ServiceStack into existing .NET 8 Apps, encourages greater knowledge and reuse and simplifies .NET development as developers have a reduced number of concepts to learn, fewer technology implementations to configure and maintain that are now applied across their entire .NET App.

It's also a departure from building upon our own platform-agnostic abstractions which allowed the same ServiceStack code-base to run on both .NET Core and .NET Framework. Our focus going forward will be to instead adopt De facto standards and conventions of the latest .NET platform which also means ServiceStack's new value-added features are only available in the latest .NET 8+ LTS release.

ServiceStack Middleware

Whilst ServiceStack integrates into ASP.NET Core Apps as custom middleware into ASP.NET Core's HTTP Request Pipeline, it invokes its own black-box of functionality from there, implemented using its own suite of overlapping features.

Whilst this allows ServiceStack to have full control over how to implement its features, it's not as integrated as it could be, with there being limits on what ServiceStack Functionality could be reused within external ASP .NET Core MVC Controllers, Razor Pages, etc. and inhibited the ability to apply application-wide authorization policies across an Application entire surface area, using and configuring different JSON Serialization implementations.

Areas for tighter integration

The major areas we've identified that would benefit from tighter integration with ASP.NET Core include:

ServiceStack v8.1 now fully integrated!

We're happy to announce the latest release of ServiceStack v8.1 now supports utilizing the optimal ASP.NET Core's standardized features to reimplement all these key areas - fostering seamless integration and greater reuse which you can learn about below:

Migrating to ASP.NET Core Endpoints

To assist ServiceStack users in upgrading their existing projects we've created a migration guide walking through the steps required to adopt these new defaults:

Endpoint Routing Everywhere

Better yet, this new behavior is enabled by default in all of ServiceStack's new ASP .NET Identity Auth .NET 8 templates that has also been tested against many of ServiceStack's existing .NET Demo Apps which are now all running with the latest recommended integration:

ServiceStack .NET 8 Example Apps

BlazorDiffusionVue BlazorDiffusionAuto TalentBlazor
TechStacks TypeChatExamples Validation
NorthwindAuto FileBlazor Chinook
TypeChatExamples Chat

ASP .NET Core IOC

The primary limitation of ServiceStack using its own Funq IOC is that any dependencies registered in Funq are not injected into Razor Pages, Blazor Components, MVC Controllers, etc.

That's why ServiceStack App's Modular Startup configurations utilize custom IHostingStartup configurations where application dependencies are registered in ASP .NET Core's IOC where they can be injected into both ServiceStack Services and ASP.NET Core's external components, e.g:

[assembly: HostingStartup(typeof(MyApp.ConfigureDb))]

namespace MyApp;

public class ConfigureDb : IHostingStartup
{
    public void Configure(IWebHostBuilder builder) => builder
        .ConfigureServices((context, services) => {
            services.AddSingleton<IDbConnectionFactory>(new OrmLiteConnectionFactory(
                context.Configuration.GetConnectionString("DefaultConnection"),
                SqliteDialect.Provider));
        });
}

But there were fundamental restrictions on what could be registered in ASP .NET Core's IOC as everything needed to be registered before AspNetCore's WebApplication was built and before ServiceStack's AppHost could be initialized. This prohibited being able to register any dependencies created by the AppHost including Services, AutoGen Services, Validators and internal functionality like App Settings, Virtual File System and Caching providers, etc.

Switch to use ASP.NET Core IOC

To enable ServiceStack to switch to use ASP .NET Core's IOC, all dependency, Plugins and Service registrations need to be moved to before the WebApplication is built by calling AddServiceStack() with the Assemblies where your ServiceStack Service Implementations are located, e.g:

builder.Services.AddServiceStack(typeof(MyServices).Assembly);

var app = builder.Build();

//...
app.UseServiceStack(new AppHost());

This change registers all ServiceStack dependencies in ASP.NET Core's IOC, including all ServiceStack Services prior to the AppHost being initialized which no longer needs to specify the Assemblies where ServiceStack Services are created and no longer needs to reference Funq since it's no longer needed.

Registering Dependencies and Plugins

Additionally ASP.NET Core's IOC requirement for all dependencies needing to be registered before the WebApplication is built means you'll no longer be able to register any dependencies or plugins in ServiceStack's AppHost.Configure():

public class AppHost() : AppHostBase("MyApp"), IHostingStartup
{
    public void Configure(IWebHostBuilder builder) => builder
        .ConfigureServices(services => {
            // Register IOC Dependencies and ServiceStack Plugins
        });

    public override void Configure()
    {
        // DO NOT REGISTER ANY PLUGINS OR DEPENDENCIES HERE
    }
}

Instead anything that needs to register dependencies in ASP.NET Core IOC should now use the IServiceCollection extension methods:

  • IServiceCollection.Add* APIs to register dependencies
  • IServiceCollection.AddPlugin API to register ServiceStack Plugins
  • IServiceCollection.RegisterService* APIs to dynamically register ServiceStack Services in external Assemblies

This can be done whenever you have access to IServiceCollection, either in Program.cs:

builder.Services.AddPlugin(new AdminDatabaseFeature());

Or in any Modular Startup IHostingStartup configuration class, e.g:

public class ConfigureDb : IHostingStartup
{
    public void Configure(IWebHostBuilder builder) => builder
        .ConfigureServices((context, services) => {
            services.AddSingleton<IDbConnectionFactory>(new OrmLiteConnectionFactory(
                context.Configuration.GetConnectionString("DefaultConnection"),
                SqliteDialect.Provider));
            
            // Enable Audit History
            services.AddSingleton<ICrudEvents>(c =>
                new OrmLiteCrudEvents(c.GetRequiredService<IDbConnectionFactory>()));
            
            // Enable AutoQuery RDBMS APIs
            services.AddPlugin(new AutoQueryFeature {
                 MaxLimit = 1000,
            });

            // Enable AutoQuery Data APIs
            services.AddPlugin(new AutoQueryDataFeature());
            
            // Enable built-in Database Admin UI at /admin-ui/database
            services.AddPlugin(new AdminDatabaseFeature());
        })
        .ConfigureAppHost(appHost => {
            appHost.Resolve<ICrudEvents>().InitSchema();
        });
}

The ConfigureAppHost() extension method can continue to be used to execute any logic that requires access to registered dependencies on StartUp.

Authoring ServiceStack Plugins

To enable ServiceStack Plugins to support both Funq and ASP .NET Core IOC, any dependencies and Services a plugin needs should be registered in the IConfigureServices.Configure(IServiceCollection) method as seen in the refactored ServerEventsFeature.cs plugin, e.g:

public class ServerEventsFeature : IPlugin, IConfigureServices
{
    //...
    public void Configure(IServiceCollection services)
    {
        if (!services.Exists<IServerEvents>())
        {
            services.AddSingleton<IServerEvents>(new MemoryServerEvents
            {
                IdleTimeout = IdleTimeout,
                HouseKeepingInterval = HouseKeepingInterval,
                OnSubscribeAsync = OnSubscribeAsync,
                OnUnsubscribeAsync = OnUnsubscribeAsync,
                OnUpdateAsync = OnUpdateAsync,
                NotifyChannelOfSubscriptions = NotifyChannelOfSubscriptions,
                Serialize = Serialize,
                OnError = OnError,
            });
        }
        
        if (UnRegisterPath != null)
            services.RegisterService<ServerEventsUnRegisterService>(UnRegisterPath);

        if (SubscribersPath != null)
            services.RegisterService<ServerEventsSubscribersService>(SubscribersPath);
    }

    public void Register(IAppHost appHost)
    {
        //...
    }
}

All Plugins refactored to support ASP .NET Core IOC

All of ServiceStack's Plugins have been refactored to make use of IConfigureServices which supports registering in both Funq and ASP.NET Core's IOC when enabled.

Funq IOC implements IServiceCollection and IServiceProvider interfaces

To enable this Funq now implements both IServiceCollection andIServiceProvider interfaces to enable 100% source-code compatibility for registering and resolving dependencies with either IOC, which we now recommend using over Funq's native Registration and Resolution APIs to simplify migration efforts to ASP.NET Core's IOC in future.

Dependency Injection

The primary difference between the IOC's is that ASP.NET Core's IOC does not support property injection by default, which will require you to refactor your ServiceStack Services to use constructor injection of dependencies, although this has become a lot more pleasant with C# 12's Primary Constructors which now requires a lot less boilerplate to define, assign and access dependencies, e.g:

public class TechStackServices(IAutoQueryDb autoQuery) : Service
{
    public async Task<object> Any(QueryTechStacks request)
    {
        using var db = autoQuery.GetDb(request, base.Request);
        var q = autoQuery.CreateQuery(request, Request, db);
        return await autoQuery.ExecuteAsync(request, q, db);
    }
}

This has become our preferred approach for injecting dependencies in ServiceStack Services which have all been refactored to use constructor injection utilizing primary constructors in order to support both IOC's.

To make migrations easier we've also added support for property injection convention in ServiceStack Services using ASP.NET Core's IOC where you can add the [FromServices] attribute to any public properties you want injected, e.g:

public class TechStackServices : Service
{
    [FromServices]
    public required IAutoQueryDb AutoQuery { get; set; }

    [FromServices]
    public MyDependency? OptionalDependency { get; set; }
}

This feature is useful for Services needing optional access to dependencies that may or may not be registered.

NOTE

[FromServices] is only supported in ServiceStack Services (i.e. not other dependencies)

Built-in ServiceStack Dependencies

This integration now makes it effortless to inject and utilize optional ServiceStack features like AutoQuery and Server Events in other parts of ASP.NET Core inc. Blazor Components, Razor Pages, MVC Controllers, Minimal APIs, etc.

Whilst the Built-in ServiceStack features that are registered by default and immediately available to be injected, include:

  • IVirtualFiles - Read/Write Virtual File System, defaults to FileSystemVirtualFiles at ContentRootPath
  • IVirtualPathProvider - Multi Virtual File System configured to scan multiple read only sources, inc WebRootPath, In Memory and Embedded Resource files
  • ICacheClient and ICacheClientAsync - In Memory Cache, or distributed Redis if ServiceStack.Redis is configured
  • IAppSettings - Multiple AppSettings configuration sources

With ASP.NET Core's IOC now deeply integrated we moved onto the next area of integration: API Integration and Endpoint Routing.

Endpoint Routing

Whilst ASP.NET Core's middleware is a flexible way to compose and execute different middleware in a HTTP Request pipeline, each middleware is effectively their own island of functionality that's able to handle HTTP Requests in which ever way they see fit.

In particular ServiceStack's middleware would execute its own Request Pipeline which would execute ServiceStack API's registered at user-defined routes with its own ServiceStack Routing implementation.

We're happy to announce that ServiceStack .NET 8 Apps support an entirely new and integrated way to run all of ServiceStack requests including all APIs, metadata and built-in UIs with support for ASP.NET Core Endpoint Routing - enabled by calling MapEndpoints() when configuring ServiceStack:

app.UseServiceStack(new AppHost(), options => {
    options.MapEndpoints();
});

Which configures ServiceStack APIs to be registered and executed along-side Minimal APIs, Razor Pages, SignalR, MVC and Web API Controllers, etc, utilizing the same routing, metadata and execution pipeline.

View ServiceStack APIs along-side ASP.NET Core APIs

Amongst other benefits, this integration is evident in endpoint metadata explorers like the Swashbuckle library which can now show ServiceStack APIs in its Swagger UI along-side other ASP.NET Core APIs in ServiceStack's new Open API v3 support.

Routing Syntax

Using Endpoint Routing also means using ASP.NET Core's Routing System which now lets you use ASP.NET Core's Route constraints for defining user-defined routes for your ServiceStack APIs, e.g:

[Route("/users/{Id:int}")]
[Route("/users/{UserName:string}")]
public class GetUser : IGet, IReturn<User>
{
    public int? Id { get; set; }
    public int? UserName { get; set; }
}

For the most part ServiceStack Routing implements a subset of ASP.NET Core's Routing features so your existing user-defined routes should continue to work as expected.

Wildcard Routes

The only incompatibility we found was when using wildcard paths which in ServiceStack Routing would use an '*' suffix, e.g: [Route("/wildcard/{Path*}")] which will need to change to use a ASP.NET Core's Routing prefix, e.g:

[Route("/wildcard/{*Path}")]
[Route("/wildcard/{**Path}")]
public class GetFile : IGet, IReturn<byte[]>
{
    public string Path { get; set; }
}

ServiceStack Routing Compatibility

To improve compatibility with ASP.NET Core's Routing, ServiceStack's Routing (when not using Endpoint Routing) now supports parsing ASP.NET Core's Route Constraints but as they're inert you would need to continue to use Custom Route Rules to distinguish between different routes matching the same path at different specificity:

[Route("/users/{Id:int}", Matches = "**/{int}")]
[Route("/users/{UserName:string}")]
public class GetUser : IGet, IReturn<User>
{
    public int? Id { get; set; }
    public int? UserName { get; set; }
}

It also supports defining Wildcard Routes using ASP.NET Core's syntax which we now recommend using instead for compatibility when switching to use Endpoint Routing:

[Route("/wildcard/{*Path}")]
[Route("/wildcard/{**Path}")]
public class GetFile : IGet, IReturn<byte[]>
{
    public string Path { get; set; }
}

Primary HTTP Method

Another difference is that an API will only register its Endpoint Route for its primary HTTP Method, if you want an API to be registered for multiple HTTP Methods you can specify them in the Route attribute, e.g:

[Route("/users/{Id:int}", "GET,POST")]
public class GetUser : IGet, IReturn<User>
{
    public required int Id { get; set; }
}

As such we recommend using the IVerb IGet, IPost, IPut, IPatch, IDelete interface markers to specify the primary HTTP Method for an API. This isn't needed for AutoQuery Services which are implicitly configured to use their optimal HTTP Method.

If no HTTP Method is specified, the Primary HTTP Method defaults to HTTP POST.

Authorization

Using Endpoint Routing also means ServiceStack's APIs are authorized the same way, where ServiceStack's Declarative Validation attributes are converted into ASP.NET Core's [Authorize] attribute to secure the endpoint:

[ValidateIsAuthenticated]
[ValidateIsAdmin]
[ValidateHasRole(role)]
[ValidateHasClaim(type,value)]
[ValidateHasScope(scope)]
public class Secured {}

Authorize Attribute on ServiceStack APIs

Alternatively you can now use ASP.NET Core's [Authorize] attribute directly to secure ServiceStack APIs should you need more fine-grained Authorization:

[Authorize(Roles = "RequiredRole")]
[Authorize(Policy = "RequiredPolicy")]
[Authorize(AuthenticationSchemes = "Identity.Application,Bearer")]
public class Secured {}

Configuring Authentication Schemes

ServiceStack will default to using the major Authentication Schemes configured for your App to secure the APIs endpoint with, this can be overridden to specify which Authentication Schemes to use to restrict ServiceStack APIs by default, e.g:

app.UseServiceStack(new AppHost(), options => {
    options.AuthenticationSchemes = "Identity.Application,Bearer";
    options.MapEndpoints();
});

Hidden ServiceStack Endpoints

Whilst ServiceStack Requests are registered and executed as endpoints, most of them are marked with builder.ExcludeFromDescription() to hide them from polluting metadata and API Explorers like Swagger UI and API Explorer.

To also hide your ServiceStack APIs you can use [ExcludeMetadata] attribute to hide them from all metadata services or use [Exclude(Feature.ApiExplorer)] to just hide them from API Explorer UIs:

[ExcludeMetadata]
[Exclude(Feature.ApiExplorer)]
public class HiddenRequest {}

Content Negotiation

An example of these hidden routes is the support for invoking and returning ServiceStack APIs in different Content Types via hidden Endpoint Routes mapped with the format /api/{Request}.{format}, e.g:

Query String Format

That continues to support specifying the Mime Type via the ?format query string, e.g:

Predefined Routes

Endpoints are only created for the newer /api/{Request} pre-defined routes, which should be easier to use with less conflicts now that ServiceStack APIs are executed along-side other endpoint routes APIs which can share the same /api base path with non-conflicting routes, e.g: app.MapGet("/api/minimal-api").

As a result clients configured to use the older /json/reply/{Request} pre-defined route will need to be configured to use the newer /api base path.

No change is required for C#/.NET clients using the recommended JsonApiClient JSON Service Client which is already configured to use the newer /api base path.

var client = new JsonApiClient(baseUri);

Older .NET clients can be configured to use the newer /api pre-defined routes with:

var client = new JsonServiceClient(baseUri) {
    UseBasePath = "/api"
};
var client = new JsonHttpClient(baseUri) {
    UseBasePath = "/api"
};

To further solidify that /api as the preferred pre-defined route we've also updated all generic service clients of other languages to use /api base path by default:

JavaScript/TypeScript

const client = new JsonServiceClient(baseUrl)

Java/Kotlin

JsonServiceClient client = new JsonServiceClient(baseUrl);

Python

client = JsonServiceClient(baseUrl)

PHP

$client = new JsonServiceClient(baseUrl);

Dart

var client = ClientFactory.api(baseUrl);

Revert to Legacy Predefined Routes

You can unset the base path to revert back to using the older /json/reply/{Request} pre-defined route, e.g:

JavaScript/TypeScript

client.basePath = null;

Java/Kotlin

client.setBasePath();

Python

client.set_base_path()

PHP

$client->setBasePath();

Dart

var client = ClientFactory.create(baseUrl);

Customize Endpoint Mapping

You can register a RouteHandlerBuilders to customize how ServiceStack APIs endpoints are registered which is also what ServiceStack uses to annotate its API endpoints to enable its new Open API v3 support:

options.RouteHandlerBuilders.Add((builder, operation, method, route) =>
{
    builder.WithOpenApi(op => { ... });
});

Endpoint Routing Compatibility Levels

The default behavior of MapEndpoints() is the strictest and recommended configuration that we want future ServiceStack Apps to use, however if you're migrating existing App's you may want to relax these defaults to improve compatibility with existing behavior. The configurable defaults for mapping endpoints are:

app.UseServiceStack(new AppHost(), options => {
    options.MapEndpoints(use:true, force:true, useSystemJson:UseSystemJson.Always);
});
  • use - Whether to use registered endpoints for executing ServiceStack APIs
  • force - Whether to only allow APIs to be executed through endpoints
  • useSystemJson - Whether to use System.Text.Json for JSON API Serialization

So you could for instance register endpoints and not use them, where they'll be visible in endpoint API explorers like Swagger UI but continue to execute in ServiceStack's Request Pipeline.

force disables fallback execution of ServiceStack Requests through ServiceStack's Request Pipeline for requests that don't match registered endpoints. You may need to disable this if you have existing clients calling ServiceStack APIs through multiple HTTP Methods, as only the primary HTTP Method is registered as an endpoint.

When enabled force ensures the only ServiceStack Requests that are not executed through registered endpoints are IAppHost.CatchAllHandlers and IAppHost.FallbackHandler handlers.

useSystemJson is a new feature that lets you specify when to use System.Text.Json for JSON API Serialization, which is our next exciting feature which enables your App to standardize on using ASP.NET Core's fast async UTF8 JSON Serializer.

System.Text.Json

The last integration piece we needed to support is utilizing System.Text.Json - the default high-performance async JSON serializer used in .NET Applications, can now be used by ServiceStack APIs to serialize and deserialize its JSON API Responses that's enabled by default when using Endpoint Routing.

This integrates ServiceStack APIs more than ever where just like Minimal APIs and Web API, uses ASP.NET Core's IOC to resolve dependencies, uses Endpoint Routing to Execute APIs that's secured with ASP.NET Core Identity Auth then uses System.Text.Json to deserialize and serialize its JSON payloads.

Enabled by Default when using Endpoint Routing

app.UseServiceStack(new AppHost(), options => {
    options.MapEndpoints();
});

Enhanced System.Text.Json

ServiceStack uses a custom JsonSerializerOptions to improve compatibility with existing ServiceStack DTOs and ServiceStack's rich ecosystem of generic Add ServiceStack Reference Service Clients, which is configured to:

  • Uses CamelCaseNamingPolicy for property names
  • Supports Case Insensitive Properties
  • Not serialize null properties
  • Serializes TimeSpan and TimeOnly Data Types with XML Schema Time format
  • Supports [DataContract] annotations
  • Supports Custom Enum Serialization

Support for DataContract Annotations

Support for .NET's DataContract serialization attributes was added using a custom TypeInfoResolver, specifically it supports:

  • [DataContract] - When annotated, only [DataMember] properties are serialized
  • [DataMember] - Specify a custom Name or Order of properties
  • [IgnoreDataMember] - Ignore properties from serialization
  • [EnumMember] - Specify a custom value for Enum values

Custom Enum Serialization

Below is a good demonstration of the custom Enum serialization support which matches ServiceStack.Text's behavior:

public enum EnumType { Value1, Value2, Value3 }

[Flags]
public enum EnumTypeFlags { Value1, Value2, Value3 }

public enum EnumStyleMembers
{
    [EnumMember(Value = "lower")]
    Lower,
    [EnumMember(Value = "UPPER")]
    Upper,
}

return new EnumExamples {
    EnumProp = EnumType.Value2, // String value by default
    EnumFlags = EnumTypeFlags.Value2 | EnumTypeFlags.Value3, // [Flags] as int
    EnumStyleMembers = EnumStyleMembers.Upper, // Serializes [EnumMember] value
    NullableEnumProp = null, // Ignores nullable enums
};

Which serializes to:

{
  "enumProp": "Value2",
  "enumFlags": 3,
  "enumStyleMembers": "UPPER"
}

Custom Configuration

You can further customize the JsonSerializerOptions used by ServiceStack by using ConfigureJsonOptions() to add any customizations that you can optionally apply to ASP.NET Core's JSON APIs and MVC with:

builder.Services.ConfigureJsonOptions(options => {
    options.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower;
})
.ApplyToApiJsonOptions()  // Apply to ASP.NET Core's JSON APIs
.ApplyToMvcJsonOptions(); // Apply to MVC

Control over when and where System.Text.Json is used

Whilst System.Text.Json is highly efficient, it's also very strict in the inputs it accepts where you may want to revert back to using ServiceStack's JSON Serializer for specific APIs, esp. when needing to support non-updatable external clients.

This can be done by annotating Request DTOs with [SystemJson] attribute, specifying when to use System.Text.Json, e.g: you can limit to only use it for serializing an APIs Response with:

[SystemJson(UseSystemJson.Response)]
public class CreateUser : IReturn<IdResponse>
{
    //...
}

Or limit to only use System.Text.Json for deserializing an APIs Request with:

[SystemJson(UseSystemJson.Request)]
public class CreateUser : IReturn<IdResponse>
{
    //...
}

Or not use System.Text.Json at all for an API with:

[SystemJson(UseSystemJson.Never)]
public class CreateUser : IReturn<IdResponse>
{
    //...
}

JsonApiClient Support

When Endpoints Routing is configured, the JsonApiClient will also be configured to utilize the same System.Text.Json options to send and receive its JSON API Requests which also respects the [SystemJson] specified behavior.

Clients external to the .NET App can be configured to use System.Text.Json with:

ClientConfig.UseSystemJson = UseSystemJson.Always;

Whilst any custom configuration can be applied to its JsonSerializerOptions with:

TextConfig.ConfigureSystemJsonOptions(options => {
    options.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower;
});

Scoped JSON Configuration

We've also added partial support for Customized JSON Responses for the following customization options:

Name Alias
EmitCamelCaseNames eccn
EmitLowercaseUnderscoreNames elun
EmitPascalCaseNames epcn
ExcludeDefaultValues edv
IncludeNullValues inv
Indent pp

These can be applied to the JSON Response by returning a decorated HttpResult with a custom ResultScope, e.g:

return new HttpResult(responseDto) {
    ResultScope = () => 
        JsConfig.With(new() { IncludeNullValues = true, ExcludeDefaultValues = true })
};

They can also be requested by API consumers by adding a ?jsconfig query string with the desired option or alias, e.g:

/api/MyRequest?jsconfig=EmitLowercaseUnderscoreNames,ExcludeDefaultValues
/api/MyRequest?jsconfig=eccn,edv

SystemJsonCompatible

Another configuration automatically applied when System.Text.Json is enabled is:

JsConfig.SystemJsonCompatible = true;

Which is being used to make ServiceStack's JSON Serializer more compatible with System.Text.Json output so it's easier to switch between the two with minimal effort and incompatibility. Currently this is only used to override DateTime and DateTimeOffset behavior which uses System.Text.Json for its Serialization/Deserialization.

OpenAPI v3

Utilizing the same ASP.NET Core Endpoints that the rest of the ASP.NET Core App uses enables your ServiceStack APIs to integrate with your wider ASP.NET Core application, opening up more opportunities for reuse of your ServiceStack APIs.

This opens up the ability to use common third party tooling. A good example of this is adding OpenAPI v3 specification generation for your endpoints offered by the Swashbuckle.AspNetCore package.

Also included in this Release is the ServiceStack.AspNetCore.OpenApi package to make this integration as easy as possible, and incorporate additional information from your ServiceStack APIs into Swagger metadata.

Previously, without the ability to map Endpoints, we've maintained a ServiceStack specific OpenAPI specification generation via the OpenApiFeature plugin. While this provided a lot of functionality by accurately describing your ServiceStack APIs, it could be tricky to customize those API descriptions to the way some users wanted to.

In this post we will look at how you can take advantage of the new OpenAPI v3 Swagger support using mapped Endpoints, customizing the generated specification, as well as touch on other related changes to ServiceStack v8.1.

AppHost Initialization

To use ServiceStack APIs as mapped Endpoints, the way ServiceStack is initialized in . To convert your App to use Endpoint Routing and ASP.NET Core IOC your ASPNET Core application needs to be updated to replace any usage of Funq IoC container to use ASP.NET Core's IOC.

Previously, the following was used to initialize your ServiceStack AppHost:

Program.cs

app.UseServiceStack(new AppHost());

The app in this example is a WebApplication resulting from an IHostApplicationBuilder calling builder.Build().

Whilst we still need to call app.UseServiceStack(), we also need to move the discovery of your ServiceStack APIs to earlier in the setup before the WebApplication is built, e.g:

// Register ServiceStack APIs, Dependencies and Plugins:
services.AddServiceStack(typeof(MyServices).Assembly);

var app = builder.Build();
//...

// Register ServiceStack AppHost
app.UseServiceStack(new AppHost(), options => {
    options.MapEndpoints();
});

app.Run();

Once configured to use Endpoint Routing we can the mix tool to apply the openapi3 Startup Configuration with:

x mix openapi3

Manually Configure OpenAPI v3 and Swagger UI

This will install the required ASP.NET Core Microsoft, Swashbuckle and ServiceStack Open API NuGet packages:

<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.*" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.*" />
<PackageReference Include="ServiceStack.AspNetCore.OpenApi" Version="8.*" />

Then add the Configure.OpenApi.cs Modular Startup class to your project:

[assembly: HostingStartup(typeof(MyApp.ConfigureOpenApi))]

namespace MyApp;

public class ConfigureOpenApi : IHostingStartup
{
    public void Configure(IWebHostBuilder builder) => builder
        .ConfigureServices((context, services) =>
        {
            if (context.HostingEnvironment.IsDevelopment())
            {
                services.AddEndpointsApiExplorer();
                services.AddSwaggerGen(); // Swashbuckle

                services.AddServiceStackSwagger();
                services.AddBasicAuth<ApplicationUser>(); // Enable HTTP Basic Auth
                //services.AddJwtAuth(); // Enable & Use JWT Auth

                services.AddTransient<IStartupFilter, StartupFilter>();
            }
        });

    public class StartupFilter : IStartupFilter
    {
        public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
            => app => {
                // Provided by Swashbuckle library
                app.UseSwagger();
                app.UseSwaggerUI();
                next(app);
            };
    }
}

All this setup is done for you in our updated Identity Auth .NET 8 Templates.

More Control

One point of friction with our previous OpenApiFeature plugin was the missing customization ability to the OpenAPI spec to somewhat disconnect from the defined ServiceStack service, and related C# Request and Response DTOs since it used Request DTOs property attributes making the structure of the OpenAPI schema mapping quite ridged, preventing the ability for certain customizations.

For example, if we have an UpdateTodo Request DTO that looks like the following class:

[Route("/todos/{Id}", "PUT")]
public class UpdateTodo : IPut, IReturn<Todo>
{
    public long Id { get; set; }
    [ValidateNotEmpty]
    public string Text { get; set; }
    public bool IsFinished { get; set; }
}

Previously, we would get a default Swagger UI that enabled all the properties as Paramters to populate:

While this correctly describes the Request DTO structure, sometimes as developers we get requirements for how we want to present our APIs to our users from within the Swagger UI.

With the updated SwaggerUI, and the use of the Swashbuckle library, we get the following UI by default:

These are essentially the same, we have a CRUD Todo API that takes a UpdateTodo Request DTO, and returns a Todo Response DTO. ServiceStack needs to have uniquely named Request DTOs, so we can't have a Todo schema as the Request DTO despite the fact that it is the same structure as our Todo model. This is a good thing, as it allows us to have a clean API contract, and separation of concerns between our Request DTOs and our models. However, it might not be desired to present this to our users, since it can be convenient to think about CRUD services as taking the same resource type as the response.

To achieve this, we use the Swashbuckle library to customize the OpenAPI spec generation. Depending on what you want to customize, you can use the SchemaFilter or OperationFilter options. In this case, we want to customize the matching operation to reference the Todo schema for the Request Body.

First, we create a new class that implements the IOperationFilter interface.

public class OperationRenameFilter : IOperationFilter
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        if (context.ApiDescription.HttpMethod == "PUT" &&
            context.ApiDescription.RelativePath == "todos/{Id}")
        {
            operation.RequestBody.Content["application/json"].Schema.Reference = 
                new OpenApiReference {
                    Type = ReferenceType.Schema,
                    Id = "Todo"
                };
        }
    }
}

The above matches some information about the UpdateTodo request we want to customize, and then sets the Reference property of the RequestBody to the Todo schema. We can then add this to the AddSwaggerGen options in the Program.cs file.

builder.Services.AddSwaggerGen(o =>
{
    o.OperationFilter<OperationRenameFilter>();
});

The result is the following Swagger UI:

This is just one simple example of how you can customize the OpenAPI spec generation, and Swashbuckle has some great documentation on the different ways you can customize the generated spec. And these customizations impact any of your ASP.NET Core Endpoints, not just your ServiceStack APIs.

Now that ServiceStack APIs can be mapped to standard ASP.NET Core Endpoints, it opens up a lot of possibilities for integrating your ServiceStack APIs into the larger ASP.NET Core ecosystem.

ASP.NET Core Identity Auth Admin UI

Now that ServiceStack is deeply integrated into ASP.NET Core Apps, we're back to refocusing our efforts on adding value-added features that can benefit all .NET Core Apps.

The new Identity Auth Admin UI is an example of this, which can be enabled when registering the AuthFeature Plugin:

public class ConfigureAuth : IHostingStartup
{
    public void Configure(IWebHostBuilder builder) => builder
        .ConfigureServices(services => {
            services.AddPlugin(new AuthFeature(IdentityAuth.For<ApplicationUser>(
                options => {
                    options.SessionFactory = () => new CustomUserSession();
                    options.CredentialsAuth();
                    options.AdminUsersFeature();
                })));
        });
}

Which just like the ServiceStack Auth Admin Users UI enables a Admin UI that's only accessible to Admin Users for managing Identity Auth users at /admin-ui/users.

Which displays a limited view of a User's info due to the minimal properties on the default IdentityAuth model:

These User Search results are customizable by specifying the ApplicationUser properties you want displayed instead:

options.AdminUsersFeature(feature =>
{
    feature.QueryIdentityUserProperties =
    [
        nameof(ApplicationUser.Id),
        nameof(ApplicationUser.DisplayName),
        nameof(ApplicationUser.Email),
        nameof(ApplicationUser.UserName),
        nameof(ApplicationUser.LockoutEnd),
    ];
});

The default display Order of Users is also customizable:

feature.DefaultOrderBy = nameof(ApplicationUser.DisplayName);

As well as the Search behavior which can be replaced to search any custom fields, e.g:

feature.SearchUsersFilter = (q, query) =>
{
    var queryUpper = query.ToUpper();
    return q.Where(x =>
        x.DisplayName!.Contains(query) ||
        x.Id.Contains(queryUpper) ||
        x.NormalizedUserName!.Contains(queryUpper) ||
        x.NormalizedEmail!.Contains(queryUpper));
};

The default Create and Edit Admin Users UI are also limited to editing the minimal IdentityAuth properties:

Whilst the Edit page includes standard features to lockout users, change user passwords and manage their roles:

By default Users are locked out indefinitely, but this can also be changed to lock users out to a specific date, e.g:

feature.ResolveLockoutDate = user => DateTimeOffset.Now.AddDays(7);

The forms editable fields can also be customized to include additional properties, e.g:

feature.FormLayout =
[
    Input.For<ApplicationUser>(x => x.UserName, c => c.FieldsPerRow(2)),
    Input.For<ApplicationUser>(x => x.Email, c => { 
        c.Type = Input.Types.Email;
        c.FieldsPerRow(2); 
    }),
    Input.For<ApplicationUser>(x => x.FirstName, c => c.FieldsPerRow(2)),
    Input.For<ApplicationUser>(x => x.LastName, c => c.FieldsPerRow(2)),
    Input.For<ApplicationUser>(x => x.DisplayName, c => c.FieldsPerRow(2)),
    Input.For<ApplicationUser>(x => x.PhoneNumber, c =>
    {
        c.Type = Input.Types.Tel;
        c.FieldsPerRow(2); 
    }),
];

That can override the new ApplicationUser Model that's created and any Validation:

feature.CreateUser = () => new ApplicationUser { EmailConfirmed = true };
feature.CreateUserValidation = async (req, createUser) =>
{
    await IdentityAdminUsers.ValidateCreateUserAsync(req, createUser);
    var displayName = createUser.GetUserProperty(nameof(ApplicationUser.DisplayName));
    if (string.IsNullOrEmpty(displayName))
        throw new ArgumentNullException(nameof(AdminUserBase.DisplayName));
    return null;
};

Admin User Events

Should you need to, Admin User Events can use used to execute custom logic before and after creating, updating and deleting users, e.g:

feature.OnBeforeCreateUser = (request, user) => { ... };
feature.OnAfterCreateUser  = (request, user) => { ... };
feature.OnBeforeUpdateUser = (request, user) => { ... };
feature.OnAfterUpdateUser  = (request, user) => { ... };
feature.OnBeforeDeleteUser = (request, userId) => { ... };
feature.OnAfterDeleteUser  = (request, userId) => { ... };

JWT Identity Auth

JWTs enable stateless authentication of clients without servers needing to maintain any Auth state in server infrastructure or perform any I/O to validate a token. As such, JWTs are a popular choice for Microservices as they only need to configured with confidential keys to validate access.

ASP.NET Core JWT Authentication

ServiceStack's JWT Identity Auth reimplements many of the existing ServiceStack JWT AuthProvider features but instead of its own implementation, integrates with and utilizes ASP.NET Core's built-in JWT Authentication that's configurable in .NET Apps with the .AddJwtBearer() extension method, e.g:

Program.cs

services.AddAuthentication()
    .AddJwtBearer(options => {
        options.TokenValidationParameters = new()
        {
            ValidIssuer = config["JwtBearer:ValidIssuer"],
            ValidAudience = config["JwtBearer:ValidAudience"],
            IssuerSigningKey = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(config["JwtBearer:IssuerSigningKey"]!)),
            ValidateIssuerSigningKey = true,
        };
    })
    .AddIdentityCookies(options => options.DisableRedirectsForApis());

Then use the JwtAuth() method to enable and configure ServiceStack's support for ASP.NET Core JWT Identity Auth:

Configure.Auth.cs

public class ConfigureAuth : IHostingStartup
{
    public void Configure(IWebHostBuilder builder) => builder
        .ConfigureServices(services => {
            services.AddPlugin(new AuthFeature(IdentityAuth.For<ApplicationUser>(
                options => {
                    options.SessionFactory = () => new CustomUserSession();
                    options.CredentialsAuth();
                    options.JwtAuth(x => {
                        // Enable JWT Auth Features...
                    });
                })));
        });
}

Enable in Swagger UI

Once configured we can enable JWT Auth in Swagger UI by installing Swashbuckle.AspNetCore:

<PackageReference Include="Swashbuckle.AspNetCore" Version="6.*" />

Then enable Open API, Swagger UI, ServiceStack's support for Swagger UI and the JWT Bearer Auth option:

public class ConfigureOpenApi : IHostingStartup
{
    public void Configure(IWebHostBuilder builder) => builder
        .ConfigureServices((context, services) => {
            if (context.HostingEnvironment.IsDevelopment())
            {
                services.AddEndpointsApiExplorer();
                services.AddSwaggerGen();
                services.AddServiceStackSwagger();
                services.AddJwtAuth();
                //services.AddBasicAuth<Data.ApplicationUser>();
            
                services.AddTransient<IStartupFilter,StartupFilter>();
            }
        });

    public class StartupFilter : IStartupFilter
    {
        public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next) => app =>
        {
            app.UseSwagger();
            app.UseSwaggerUI();
            next(app);
        };
    }
}

This will enable the Authorize button in Swagger UI where you can authenticate with a JWT Token:

JWT Auth in Built-in UIs

This also enables the JWT Auth Option in ServiceStack's built-in API Explorer, Locode and Admin UIs:

Authenticating with JWT

JWT Identity Auth is a drop-in replacement for ServiceStack's JWT AuthProvider where Authenticating via Credentials will convert the Authenticated User into a JWT Bearer Token returned in the HttpOnly, Secure ss-tok Cookie that will be used to Authenticate the client:

var client = new JsonApiClient(BaseUrl);
await client.SendAsync(new Authenticate {
    provider = "credentials",
    UserName = Username,
    Password = Password,
});

var bearerToken = client.GetTokenCookie(); // ss-tok Cookie

JWT Refresh Tokens

Refresh Tokens can be used to allow users to request a new JWT Access Token when the current one expires.

To enable support for JWT Refresh Tokens your IdentityUser model should implement the IRequireRefreshToken interface which will be used to store the 64 byte Base64 URL-safe RefreshToken and its RefreshTokenExpiry in its persisted properties:

public class ApplicationUser : IdentityUser, IRequireRefreshToken
{
    //...
    public string? RefreshToken { get; set; }
    public DateTime? RefreshTokenExpiry { get; set; }
}

Now after successful authentication, the RefreshToken will also be returned in the ss-reftok Cookie:

var refreshToken = client.GetRefreshTokenCookie(); // ss-reftok Cookie

Transparent Server Auto Refresh of JWT Tokens

To be able to terminate a users access, Users need to revalidate their eligibility to verify they're still allowed access (e.g. deny Locked out users). This JWT revalidation pattern is implemented using Refresh Tokens which are used to request revalidation of their access and reissuing a new JWT Access Token which can be used to make authenticated requests until it expires.

As Cookies are used to return Bearer and Refresh Tokens ServiceStack is able to implement the revalidation logic on the server where it transparently validates Refresh Tokens, and if a User is eligible will reissue a new JWT Token Cookie that replaces the expired Access Token Cookie.

Thanks to this behavior HTTP Clients will be able to Authenticate with just the Refresh Token, which will transparently reissue a new JWT Access Token Cookie and then continue to perform the Authenticated Request:

var client = new JsonApiClient(BaseUrl);
client.SetRefreshTokenCookie(RefreshToken);

var response = await client.SendAsync(new Secured { ... });

There's also opt-in sliding support for extending a User's RefreshToken after usage which allows Users to treat their Refresh Token like an API Key where it will continue extending whilst they're continuously using it to make API requests, otherwise expires if they stop. How long to extend the expiry of Refresh Tokens after usage can be configured with:

options.JwtAuth(x => {
    // How long to extend the expiry of Refresh Tokens after usage (default None)
    x.ExtendRefreshTokenExpiryAfterUsage = TimeSpan.FromDays(90);
});

Convert Session to Token Service

Another useful Service that's available is being able to Convert your current Authenticated Session into a Token with the ConvertSessionToToken Service which can be enabled with:

options.JwtAuth(x => {
    x.IncludeConvertSessionToTokenService = true;
});

This can be useful for when you want to Authenticate via an external OAuth Provider that you then want to convert into a stateless JWT Token by calling the ConvertSessionToToken on the client, e.g:

.NET Clients

await client.SendAsync(new ConvertSessionToToken());

TypeScript/JavaScript

fetch('/session-to-token', { method:'POST', credentials:'include' })

The default behavior of ConvertSessionToToken is to remove the Current Session from the Auth Server which will prevent access to protected Services using our previously Authenticated Session. If you still want to preserve your existing Session you can indicate this with:

await client.SendAsync(new ConvertSessionToToken { 
    PreserveSession = true 
});

JWT Options

Other configuration options available for Identity JWT Auth include:

options.JwtAuth(x => {
    // How long should JWT Tokens be valid for. (default 14 days)
    x.ExpireTokensIn = TimeSpan.FromDays(14);
    
    // How long should JWT Refresh Tokens be valid for. (default 90 days)
    x.ExpireRefreshTokensIn = TimeSpan.FromDays(90);
    
    x.OnTokenCreated = (req, user, claims) => {
        // Customize which claims are included in the JWT Token
    };
    
    // Whether to invalidate Refresh Tokens on Logout (default true)
    x.InvalidateRefreshTokenOnLogout = true;
    
    // How long to extend the expiry of Refresh Tokens after usage (default None)
    x.ExtendRefreshTokenExpiryAfterUsage = null;
});

Blazor WASM

Blazor WASM Tailwind

Create a new Blazor WASM Tailwind App

Create a new Blazor WASM Tailwind project with your preferred project name:

Since the release of .NET 8, we have been upgrading our Blazor Templates and example applications to take advantage of some of the new features, including making use of static Server Side Rendering (SSR) for Blazor, which allows for faster initial page loads and better SEO, and our blazor-wasm template uses InteractiveAuto by default to provide a more Responsive UI.

What is InteractiveAuto?

Blazor for .NET 8 has four different rendering modes you can take advantage of:

  • Static Server (static SSR)
  • Interactive Server
  • Interactive WebAssembly (WASM)
  • Interactive Auto

For non-interactive pages, the static SSR mode is the fastest, as it renders the page on the server and sends the HTML to the client. However, when your page needs to be interactive, you need to use one of the interactive modes.

Prior to .NET 8, there was a trade-off between the two available render modes (static server rendering wasn't yet available). The Interactive Server mode was faster to load, but the Interactive WASM mode was more responsive.

The initial load times for Interactive WASM could be quite slow, as the entire application and all its dependencies needed to be downloaded before the page could render most of the content.

The initial load time for the Interactive WASM mode can be quite slow even for a minimal app

Our templates previously worked around this limitation with a custom Pre-Rendering solution, as the wait times were too long for a good user experience.

.NET 8's new Interactive Auto mode provides the best of both worlds as pre-rendering is now enabled by default.

When the page is first loaded, it uses the Interactive Server mode, which is faster than Interactive WASM as it doesn't need to download WASM resources. So the user can start interacting with the page straight away, but with a slight delay for each of their interactions due to having to perform round-trips to the server for each interaction.

In the background, the WASM resources are downloaded which can then be used to render the site on the client for subsequent visits.

Using InteractiveAuto in your Blazor application

In Blazor for .NET 8, render modes can be set on both a per-page and per-component basis.

@page "/counter"
@rendermode InteractiveAuto

<Counter />
<Counter @rendermode="RenderMode.InteractiveAuto" />

ServiceStack.Blazor Components

The ServiceStack.Blazor Components have been updated for .NET 8 and work with the new InteractiveAuto render mode.

This means you can focus more on your application logic and less on the UI, as the components provide a high-productivity UI for common tasks such as CRUD operations.

AutoQueryGrid

The AutoQueryGrid component provides a full-featured data grid that can be used to display and edit data from an AutoQuery service. This is ideal for creating custom admin pages for your application. By integrating your admin screens into your application, you can optimize the user experience for specific workflows and get a huge amount of reuse of your existing AutoQuery services.

<AutoQueryGrid Model="Modifier" Apis="Apis.AutoQuery<QueryModifiers,CreateModifier,UpdateModifier,DeleteModifier>()" />

For BlazorDiffusion, our StableDiffusion example application, we used the AutoQueryGrid to create a custom admin page for managing the modifiers in the application.

This is the simplest and fastest use of the AutoQueryGrid component, but it can also be heavily customized for lots of different use cases.

In BlazorDiffusion we customize the grid to enable easy navigation contextually between separate customized admin screens for each Creative, linking to related table data.

<AutoQueryGrid @ref=@grid Model="Creative" Apis="Apis.AutoQuery<QueryCreatives,UpdateCreative,HardDeleteCreative>()"
               ConfigureQuery="ConfigureQuery">
    <EditForm>
        <div class="relative z-10" aria-labelledby="slide-over-title" role="dialog" aria-modal="true">
            <div class="pointer-events-none fixed inset-y-0 right-0 flex max-w-full pl-10 sm:pl-16">
                <CreativeEdit Creative="context" OnClose="grid.OnEditDone" />
            </div>
        </div>
    </EditForm>
    <Columns>
        <Column Title="User" Field="(Creative x) => x.OwnerId" />
        <Column Title="Id" Field="(Creative x) => x.Id" />
        <Column Field="(Creative x) => x.Modifiers">
            <Template>
                @if (context.Modifiers?.Count > 0)
                {
                <TextLink class="flex" href=@($"/admin/modifiers?Ids={string.Join(",", context.Modifiers.Select(x => x.ModifierId))}")>
                    <Icon class="w-6 h-6 mr-1" Image=@typeof(Modifier).GetIcon() />
                    @TextUtils.Pluralize("Modifier", context.Modifiers)
                </TextLink>
                }
            </Template>
        </Column>
        <Column Field="(Creative x) => x.Artists">
            <Template>
                @if (context.Artists?.Count > 0)
                {
                <TextLink class="flex" href=@($"/admin/artists?Ids={string.Join(",", context.Artists.Select(x => x.ArtistId))}")>
                    <Icon class="w-6 h-6 mr-1" Image=@typeof(Artist).GetIcon() />
                    @TextUtils.Pluralize("Artist", context.Artists)
                </TextLink>
                }
            </Template>
        </Column>
        <Column Field="(Creative x) => x.Artifacts">
            <Template>
                @if (context.Artifacts?.Count > 0)
                {
                <TextLink class="flex" href=@($"/admin/artifacts?CreativeId={context.Id}")>
                    <Icon class="w-6 h-6 mr-1" Image=@typeof(Artifact).GetIcon() />
                    @TextUtils.Pluralize("Artifact", context.Artifacts)
                </TextLink>
                }
            </Template>
        </Column>
        <Column Field="(Creative x) => x.Key" />
        <Column Field="(Creative x) => x.CreatedDate" Format="s" />
        <Column Field="(Creative x) => x.UserPrompt" />
    </Columns>
</AutoQueryGrid>

In the above example, we use the ConfigureQuery parameter to customize the query used by the AutoQueryGrid when displaying values. This is ideal if you want to filter the data for specific workflows, for example, only showing the data that is relevant to the current user.

We combine this with a Tabs component to provide a navigation bar for the user to switch between the different filters on the same AutoQueryGrid.

<Tabs TabOptions="TabOptions" TabChanged="TabChangedAsync" />

We also use the EditForm parameter to customize the edit form for the AutoQueryGrid, so the workflow for editing a Creative is optimized using your own completely custom UI.

<AutoQueryGrid @ref=@grid Model="Creative" Apis="Apis.AutoQuery<QueryCreatives,UpdateCreative,HardDeleteCreative>()"
               ConfigureQuery="ConfigureQuery">
    <EditForm>
        <div class="relative z-10" aria-labelledby="slide-over-title" role="dialog" aria-modal="true">
            <div class="pointer-events-none fixed inset-y-0 right-0 flex max-w-full pl-10 sm:pl-16">
                <CreativeEdit Creative="context" OnClose="grid.OnEditDone" />
            </div>
        </div>
    </EditForm>

Upgrading to .NET 8

BlazorDiffusion was an example application we originally developed for .NET 6. We upgraded the production release of this application to use our blazor-vue template, which can be perfect for public-facing web applications and teams that don't mind including a JavaScript framework in their application.

However, to show the flexibility of Blazor for .NET 8, we also upgraded the whole application from our updated blazor-wasm template to take advantage of the new InteractiveAuto mode.

Component Compatibility

Since the ServiceStack.Blazor library has been updated for .NET 8, we just needed to bring over the shared components from the original application and update the references to the new library.

When upgrading your application pages and components, you will need to avoid any JavaScript interop that runs during the InitializeAsync lifecycle method, as this is not supported in the InteractiveAuto mode.

Running on both Server vs Client

When using the InteractiveAuto mode, first visits will be running on the server, so your pages and components need to be available to both projects, as well as have any required dependencies registered in both projects Program.cs files.

By placing your shared pages and components in a shared project like the .Client project in the blazor-wasm template, you can easily share them between the two projects.

Look for any of your pages or components that use the @injects directive, as these will need to be registered in both projects.

INFO

Avoid sharing sensitive information via dependency injection, as this will be available to the client at runtime which will be able to be decompiled and inspected.

Source code and live demo

The source code for the upgraded BlazorDiffusionAuto application is available on GitHub and you can view a live demo of the application at auto.blazordiffusion.com.

Conclusion

The new InteractiveAuto mode in Blazor for .NET 8 provides the best of both worlds for Blazor applications. A built in pre-rendering solution means that you can have a fast initial load time, but still have a responsive UI for subsequent visits.

And since the ServiceStack.Blazor components have been updated for .NET 8, you can take advantage of the high-productivity UI components to quickly create customizable and professional-looking admin pages in a Blazor application.

Breaking Changes

In order to promote adoption of ASP.NET Core's APIs the IServiceProvider.TryResolve<T>() extension method was removed:

services.AddSingleton<ICrudEvents>(c =>
    new OrmLiteCrudEvents(c.Resolve<IDbConnectionFactory>()));

Which should be replaced with IServiceProvider's GetService<T>() and GetRequiredService<T>():

services.AddSingleton<ICrudEvents>(c =>
    new OrmLiteCrudEvents(c.GetRequiredService<IDbConnectionFactory>()));

Feedback

If you have any questions or feedback about anything in this release, please feel free to reach out to us on the Customer Forums or GitHub Discussions.