diff --git a/docs/Options.md b/docs/Options.md index 395484cb..7ac7ad34 100644 --- a/docs/Options.md +++ b/docs/Options.md @@ -1,17 +1,23 @@ # Per-Tenant Options -Finbuckle.MultiTenant integrates with the standard ASP.NET -Core [Options pattern](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options) and lets apps -customize options distinctly for each tenant. The current tenant determines which options are retrieved via -the `IOptions` (or derived) instance's `Value` property and `Get(string name)` method. +Finbuckle.MultiTenant integrates with the +standard [.NET Options pattern](https://learn.microsoft.com/en-us/dotnet/core/extensions/options) (see also the [ASP.NET +Core Options pattern](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options) and lets apps +customize options distinctly for each tenant. + +The current tenant determines which options are retrieved via +the `IOptions`, `IOptionsSnapshot`, or `IOptionsMonitor` instances' `Value` property and +`Get(string name)` method. A specialized variation of this is [per-tenant authentication](Authentication). Per-tenant options will work with *any* options class when using `IOptions`, `IOptionsSnapshot`, or `IOptionsMonitor` with dependency injection or service resolution. This includes an app's own code *and* -code internal to ASP.NET Core or other libraries that use the Options pattern. There is one potential caveat: ASP.NET -Core and other libraries may internally cache options or exhibit other unexpected behavior resulting in the wrong option -values! +code internal to ASP.NET Core or other libraries that use the Options pattern. There is a caveat to be aware of: some +code, such as certain classes in ASP.NET Core or other libraries, may internally cache options resulting in only the +values from the first tenant being used despite the current tenant. + +## Options Basics Consider a typical scenario in ASP.Net Core, starting with a simple class: @@ -23,25 +29,24 @@ public class MyOptions } ``` -In the `ConfigureServices` method of the startup class, `services.Configure` is called with a delegate +In the app configuration, `services.Configure` is called with a delegate or `IConfiguration` parameter to set the option values: ```cs -public class Startup -{ - public void ConfigureServices(IServiceCollection services) - { - services.Configure(options => options.Option1 = 1); +var builder = WebApplication.CreateBuilder(args); + +// other code omitted... + +builder.Services.Configure(options => options.Option1 = 1); - // Other services configured here... - } -} + // rest of app code... ``` -Dependency injection of `IOptions` into a controller (or anywhere DI can be used) provides access to the -options values, which are the same for every tenant at this point: +Dependency injection of `IOptions` or its siblings into a class constructor, such as a controller, provides +access to the options values. A service provider instance can also provide access to the options values. ```cs +// access options via dependency injection in a class constructor public MyController : Controller { private readonly MyOptions _myOptions; @@ -52,34 +57,65 @@ public MyController : Controller _myOptions = optionsAccessor.Value; } } + +// or with a service provider +httpContext.RequestServices.GetServices(); ``` +At this point the options would be the same for all tenants. + ## Customizing Options Per Tenant -This sections assumes Finbuckle.MultiTenant is installed and configured. See [Getting Started](GettingStarted) for -details. +This sections assumes Finbuckle.MultiTenant is installed and configured with a `TTenantInfo` type of `TenantInfo`. +See [Getting Started](GettingStarted) for details. -Call `WithPerTenantOptions` after `AddMultiTenant` in the `ConfigureServices` method: +To configure options per tenant, the standard `Configure` method variants on the service collection now all +have `PerTenant` equivalents which accept a `Action` delegate. When the options are created at +runtime the delegate will be called with the current tenant details. ```cs -services.AddMultiTenant()... - .WithPerTenantOptions((options, tenantInfo) => +var builder = WebApplication.CreateBuilder(args); + +// configure options per tenant +builder.Services.ConfigurePerTenant((options, tenantInfo) => { options.MyOption1 = tenantInfo.Option1Value; options.MyOption2 = tenantInfo.Option2Value; }); -``` -The type parameter `TOptions` is the options type being customized per-tenant. The method parameter is -an `Action`. This action will modify the options instance *after* the options normal configuration -and *before* its [post configuration](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?#ipostconfigureoptions) -. +// or configure named options per tenant +builder.Services.ConfigurePerTenant("scheme2", (options, tenantInfo) => + { + options.MyOption1 = tenantInfo.Option1Value; + options.MyOption2 = tenantInfo.Option2Value; + }); -`WithPerTenantOptions` can be called multiple times on the same `TOptions` -type and the configuration will run in the respective order. +// ConfigureAll options variant +builder.Services.ConfigureAllPerTenant((options, tenantInfo) => + { + options.MyOption1 = tenantInfo.Option1Value; + options.MyOption2 = tenantInfo.Option2Value; + }); + +// can also configure post options, named post options, and all post options variants +builder.Services.PostConfigurePerTenant((options, tenantInfo) => + { + options.MyOption1 = tenantInfo.Option1Value; + options.MyOption2 = tenantInfo.Option2Value; + }); -The same delegate passed to `WithPerTenantOptions` is applied to all options generated of type `TOptions` -regardless of the option name, similar to the .NET `ConfigureAll` method. +builder.Services.PostConfigurePerTenant("scheme2", (options, tenantInfo) => + { + options.MyOption1 = tenantInfo.Option1Value; + options.MyOption2 = tenantInfo.Option2Value; + }); + +builder.Services.PostConfigureAllPerTenant((options, tenantInfo) => + { + options.MyOption1 = tenantInfo.Option1Value; + options.MyOption2 = tenantInfo.Option2Value; + }); +``` Now with the same controller example from above, the option values will be specific to the current tenant: