diff --git a/.gitignore b/.gitignore index e9dbd9ff..2ee4f388 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ # added .vscode/ *.db +*.db-shm +*.db-wal # globs Makefile.in diff --git a/docs/Options.md b/docs/Options.md index 7ac7ad34..5824ff66 100644 --- a/docs/Options.md +++ b/docs/Options.md @@ -1,21 +1,37 @@ # Per-Tenant Options +Finbuckle.MultiTenant is designed to emphasize using per-tenant options in an app to drive per-tenant behavior. This +approach allows app logic to be written having to add tenant-dependent or +tenant-specific logic to the code. + +By using per-tenant options, the options values used within app logic will automatically +reflect the per-tenant values as configured for the current tenant. Any code already using the Options pattern will gain +multi-tenant capability with minimal code changes. + 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. +Note: For authentication options, Finbuckle.MultiTenant provides special support +for [per-tenant authentication](Authentication). + 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 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. +code internal to ASP.NET Core or other libraries that use the Options pattern. + +A potential issue arises when code internally stores or caches options values from +an `IOptions`, `IOptionsSnapshot`, or `IOptionsMonitor` instance. This is usually +unnecessary because the options are already cached within the .NET options infrastructure, and in these cases the +initial instance of the options is always used, regardless of the current tenant. Finbuckle.MultiTenant works around +this for some parts of +ASP.NET Core, and recommends that in your own code to always access options values via +the `IOptions`, `IOptionsSnapshot`, or `IOptionsMonitor` instance. This will ensure the +correct values for the current tenant are used. ## Options Basics @@ -62,11 +78,12 @@ public MyController : Controller httpContext.RequestServices.GetServices(); ``` -At this point the options would be the same for all tenants. +With standard options each tenant would get see the same exact options. ## Customizing Options Per Tenant -This sections assumes Finbuckle.MultiTenant is installed and configured with a `TTenantInfo` type of `TenantInfo`. +This sections assumes a standard web application builder is configured and Finbuckle.MultiTenant is configured with +a `TTenantInfo` type of `TenantInfo`. See [Getting Started](GettingStarted) for details. To configure options per tenant, the standard `Configure` method variants on the service collection now all @@ -74,8 +91,6 @@ have `PerTenant` equivalents which accept a `Action` dele runtime the delegate will be called with the current tenant details. ```cs -var builder = WebApplication.CreateBuilder(args); - // configure options per tenant builder.Services.ConfigurePerTenant((options, tenantInfo) => { @@ -132,42 +147,27 @@ public MyController : Controller } ``` -## Named Options - -You can configure options by name using the `WithPerTenantNamedOptions` method. - -Call `WithPerTenantNamedOptions` after `AddMultiTenant` in the `ConfigureServices` method: - -```cs -services.AddMultiTenant()... - .WithPerTenantNamedOptions(someOptionsName, (options, tenantInfo) => - { - // only update options named "someOptionsName" - options.MyOption1 = tenantInfo.Option1Value; - options.MyOption2 = tenantInfo.Option2Value; - }); +## Using the OptionsBuilder API + +.NET provides +the [OptionsBuilder](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-8.0#optionsbuilder-api) +API to provide more flexibility for configuring options. This pattern simplifies dependency injection and validation for +the standard [Options pattern](https://learn.microsoft.com/en-us/dotnet/core/extensions/options). Finbuckle.MultiTenant +extends this API to enable options configuration for per-tenant options similarly. Note that while the `OptionsBuilder` +normally supports up to five dependencies, Finbuckle.MultiTenant support only supports four. + +```csharp +// use OptionsBuilder API to configure per-tenant options with dependencies +builder.Services + .AddOptions("optionalName") + .ConfigurePerTenant( + (options, es, tenantInfo) => + options.Property = DoSomethingWith(es, tenantInfo)); ``` -The `string` parameter is the name of the options. 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) -. - -`WithPerTenantNameOptions` can be called multiple times on the same `TOptions` -type and the configuration will run in the respective order. - -The same delegate passed to `WithPerTenantNameOptions` is applied to all options generated of type `TOptions` -regardless of the option name. You can use the `name` argument in the callback to help you set the correct options by -name. - -`WithPerTenantOptions` can be used in combination with `WithPerTenantNameOptions` for the same -type `TOptions`. The `WithPerTenantOptions` callbacks will be invoked first, followed by -the `WithPerTenantNameOptions` callbacks. - -## Options Caching +## Options and Caching -Internally ASP.NET Core caches options, and Finbuckle.MultiTenant extends this to cache options per tenant. Caching +Internally .NET caches options, and Finbuckle.MultiTenant extends this to cache options per tenant. Caching occurs when a `TOptions` instance is retrieved via `Value` or `Get` on the injected `IOptions` (or derived) instance for the first time for a tenant.