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.