diff --git a/README.md b/README.md
index ef6c31e..844306e 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Orleans.Multitenant
+# Orleans.Multitenant
Secure, flexible tenant separation for Microsoft Orleans 8
> [![Nuget (with prereleases)](https://img.shields.io/nuget/vpre/Orleans.Multitenant?color=gold&label=NuGet:%20Orleans.Multitenant&style=plastic)](https://www.nuget.org/packages/Orleans.Multitenant)
@@ -49,6 +49,26 @@ siloBuilder
)
```
+#### Customize storage provider constructor parameters
+By default, the parameters passed into the storage provider instance for a tenant are the tenant provider name (which contains the tenant Id) and the tenant options. Some storage providers may expect a different (wrapper) type for the options, or you may want to pass in additional parameters (e.g. `ClusterOptions`).
+
+To do this, you can pass in an optional `GrainStorageProviderParametersFactory? getProviderParameters` parameter.
+
+E.g. the Orleans ADO.NET storage provider constructor expects an `IOptions` instead of an `AdoNetGrainStorageOptions`. You can use `getProviderParameters` to wrap the `AdoNetGrainStorageOptions` in an `IOptions`:
+
+```csharp
+.AddMultitenantGrainStorageAsDefault(
+ (silo, name) => silo.AddAdoNetGrainStorage(name, options => options.ConnectionString = sqlConnectionString),
+
+ configureTenantOptions: (options, tenantId) => options.ConnectionString = sqlConnectionString.Replace("[DatabaseName]", tenantId, StringComparison.Ordinal),
+
+ getProviderParameters: (services, providerName, tenantProviderName, options) => [Options.Create(options)]
+)
+```
+Note that you do not need to include the `tenantProviderName` in the returned provider parameters; it is added automatically.
+
+The parameters passed to `getProviderParameters` allow to access relevant services from DI to retrieve additional provider parameters, if needed.
+
### Add multitenant streams
To configure a silo to use a specific stream provider type as a named stream provider with tenant separation, use `AddMultitenantStreams`. Any Orleans stream provider can be used:
```csharp
diff --git a/src/Example/Apis/Apis.csproj b/src/Example/Apis/Apis.csproj
index 3d9efc4..093f493 100644
--- a/src/Example/Apis/Apis.csproj
+++ b/src/Example/Apis/Apis.csproj
@@ -20,7 +20,7 @@
-
+
diff --git a/src/Example/Apis/Foundation/Program.cs b/src/Example/Apis/Foundation/Program.cs
index 03747e5..895c253 100644
--- a/src/Example/Apis/Foundation/Program.cs
+++ b/src/Example/Apis/Foundation/Program.cs
@@ -1,6 +1,6 @@
-using Microsoft.OpenApi.Models;
-using Azure.Data.Tables;
+using Azure.Data.Tables;
using Orleans.Configuration;
+using Microsoft.OpenApi.Models;
using Orleans.Multitenant;
using Orleans.Storage;
using Orleans4Multitenant.Apis;
@@ -12,19 +12,19 @@
.UseLocalhostClustering()
.AddMultitenantCommunicationSeparation()
.AddMultitenantGrainStorageAsDefault(
- (silo, name) => silo.AddAzureTableGrainStorage(name, options =>
- options.TableServiceClient = new TableServiceClient(tableStorageConnectionString)),
- // Called during silo startup, to ensure that any common dependencies
- // needed for tenant-specific provider instances are initialized
-
- configureTenantOptions: (options, tenantId) =>
- {
- options.TableServiceClient = new TableServiceClient(tableStorageConnectionString);
- options.TableName = $"OrleansGrainState{tenantId}";
- } // Called on the first grain state access for a tenant in a silo,
- // to initialize the options for the tenant-specific provider instance
- // just before it is instantiated
- )
+ (silo, name) => silo.AddAzureTableGrainStorage(name, options =>
+ options.TableServiceClient = new TableServiceClient(tableStorageConnectionString)),
+ // Called during silo startup, to ensure that any common dependencies
+ // needed for tenant-specific provider instances are initialized
+
+ configureTenantOptions: (options, tenantId) =>
+ {
+ options.TableServiceClient = new TableServiceClient(tableStorageConnectionString);
+ options.TableName = $"OrleansGrainState{tenantId}";
+ } // Called on the first grain state access for a tenant in a silo,
+ // to initialize the options for the tenant-specific provider instance
+ // just before it is instantiated
+ )
);
// Add services to the container.
diff --git a/src/Example/Services.Tenant/Services.Tenant.csproj b/src/Example/Services.Tenant/Services.Tenant.csproj
index d3d4007..a0de802 100644
--- a/src/Example/Services.Tenant/Services.Tenant.csproj
+++ b/src/Example/Services.Tenant/Services.Tenant.csproj
@@ -16,7 +16,7 @@
-
+
diff --git a/src/Orleans.Multitenant/Extensions.cs b/src/Orleans.Multitenant/Extensions.cs
index e84a7b9..412e1af 100644
--- a/src/Orleans.Multitenant/Extensions.cs
+++ b/src/Orleans.Multitenant/Extensions.cs
@@ -39,12 +39,14 @@ public static ISiloBuilder AddMultitenantCommunicationSeparation(
/// This storage provider instance will not be used to store state - it is only called at silo initialization, to ensure that any shared dependencies needed by the tenant-specific storage provider instances are initialized
///
/// Action to configure the supplied based on the supplied tenant ID (e.g. use the tenant ID in a storage table name to realize separate storage per tenant)
+ /// Optional factory to transform or add constructor parameters for tenant grain storage providers; for details see When omitted, only is passed in
/// Action to configure the
/// The same instance of the for chaining
public static ISiloBuilder AddMultitenantGrainStorageAsDefault(
this ISiloBuilder builder,
Func addStorageProvider,
Action? configureTenantOptions = null,
+ GrainStorageProviderParametersFactory? getProviderParameters = null,
Action>? configureOptions = null)
where TGrainStorage : IGrainStorage
where TGrainStorageOptions : class, new()
@@ -53,6 +55,7 @@ public static ISiloBuilder AddMultitenantGrainStorageAsDefault { _ = addStorageProvider(builder, name); return services; },
configureTenantOptions,
+ getProviderParameters,
configureOptions);
///
@@ -70,6 +73,7 @@ public static ISiloBuilder AddMultitenantGrainStorageAsDefault
/// Action to configure the supplied based on the supplied tenant ID (e.g. use the tenant ID in a storage table name to realize separate storage per tenant)
+ /// Optional factory to transform or add constructor parameters for tenant grain storage providers; for details see When omitted, only is passed in
/// Action to configure the
/// The same instance of the for chaining
public static ISiloBuilder AddMultitenantGrainStorage(
@@ -77,6 +81,7 @@ public static ISiloBuilder AddMultitenantGrainStorage addStorageProvider,
Action? configureTenantOptions = null,
+ GrainStorageProviderParametersFactory? getProviderParameters = null,
Action>? configureOptions = null)
where TGrainStorage : IGrainStorage
where TGrainStorageOptions : class, new()
@@ -85,6 +90,7 @@ public static ISiloBuilder AddMultitenantGrainStorage { _ = addStorageProvider(builder, name); return sevices; },
configureTenantOptions,
+ getProviderParameters,
configureOptions));
/// Configure silo to use a specific stream provider type as a named stream provider, with tenant separation
@@ -121,12 +127,14 @@ public static class ServiceCollectionExtensions
/// This storage provider instance will not be used to store state - it is only called at silo initialization, to ensure that any shared dependencies needed by the tenant-specific storage provider instances are initialized
///
/// Action to configure the supplied based on the supplied tenant ID (e.g. use the tenant ID in a storage table name to realize separate storage per tenant)
+ /// Optional factory to transform or add constructor parameters for tenant grain storage providers; for details see When omitted, only is passed in
/// Action to configure the
/// The same instance of the for chaining
public static IServiceCollection AddMultitenantGrainStorageAsDefault(
this IServiceCollection services,
Func addStorageProvider,
Action? configureTenantOptions = null,
+ GrainStorageProviderParametersFactory? getProviderParameters = null,
Action>? configureOptions = null)
where TGrainStorage : IGrainStorage
where TGrainStorageOptions : class, new()
@@ -135,6 +143,7 @@ public static IServiceCollection AddMultitenantGrainStorageAsDefault
@@ -152,6 +161,7 @@ public static IServiceCollection AddMultitenantGrainStorageAsDefault
/// Action to configure the supplied based on the supplied tenant ID (e.g. use the tenant ID in a storage table name to realize separate storage per tenant)
+ /// Optional factory to transform or add constructor parameters for tenant grain storage providers; for details see When omitted, only is passed in
/// Action to configure the
/// The same instance of the for chaining
public static IServiceCollection AddMultitenantGrainStorage(
@@ -159,6 +169,7 @@ public static IServiceCollection AddMultitenantGrainStorage addStorageProvider,
Action? configureTenantOptions = null,
+ GrainStorageProviderParametersFactory? getProviderParameters = null,
Action>? configureOptions = null)
where TGrainStorage : IGrainStorage
where TGrainStorageOptions : class, new()
@@ -167,11 +178,29 @@ public static IServiceCollection AddMultitenantGrainStorage TenantGrainStorageFactoryFactory.Create(services, name, configureTenantOptions),
+ (services, name) => TenantGrainStorageFactoryFactory.Create(services, name, configureTenantOptions, getProviderParameters),
configureOptions);
}
}
+///
+/// Factory delegate, used to create parameters for a tenant grain storage provider constructor.
+/// Allows to e.g. transform the options if the provider expects a different type than ,
+/// or to retrieve an add extra parameters like
+///
+/// The provider-specific grain storage options type, e.g. Orleans.Storage.MemoryGrainStorageOptions or Orleans.Storage.AzureTableStorageOptions
+/// The silo services
+/// The name - without the tenant id - of the provider; can be used to access named provider services that are not tenant specific
+/// The name - including the tenant id - of the tenant provider; can be used to access named provider services that are tenant specific
+/// The options to pass to the provider. Note that configureTenantOptions and options validation have already been executed on this
+/// The tenant storage provider construction parameters to pass to DI. Don't include in these; it is added automatically
+public delegate object[] GrainStorageProviderParametersFactory(
+ IServiceProvider services,
+ string providerName,
+ string tenantProviderName,
+ TGrainStorageOptions options
+);
+
public static class GrainExtensions
{
/// Get a tenant stream provider from within a , for the tenant that this grain belongs to
diff --git a/src/Orleans.Multitenant/Internal/TenantGrainStorageFactory.cs b/src/Orleans.Multitenant/Internal/TenantGrainStorageFactory.cs
index 0cef3fb..7209668 100644
--- a/src/Orleans.Multitenant/Internal/TenantGrainStorageFactory.cs
+++ b/src/Orleans.Multitenant/Internal/TenantGrainStorageFactory.cs
@@ -6,16 +6,23 @@ namespace Orleans.Multitenant.Internal;
static class TenantGrainStorageFactoryFactory
{
- public static ITenantGrainStorageFactory Create(IServiceProvider services, string name, Action? configureTenantOptions = null)
- where TGrainStorage : IGrainStorage
+ public static ITenantGrainStorageFactory Create(
+ IServiceProvider services,
+ string name,
+ Action? configureTenantOptions = null,
+ GrainStorageProviderParametersFactory? getProviderParameters = null
+ ) where TGrainStorage : IGrainStorage
where TGrainStorageOptions : class, new()
where TGrainStorageOptionsValidator : class, IConfigurationValidator
- => configureTenantOptions is null ?
- ActivatorUtilities.CreateInstance>(services, name) :
- ActivatorUtilities.CreateInstance>(services, name, configureTenantOptions);
+ {
+ List