A Microsoft.Extensions.Hosting IHostBuilder extension, which allows Stashbox to be configured as the default service provider.
$ dotnet add package Stashbox.Extensions.HostingThis repository contains Stashbox integrations for ASP.NET Core, .NET Generic Host and simple ServiceCollection based applications.
| Package | Version |
|---|---|
| Stashbox.Extensions.Dependencyinjection | |
| Stashbox.Extensions.Hosting | |
| Stashbox.AspNetCore.Hosting | |
| Stashbox.AspNetCore.Multitenant |
IDisposable and IAsyncDisposable services.Developement environments, but can be extended to all environment types.The following example shows how you can integrate Stashbox (with the Stashbox.Extensions.Hosting package) as the default IServiceProvider implementation into your ASP.NET Core application:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(String[] args)
{
return Host.CreateDefaultBuilder(args)
.UseStashbox(container => // Optional configuration options.
{
// This one enables the lifetime validation for production environments too.
container.Configure(config => config.WithLifetimeValidation());
})
.ConfigureContainer<IStashboxContainer>((context, container) =>
{
// Execute a dependency tree validation.
if (context.HostingEnvironment.IsDevelopment())
container.Validate();
})
.ConfigureWebHostDefaults(
webBuilder => webBuilder
.UseStartup<Startup>());
}
}
You can also use the ConfigureContainer() method in your Startup class to use further configuration options:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Your service configuration.
}
public void ConfigureContainer(IStashboxContainer container)
{
// Your container configuration.
container.Configure(config => config.WithLifetimeValidation());
}
public void Configure(IApplicationBuilder app)
{
// Your application configuration.
}
}
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseStashbox(container => // Optional configuration options.
{
// This one enables the lifetime validation for production environments too.
container.Configure(config => config.WithLifetimeValidation());
});
builder.Host.ConfigureContainer<IStashboxContainer>((context, container) =>
{
// Execute a dependency tree validation.
if (context.HostingEnvironment.IsDevelopment())
container.Validate();
});
By default the ASP.NET Core framework uses the DefaultControllerActivator to instantiate controllers, but it uses the ServiceProvider only for instantiating their constructor dependencies. This behaviour could hide important errors Stashbox would throw in case of a misconfiguration, so it's recommended to let Stashbox activate your controllers and views.
You can enable this by adding the following options to your service configuration:
public void ConfigureServices(IServiceCollection services)
{
// For controllers only.
services.AddControllers()
.AddControllersAsServices();
// For controllers and views.
services.AddControllersWithViews()
.AddControllersAsServices()
.AddViewComponentsAsServices();
}
// For controllers only.
builder.Services.AddControllers()
.AddControllersAsServices();
// For controllers and views.
builder.Services.AddControllersWithViews()
.AddControllersAsServices()
.AddViewComponentsAsServices();
The Stashbox.AspNetCore.Multitenant package provides support for multitenant applications with a component called TenantDistributor. It's responsible for the following tasks:
ITenantIdExtractor implementation.// The type used to extract the current tenant identifier.
// This implementation shows how to extract the tenant id from a HTTP header.
public class HttpHeaderTenantIdExtractor : ITenantIdExtractor
{
public Task<object> GetTenantIdAsync(HttpContext context)
{
if (!context.Request.Headers.TryGetValue("TENANT-ID", out var value))
return Task.FromResult<object>(null);
return Task.FromResult<object>(value.First());
}
}
public static IHostBuilder CreateHostBuilder(String[] args)
{
return Host.CreateDefaultBuilder(args)
.UseStashboxMultitenant<HttpHeaderTenantIdExtractor>(
distributor => // The tenant distributor configuration options.
{
// The default service registration, it registers into the root container.
// It also could be registered into the default
// service collection through the ConfigureServices() api.
distributor.Register<IDependency, DefaultDependency>();
// Configure tenants.
distributor.ConfigureTenant("TenantA", container =>
// Register tenant specific service override
container.Register<IDependency, TenantASpecificDependency>());
distributor.ConfigureTenant("TenantB", container =>
// Register tenant specific service override
container.Register<IDependency, TenantBSpecificDependency>());
})
// The container parameter is the tenant distributor itself.
// Calling its Validate() method will verify the root container and each tenant.
.ConfigureContainer<IStashboxContainer>((context, container) =>
{
// Validate the root container and all tenants.
if (context.HostingEnvironment.IsDevelopment())
container.Validate();
})
.ConfigureWebHostDefaults(
webBuilder => webBuilder
.UseStartup<Startup>());
}
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseStashboxMultitenant<HttpHeaderTenantIdExtractor>(
distributor => // The tenant distributor configuration options.
{
// The default service registration, it registers into the root container.
// It also could be registered into the default
// service collection through the ConfigureServices() api.
distributor.Register<IDependency, DefaultDependency>();
// Configure tenants.
distributor.ConfigureTenant("TenantA", container =>
// Register tenant specific service override
container.Register<IDependency, TenantASpecificDependency>());
distributor.ConfigureTenant("TenantB", container =>
// Register tenant specific service override
container.Register<IDependency, TenantBSpecificDependency>());
});
// The container parameter is the tenant distributor itself.
// Calling its Validate() method will verify the root container and each tenant.
builder.Host.ConfigureContainer<IStashboxContainer>((context, container) =>
{
// Validate the root container and all tenants.
if (context.HostingEnvironment.IsDevelopment())
container.Validate();
});
With this example setup, you can differentiate tenants in a per-request basis identified by a HTTP header, where every tenant gets their overridden services.
The following example adds Stashbox (with the Stashbox.Extensions.Hosting package) as the default IServiceProvider implementation into your .NET Generic Host application:
public class Program
{
public static async Task Main(string[] args)
{
var host = Host.CreateDefaultBuilder(args)
.UseStashbox(container => // Optional configuration options.
{
// This one enables the lifetime validation for production environments too.
container.Configure(config => config.WithLifetimeValidation());
})
.ConfigureContainer<IStashboxContainer>((context, container) =>
{
// Execute a dependency tree validation.
if (context.HostingEnvironment.IsDevelopment())
container.Validate();
})
.ConfigureServices((context, services) =>
{
services.AddHostedService<Service>();
}).Build();
await host.RunAsync();
}
}
With the Stashbox.Extensions.Dependencyinjection package you can replace Microsoft's built-in dependency injection container with Stashbox. This package contains the core functionality used by the Stashbox.Extensions.Hosting, Stashbox.AspNetCore.Hosting and Stashbox.AspNetCore.Multitenant packages.
The following example shows how you can use this integration:
public class Program
{
public static async Task Main(string[] args)
{
// Create the service collection.
var services = new ServiceCollection();
// Configure your service collection.
services.AddLogging();
services.AddOptions();
// Add your services.
services.AddScoped<IService, Service>();
// Integrate Stashbox with the collection and grab your ServiceProvider.
var serviceProvider = services.UseStashbox(container => // Optional configuration options.
{
container.Configure(config => config.WithLifetimeValidation());
});
// Start using the application.
using (var scope = serviceProvider.CreateScope())
{
var service = scope.ServiceProvider.GetService<IService>();
await service.DoSomethingAsync();
}
}
}
Or you can use your own StashboxContainer to integrate with the ServiceCollection:
public class Program
{
public static async Task Main(string[] args)
{
// Create your container.
var container = new StashboxContainer(config => // Optional configuration options.
{
config.WithLifetimeValidation();
});
// Create the service collection.
var services = new ServiceCollection();
// Configure your service collection.
services.AddLogging();
services.AddOptions();
// Add your services.
services.AddScoped<IService, Service>();
// Or add them through Stashbox.
container.RegisterScoped<IService, Service>();
// Integrate Stashbox with the collection.
services.UseStashbox(container);
// Execute a dependency tree validation.
container.Validate();
// Start using the application.
await using (var scope = container.BeginScope())
{
var service = scope.Resolve<IService>();
await service.DoSomethingAsync();
}
}
}
IServiceCollection ExtensionsMost of Stashbox's service registration functionalities are available as extension methods of IServiceCollection.
class Service2 : IService2
{
private readonly IService service;
public Service2(IService service)
{
this.service = service;
}
}
var services = new ServiceCollection();
services.AddTransient<IService, Service>(); // Name-less registration.
services.AddTransient<IService, AnotherService>("serviceName"); // Register dependency with name.
services.AddTransient<IService2, Service2>(config =>
// Inject the named service as dependency.
config.WithDependencyBinding<IService>(
"serviceName" // Name of the dependency.
));
Service configuration with Stashbox's Fluent Registration API:
var services = new ServiceCollection();
services.AddTransient<IService, Service>(config =>
config.WithFactory<IDependency>(dependency => new Service(dependency)).AsImplementedTypes());
class ServiceDecorator : IService
{
private readonly IService decorated;
public ServiceDecorator(IService service)
{
this.decorated = service;
}
}
var services = new ServiceCollection();
services.AddTransient<IService, Service>();
services.Decorate<IService, ServiceDecorator>();
var services = new ServiceCollection();
services.ScanAssemblyOf<IService>(
// Set a filter for which types should be excluded/included in the registration process.
// In this case, only the publicly available types are selected from the assembly.
type => type.IsPublic,
// The service type selector. Used to filter which interface or base types the implementation should be mapped to.
// In this case, we are registering only by interfaces.
(implementationType, serviceType) => serviceType.IsInterface,
false, // Do not map services to themselves. E.g: Service -> Service.
config =>
{
// Register IService instances as scoped.
if (config.ServiceType == typeof(IService))
config.WithScopedLifetime();
}
);
class CompositionRoot : ICompositionRoot
{
public void Compose(IStashboxContainer container)
{
container.Register<IService, Service>();
}
}
var services = new ServiceCollection();
services.ComposeBy<CompositionRoot>();
// Or let Stashbox find all composition roots in an assembly.
services.ComposeAssembly(typeof(CompositionRoot).Assembly);