INFO
For more information on the earlier Modular Startup in ServiceStack v5.x see our Legacy Modular Startup docs
Taking advantage of C# 9 top level statements and .NET 6 WebApplication Hosting Model, ServiceStack templates by utilize both these features to simplify configuring your AppHost in a modular way.
Program.cs
becomes a script-like file since C# 9 top level statements are generating application entry point implicitly.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
app.UseHttpsRedirection();
}
app.UseServiceStack(new AppHost())
app.Run();
The application AppHost
hooks into startup using HostingStartup
assembly attribute.
In ServiceStack templates, this uses the file name prefix of Configure.*.cs
to help
identify these startup modules.
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("MyApp"), IHostingStartup
{
public void Configure(IWebHostBuilder builder) => builder
.ConfigureServices(services => {
// Configure ASP.NET Core IOC Dependencies
});
public override void Configure()
{
// Configure ServiceStack, Run custom logic after ASP.NET Core Startup
SetConfig(new HostConfig {
});
}
}
The use of Modular Startup does not change the AppHost declaration, but enables the modular grouping of configuration concerns. 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:
//[assembly: HostingStartup(typeof(MyApp.AppHost))]
Module composition using mix
​
This has enabled ServiceStack Apps to be easily composed with the features developers need in mind. 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 where you can view all available mix gists that can be added to projects with:
x mix
.NET 6's idiom is incorporated into the 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, you can replace MongoDB with a completely different PostgreSQL RDBMS implementation by running:
x mix auth auth-db postgres
Services and App Customizations​
Modular Startup configurations are flexible enough to encapsulate customizing ASP.NET Core's IOC and the built WebApplication
by registering a IStartupFilter
which is required by the Open API v3 Modular Configuration:
x mix openapi3
[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();
services.AddServiceStackSwagger();
services.AddBasicAuth<Data.ApplicationUser>();
//services.AddJwtAuth();
services.AddTransient<IStartupFilter, StartupFilter>();
}
});
public class StartupFilter : IStartupFilter
{
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next) => app =>
{
app.UseSwagger();
app.UseSwaggerUI();
next(app);
};
}
}
ConfigureAppHost​
Looking deeper, we can see where we're plugins are able to configure ServiceStack via the .ConfigureAppHost()
extension method to
execute custom logic on AppHost
Startup:
[assembly: HostingStartup(typeof(MyApp.ConfigureAutoQuery))]
namespace MyApp;
public class ConfigureAutoQuery : IHostingStartup
{
public void Configure(IWebHostBuilder builder) => builder
.ConfigureServices(services => {
// Enable Audit History
services.AddSingleton<ICrudEvents>(c =>
new OrmLiteCrudEvents(c.GetRequiredService<IDbConnectionFactory>()));
// For TodosService
services.AddPlugin(new AutoQueryDataFeature());
// For Bookings https://docs.servicestack.net/autoquery-crud-bookings
services.AddPlugin(new AutoQueryFeature
{
MaxLimit = 1000,
//IncludeTotal = true,
});
})
.ConfigureAppHost(appHost => {
appHost.Resolve<ICrudEvents>().InitSchema();
});
}
Customize AppHost at different Startup Lifecycles​
By default, any AppHost configuration is called before AppHost.Configure()
is run, but 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
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 .NET 6 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
});
}
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
});
}
}
// net8.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
.ConfigureServices(services => {
services.AddPlugin(new AutoQueryFeature {
MaxLimit = 1000,
IncludeTotal = true
});
});
}
Remembering also that infrastructure like your
Dockerfile
or host will likely need the runtimes/SDKs updated as well.