ServiceStack v5.13.2

We've cut this release cadence short to focus on putting out a quality release to take advantage of the newest and best .NET runtime yet!

In a lot of ways .NET 6 marks the start of a new era for .NET with the first LTS release after the end of the .NET Framework and .NET Standard with only 1 actively developed runtime going forward.

In alignment with .NET's future we're looking to embracing new .NET runtime features and provide better integration with .NET 6's development model so ServiceStack can be regarded as another module in an ASP .NET Core App's pipeline, allowing knowledge to be more reusably shared and to that end will be continuing to work on deeper integrations in future releases.

net6.0 TFM builds added to all packages

This release lays the foundation for the new style of Apps promoted by .NET 6.0 starting with new net6.0 TFM builds added to all active ServiceStack NuGet packages. Previously ServiceStack's ASP .NET Core functionality was delivered in .NET Standard 2.0 .dll's whose builds have served running ServiceStack on all .NET Core runtimes dating back to .NET Core 2.x.

The new net6.0 target builds has allowed us to reference the latest versions of each ASP .NET Core dependency we use, including the recommendation of referencing the Microsoft.AspNetCore.App <FrameworkReference/> bundled with each .NET installation, a good example of this is seen in ServiceStack.csproj:

<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
    <PackageReference Include="System.Memory" Version="4.5.4" />
    <PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
    <PackageReference Include="Microsoft.Extensions.Primitives" Version="2.2.0" />
    <PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
    <PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
    <PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.2.0" />
    <PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" />
    <PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
    <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.2.0" />
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
    <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.2.0" />
    <PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="2.2.0" />
    <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="2.2.4" />
    <PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="2.2.0" />
    <PackageReference Include="System.Threading.Thread" Version="4.3.0" />
    <PackageReference Include="System.Reflection.Emit" Version="4.7.0" />
    <PackageReference Include="System.Linq.Queryable" Version="4.3.0" />
    <PackageReference Include="System.Drawing.Common" Version="5.0.2" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net6.0' ">
    <FrameworkReference Include="Microsoft.AspNetCore.App" />
    <PackageReference Include="System.Memory" Version="4.5.4" />
    <PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
    <PackageReference Include="System.Threading.Thread" Version="4.3.0" />
    <PackageReference Include="System.Reflection.Emit" Version="4.7.0" />
    <PackageReference Include="System.Linq.Queryable" Version="4.3.0" />
    <PackageReference Include="System.Drawing.Common" Version="5.0.2" />
</ItemGroup>

.NET 6 new Hosting Model

One of the most user visible features in .NET 6 is its new WebApplication Hosting Model which unifies existing Program.cs and Startup.cs classes and takes advantage of C# 9's Top-level statements where a minimal .NET 6 Hello World Web Application can now be as small as:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World");

app.Run();

As with most new radical features the contrast can be initially jarring, with one of the initial criticisms being that it lacks the forced structure of the Startup and encourages App's to mix all features into a single class.

Whilst we agree it's bad practice for large Apps to maintain all their configuration in a single file, we also believe .NET 6's new Hosting Model offers a simpler App configuration as now the Program.cs for most new net6.0 ServiceStack Project Templates looks like:

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
    app.UseHttpsRedirection();
}

app.Run();

That's because now all ServiceStack's features are loaded using .NET's HostingStartup, including ServiceStack's AppHost itself that's now being configured in Configure.AppHost.cs, e.g:

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

namespace MyApp;

public class AppHost : AppHostBase, IHostingStartup
{
    public void Configure(IWebHostBuilder builder) => builder
        .ConfigureServices(services => {
            // Configure ASP .NET Core IOC Dependencies
        })
        .Configure(app => {
            // Configure ASP .NET Core App
            if (!HasInit)
                app.UseServiceStack(new AppHost());
        });

    public AppHost() : base("MyApp", typeof(MyServices).Assembly) {}

    public override void Configure(Container container)
    {
        // Configure ServiceStack only IOC, Config & Plugins
        SetConfig(new HostConfig {
            UseSameSiteCookies = true,
        });
    }
}

Whilst ServiceStack's AppHost remains the same, we're now recommending that it's registered using .NET's familiar HostingStartup so that all configuration pertaining to different features are encapsulated together allowing them to be more easily updated or replaced, e.g. each feature could be temporarily disabled by commenting out its assembly HostingStartup's attribute, including ServiceStack itself:

//[assembly: HostingStartup(typeof(MyApp.AppHost))]

INFO

Reason for only conditionally registering ServiceStack with if (!HasInit) is to allow other plugins (like Auth) the opportunity to precisely control where ServiceStack is registered within its preferred ASP .NET Core's pipeline

New Modular Startup

Our Modular Startup approach has encouraged the modular configuration of ServiceStack ASP .NET Core Apps using feature encapsulated configuration blocks for a number of years. This has enabled ServiceStack Apps to be easily composed with just the features developers need, either at project creation with servicestack.net/start page or after a project's creation where features can easily be added and removed using the command-line mix tool.

We're now embracing .NET 6's idiom and have rewritten all our mix gist config files to adopt its HostingStartup which is better able to load modular Startup configuration without assembly scanning.

This is a standard ASP .NET Core feature that we can use to configure Mongo DB in any ASP .NET Core App with:

$ x mix mongodb

Which adds the mongodb gist file contents to your ASP .NET Core Host project:

using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using MongoDB.Driver;

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

namespace MyApp
{
    public class ConfigureMongoDb : IHostingStartup
    {
        public void Configure(IWebHostBuilder builder) => builder
            .ConfigureServices((context, services) => {
                var mongoClient = new MongoClient();
                IMongoDatabase mongoDatabase = mongoClient.GetDatabase("MyApp");
                services.AddSingleton(mongoDatabase);
            });
    }    
}

As it's not a ServiceStack feature it can be used to configure ASP .NET Core Apps with any feature, e.g. we could also easily configure Marten in an ASP .NET Core App with:

$ x mix marten

The benefit of this approach is entire modules of features can be configured in a single command, e.g. An empty ServiceStack App can be configured with MongoDB, ServiceStack Auth and a MongoDB Auth Repository with a single command:

$ x mix auth auth-mongodb mongodb

Likewise should you wish, you can replace MongoDB with a completely different PostgreSQL RDBMS implementation by running:

$ x mix auth auth-db postgres

ConfigureAppHost

Looking deeper, we can see where we're plugins are able to configure ServiceStack via the .ConfigureAppHost() extension method:

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

namespace MyApp
{
    // Add any additional metadata properties you want to store in the Users Typed Session
    public class CustomUserSession : AuthUserSession
    {
    }
    
    // Custom Validator to add custom validators to built-in /register Service requiring DisplayName and ConfirmPassword
    public class CustomRegistrationValidator : RegistrationValidator
    {
        public CustomRegistrationValidator()
        {
            RuleSet(ApplyTo.Post, () =>
            {
                RuleFor(x => x.DisplayName).NotEmpty();
                RuleFor(x => x.ConfirmPassword).NotEmpty();
            });
        }
    }

    public class ConfigureAuth : IHostingStartup
    {
        public void Configure(IWebHostBuilder builder) => builder
            .ConfigureServices(services => {
                //services.AddSingleton<ICacheClient>(new MemoryCacheClient()); //Store User Sessions in Memory Cache (default)
            })
            .ConfigureAppHost(appHost => {
                var appSettings = appHost.AppSettings;
                appHost.Plugins.Add(new AuthFeature(() => new CustomUserSession(),
                    new IAuthProvider[] {
                        new CredentialsAuthProvider(appSettings),     /* Sign In with Username / Password credentials */
                        new FacebookAuthProvider(appSettings),        /* Create App https://developers.facebook.com/apps */
                        new GoogleAuthProvider(appSettings),          /* Create App https://console.developers.google.com/apis/credentials */
                        new MicrosoftGraphAuthProvider(appSettings),  /* Create App https://apps.dev.microsoft.com */
                    }));

                appHost.Plugins.Add(new RegistrationFeature()); //Enable /register Service

                //override the default registration validation with your own custom implementation
                appHost.RegisterAs<CustomRegistrationValidator, IValidator<Register>>();
            });
    }
}

By default any AppHost configuration is called before AppHost.Configure() is run, the AppHost can be further customized after it's run:

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

namespace MyApp
{
    // Custom User Table with extended Metadata properties
    public class AppUser : UserAuth
    {
        public string ProfileUrl { get; set; }
        public string LastLoginIp { get; set; }
        public DateTime? LastLoginDate { get; set; }
    }

    public class AppUserAuthEvents : AuthEvents
    {
        public override void OnAuthenticated(IRequest req, IAuthSession session, IServiceBase authService, 
            IAuthTokens tokens, Dictionary<string, string> authInfo)
        {
            var authRepo = HostContext.AppHost.GetAuthRepository(req);
            using (authRepo as IDisposable)
            {
                var userAuth = (AppUser)authRepo.GetUserAuth(session.UserAuthId);
                userAuth.ProfileUrl = session.GetProfileUrl();
                userAuth.LastLoginIp = req.UserHostAddress;
                userAuth.LastLoginDate = DateTime.UtcNow;
                authRepo.SaveUserAuth(userAuth);
            }
        }
    }

    public class ConfigureAuthRepository : IHostingStartup
    {
        public void Configure(IWebHostBuilder builder) => builder
            .ConfigureServices(services => services.AddSingleton<IAuthRepository>(c =>
                new OrmLiteAuthRepository<AppUser, UserAuthDetails>(c.Resolve<IDbConnectionFactory>()) {
                    UseDistinctRoleTables = true
                }))
            .ConfigureAppHost(appHost => {
                var authRepo = appHost.Resolve<IAuthRepository>();
                authRepo.InitSchema();
                // CreateUser(authRepo, "admin@email.com", "Admin User", "p@55wOrd", roles:new[]{ RoleNames.Admin });
            }, afterConfigure: appHost => 
                appHost.AssertPlugin<AuthFeature>().AuthEvents.Add(new AppUserAuthEvents()));

        // Add initial Users to the configured Auth Repository
        public void CreateUser(IAuthRepository authRepo, string email, string name, string password, string[] roles)
        {
            if (authRepo.GetUserAuthByUserName(email) == null)
            {
                var newAdmin = new AppUser { Email = email, DisplayName = name };
                var user = authRepo.CreateUserAuth(newAdmin, password);
                authRepo.AssignRoles(user, roles);
            }
        }
    }
}

Customize AppHost at different Startup Lifecycles

To cater for all plugins, AppHost configurations can be registered at different stages within the AppHost's initialization:

public void Configure(IWebHostBuilder builder) => builder
    .ConfigureAppHost(
        beforeConfigure:    appHost => /* fired before AppHost.Configure() */, 
        afterConfigure:     appHost => /* fired after AppHost.Configure() */,
        afterPluginsLoaded: appHost => /* fired after plugins are loaded */,
        afterAppHostInit:   appHost => /* fired after AppHost has initialized */);

Removing Features

The benefits of adopting a modular approach to AppHost configuration is the same as general organizational code structure which results in better decoupling and cohesion where it's easier to determine all the dependencies of a feature, easier to update, less chance of unintended side-effects, easier to share standard configuration amongst multiple projects and easier to remove the feature entirely, either temporarily if needing to isolate & debug a runtime issue by:

// [assembly: HostingStartup(typeof(MyApp.ConfigureAuth))]

Or easier to permanently replace or remove features by either directly deleting the isolated *.cs source files or by undoing mixing in the feature using mix -delete, e.g:

$ x mix -delete auth auth-db postgres

Which works similar to package managers where it removes all files contained within each mix gist.

INFO

Please see the Mix HowTo to find out how you can contribute your own gist mix features

Preserve ModularStartup configuration

Whilst using HostingStartup is recommended going forward, your existing ModularStartup classes will work without change when upgrading your projects to .NET6.

If you want to take advantage of the new .NET6 top-level host builder to side-step the need for a Startup class, you can still reuse and preserve your modular startup by adding Services.AddModularStartup<AppHost>() method before calling builder.Build();, e.g:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddModularStartup<AppHost>(builder.Configuration);

var app = builder.Build();
app.UseServiceStack(new AppHost());

The AppHost registration has become slightly easier to enable ServiceStack in this release when using the NetCoreAppSettings, as you no longer need to manually inject the IConfiguration class as it's now automatically populated by default :

// Previously
app.UseServiceStack(new AppHost
{
    AppSettings = new NetCoreAppSettings(builder.Configuration)
});

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

Migrating to HostingStartup

As we'll be using the new HostingStartup model going forward we recommend migrating your existing configuration to use them.

To help with this you can refer to the mix diff showing how each of the existing mix configurations were converted to the new model.

As a concrete example, lets take a look at the steps used to migrate our Chinook example application from NET5 using the previous Startup : ModularStartup, to the new HostingStartup.

Step 1

Migrate your existing ConfigureServices and Configure(IApplicationBuilder) from Startup : ModularStartup to the top-level host builder in Program.cs. Eg

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. 
    // You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
    app.UseHttpsRedirection();
}

app.Run();

Step 2

Move your AppHost class to a new Configure.AppHost.cs file.

Step 3

Implement IHostingStartup on your AppHost with automatic initialization. Eg:

public void Configure(IWebHostBuilder builder)
{
    builder.ConfigureServices(services => {
            // Configure ASP.NET Core IOC Dependencies
        })
        .Configure(app => {
            // Configure ASP.NET Core App
            if (!HasInit)
                app.UseServiceStack(new AppHost());
        });
}

Step 4

Declare assembly: HostingStartup for your AppHost in the same Configure.AppHost.cs. Eg:

[assembly: HostingStartup(typeof(Chinook.AppHost))]

Step 5

Migrate each existing modular startup class that implements IConfgiureServices and/or IConfigureApp to use IHostingStartup. Eg:

// net5.0 modular startup
using ServiceStack;

namespace Chinook
{
    public class ConfigureAutoQuery : IConfigureAppHost
    {
        public void Configure(IAppHost appHost)
        {
            appHost.Plugins.Add(new AutoQueryFeature {
                MaxLimit = 1000,
                IncludeTotal = true
            });
        }
    }
}
// net6.0 modular startup using IHostingStartup
using Microsoft.AspNetCore.Hosting;
using ServiceStack;

[assembly: HostingStartup(typeof(Chinook.ConfigureAutoQuery))]

namespace Chinook
{
    public class ConfigureAutoQuery : IHostingStartup
    {
        public void Configure(IWebHostBuilder builder)
        {
            builder.ConfigureAppHost(appHost =>
            {
                appHost.Plugins.Add(new AutoQueryFeature {
                    MaxLimit = 1000,
                    IncludeTotal = true
                });
            });
        }
    }
}

Remembering also that infrastructure like your Dockerfile or host will likely need the runtimes/SDKs updated as well.

Utilizing net5 mix templates

To support older projects the Existing ModularStartup configuration can still be used for when running on earlier .NET Core runtimes with the mix tool by setting its gist Id in the MIX_SOURCE Environment Variable, e.g:

$ MIX_SOURCE=7362ea802aef361bbdc21097b6a99e0d x mix

Which will use the older mix configuration as its source.

All project templates upgraded to .NET 6

As an LTS release we expect everyone will want to upgrade to .NET 6 as soon as possible to take advantage of .NET's best & fastest runtime yet. We're already using .NET 6 in production and haven't come across any reason not to upgrade yet.

With the project templates upgraded, you can now create a new ServiceStack .NET 6 App with x new:

$ x new

Or from our online project template creator at:

servicestack.net/start

Creating new .NET 5 projects

If you're not yet ready to move to .NET 6 you can still create new project templates of older versions of .NET Core project templates.

Where we've added .NET 5 project template support to our online Project creator page at:

servicestack.net/start?tag=net5

Otherwise our .NET Core project templates have had their last .NET 5.0 version tagged with net5 which can be installed with the x tool by using the full URL of its Source Code .zip archive in place of the Template name, e.g:

$ x new https://github.com/NetCoreTemplates/<template>/archive/refs/tags/net5.zip ProjectName

ServiceStackVS now supports Visual Studio 2022

Developers that have upgraded to Visual Studio 2022 can now use the Add ServiceStack Reference tooling for C#, TypeScript, F# and VB-NET!

The latest update supports both Visual Studio 2019 and 2022. If you are using an older version of Visual Studio, the previous version can still be downloaded here.

INFO

Project templates are no longer a part of the Visual Studio Extension, instead developers can use x new or Getting Started page on the ServiceStack website.

.NET 6 DateOnly and TimeOnly Types

To be able to more intently capture whole Dates and Times, .NET 6 sees the introduction of new DateOnly and TimeOnly Value Types which we expect to be a welcomed addition to Data Models so we've added built-in support for them in our JSON, JSV and CSV Text Serializers as well as for persistance in OrmLite Data Models.

Validation now a pre-configured Feature

Like ASP .NET Core itself, ServiceStack is primarily a modular Pay-to-Play framework with its functionality included as removable plugins where we limit built-ins to the bare essentials so unused features have no effect at runtime as well as to not prohibitively discourage better implementations from competing with defaults.

Validation has become one of those features which many people have come to rely on, where a popular reported issue is that is expected to work out-of-the-box. As it's based on an opinionated FluentValidation library we were hesitant to register it by default to leave room should a better and more ergonomic implementation for .NET was created, but as that hasn't happened we've built-in and extended validation support to add support for Type Validators, Declarative Validation Attributes and dynamic data sources like DB Validation Rules.

As it's a popular feature used in APIs we've upgraded it to a pre-registered plugin so it no longer needs to be explicitly registered:

// Plugins.Add(new ValidationFeature());

For Backwards compatibility we'll emit a warning if it's explicitly registered, and remove the pre-registered plugin.

Like other Auto-registered Plugins it can be resolved from the Plugins collection or AppHost:

var feature = Plugins.FirstOrDefault(x => x is ValidationFeature); 
var feature = appHost.AssertPlugin<ValidationFeature>(); // throw if not registered

And likewise can be removed from the Plugins collection or Feature Flags:

Plugins.RemoveAll(x => x is ValidationFeature); 

SetConfig(new HostConfig {
    EnableFeatures = Feature.All.Remove(Feature.Validation),
});

Breaking Changes

One breaking change that was unavoidable after adding .NET 6 TFM builds was needing to rename UpdateOnly OrmLite APIs to resolve new C# compiler ambiguous method overload errors.

Previously UpdateOnly() APIs accepted either a lambda expression where the fields to update would be inferred by the constructor Expression or it could accept a Data Model Instance which requires explicitly specifying which Properties of the Data Model should be updated, e.g. both of these are API Examples have the same behavior:

db.UpdateOnly(() => new Person { FirstName = "Ty" });
db.UpdateOnly(() => new Person { FirstName = "Ty", Age = 27 });

var person = new Person { FirstName = "Ty", Age = 27 };
db.UpdateOnly(person, onlyFields: p => p.FirstName);
db.UpdateOnly(person, onlyFields: p => new { p.FirstName, p.Age });

This now results in a build error so we've renamed the instance UpdateOnly APIs to UpdateOnlyFields, e.g:

db.UpdateOnlyFields(person, onlyFields: p => p.FirstName);
db.UpdateOnlyFields(person, onlyFields: p => new { p.FirstName, p.Age });

Basically if you encounter a build error after upgrading you'll need to add a *Fields suffix to the UpdateOnly method name.

Lets go .NET 6.0!

Despite only being a minor feature release, it in some ways marks a turning point for ServiceStack where we can look beyond the Legacy .NET Framework and .NET Standard past and focus new features that enable deeper integrations on the single unified .NET Runtime of .NET's future, we're excited to have ServiceStack join the .NET 6 party and look forward to resume working on our next feature release.

If you run into any issues please let us know in the Customer Forums or Issue Tracker.

v4.5 .NET Framework Deprecation Notice

In an effort to streamline the supported runtimes ServiceStack runs on we intend on following Microsoft .NET Framework end of support dates and drop support for .NET v4.5 in after its support end date at April 26, 2022.

We're already forced to maintain multiple minimum .NET Framework version requirements in a number of packages with 3rd Party dependencies lacking .NET 4.5 TFM builds so our packages currently have a mix of different minimum version requirements. To consolidate all .NET Framework builds to use a single version we intend on upgrading the minimum version to .NET Framework v4.7.2 after April 26, 2022.

If you're currently running ServiceStack on .NET v4.5 or v4.6 and intend to continue updating to newer versions we encourage you to start planning your upgrade to a newer .NET Framework on or after v4.7.2.

If you're unable to upgrade for any reason please let us know in the Customer Forums so we can measure the number of Customers affected for consideration in lowering our minimum .NET Framework requirements.