Skip to content

Commit cfc2c21

Browse files
authored
Merge pull request #8 from Applicita/feature-custom-provider-parameters
Feature: custom storage provider parameters
2 parents a575640 + a287710 commit cfc2c21

File tree

9 files changed

+114
-29
lines changed

9 files changed

+114
-29
lines changed

README.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# <img src="img/CSharp-Toolkit-Icon.png" alt="Backend Toolkit" width="64px" />Orleans.Multitenant
1+
# <img src="img/CSharp-Toolkit-Icon.png" alt="Backend Toolkit" width="64px" />Orleans.Multitenant
22
Secure, flexible tenant separation for Microsoft Orleans 8
33

44
> [![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)<br />
@@ -49,6 +49,26 @@ siloBuilder
4949
)
5050
```
5151

52+
#### Customize storage provider constructor parameters
53+
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`).
54+
55+
To do this, you can pass in an optional `GrainStorageProviderParametersFactory<TGrainStorageOptions>? getProviderParameters` parameter.
56+
57+
E.g. the Orleans ADO.NET storage provider constructor expects an `IOptions<AdoNetGrainStorageOptions>` instead of an `AdoNetGrainStorageOptions`. You can use `getProviderParameters` to wrap the `AdoNetGrainStorageOptions` in an `IOptions<AdoNetGrainStorageOptions>`:
58+
59+
```csharp
60+
.AddMultitenantGrainStorageAsDefault<AdoNetGrainStorage, AdoNetGrainStorageOptions, AdoNetGrainStorageOptionsValidator>(
61+
(silo, name) => silo.AddAdoNetGrainStorage(name, options => options.ConnectionString = sqlConnectionString),
62+
63+
configureTenantOptions: (options, tenantId) => options.ConnectionString = sqlConnectionString.Replace("[DatabaseName]", tenantId, StringComparison.Ordinal),
64+
65+
getProviderParameters: (services, providerName, tenantProviderName, options) => [Options.Create(options)]
66+
)
67+
```
68+
Note that you do not need to include the `tenantProviderName` in the returned provider parameters; it is added automatically.
69+
70+
The parameters passed to `getProviderParameters` allow to access relevant services from DI to retrieve additional provider parameters, if needed.
71+
5272
### Add multitenant streams
5373
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:
5474
```csharp

src/Example/Apis/Apis.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
<PackageReference Include="Microsoft.Orleans.Persistence.AzureStorage" Version="8.1.0" />
2121
<PackageReference Include="Microsoft.Orleans.Persistence.Memory" Version="8.1.0" />
2222
<PackageReference Include="Microsoft.Orleans.Sdk" Version="8.1.0" />
23-
<PackageReference Include="Orleans.Multitenant" Version="2.1.0" />
23+
<PackageReference Include="Orleans.Multitenant" Version="2.2.8" />
2424
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.4" />
2525
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
2626
</ItemGroup>

src/Example/Apis/Foundation/Program.cs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
using Microsoft.OpenApi.Models;
2-
using Azure.Data.Tables;
1+
using Azure.Data.Tables;
32
using Orleans.Configuration;
3+
using Microsoft.OpenApi.Models;
44
using Orleans.Multitenant;
55
using Orleans.Storage;
66
using Orleans4Multitenant.Apis;
@@ -12,19 +12,19 @@
1212
.UseLocalhostClustering()
1313
.AddMultitenantCommunicationSeparation()
1414
.AddMultitenantGrainStorageAsDefault<AzureTableGrainStorage, AzureTableStorageOptions, AzureTableGrainStorageOptionsValidator>(
15-
(silo, name) => silo.AddAzureTableGrainStorage(name, options =>
16-
options.TableServiceClient = new TableServiceClient(tableStorageConnectionString)),
17-
// Called during silo startup, to ensure that any common dependencies
18-
// needed for tenant-specific provider instances are initialized
19-
20-
configureTenantOptions: (options, tenantId) =>
21-
{
22-
options.TableServiceClient = new TableServiceClient(tableStorageConnectionString);
23-
options.TableName = $"OrleansGrainState{tenantId}";
24-
} // Called on the first grain state access for a tenant in a silo,
25-
// to initialize the options for the tenant-specific provider instance
26-
// just before it is instantiated
27-
)
15+
(silo, name) => silo.AddAzureTableGrainStorage(name, options =>
16+
options.TableServiceClient = new TableServiceClient(tableStorageConnectionString)),
17+
// Called during silo startup, to ensure that any common dependencies
18+
// needed for tenant-specific provider instances are initialized
19+
20+
configureTenantOptions: (options, tenantId) =>
21+
{
22+
options.TableServiceClient = new TableServiceClient(tableStorageConnectionString);
23+
options.TableName = $"OrleansGrainState{tenantId}";
24+
} // Called on the first grain state access for a tenant in a silo,
25+
// to initialize the options for the tenant-specific provider instance
26+
// just before it is instantiated
27+
)
2828
);
2929

3030
// Add services to the container.

src/Example/Services.Tenant/Services.Tenant.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
<ItemGroup>
1818
<PackageReference Include="Microsoft.Orleans.Sdk" Version="8.1.0" />
19-
<PackageReference Include="Orleans.Multitenant" Version="2.1.0" />
19+
<PackageReference Include="Orleans.Multitenant" Version="2.2.8" />
2020
</ItemGroup>
2121

2222
<ItemGroup>

src/Orleans.Multitenant/Extensions.cs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,14 @@ public static ISiloBuilder AddMultitenantCommunicationSeparation(
3939
/// 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
4040
/// </param>
4141
/// <param name="configureTenantOptions">Action to configure the supplied <typeparamref name="TGrainStorageOptions"/> based on the supplied tenant ID (e.g. use the tenant ID in a storage table name to realize separate storage per tenant)</param>
42+
/// <param name="getProviderParameters">Optional factory to transform or add constructor parameters for tenant grain storage providers; for details see <see cref="GrainStorageProviderParametersFactory{TGrainStorageOptions}"/><br />When omitted, only <typeparamref name="TGrainStorageOptions"/> is passed in</param>
4243
/// <param name="configureOptions">Action to configure the <see cref="MultitenantStorageOptions"/></param>
4344
/// <returns>The same instance of the <see cref="ISiloBuilder"/> for chaining</returns>
4445
public static ISiloBuilder AddMultitenantGrainStorageAsDefault<TGrainStorage, TGrainStorageOptions, TGrainStorageOptionsValidator>(
4546
this ISiloBuilder builder,
4647
Func<ISiloBuilder, string, ISiloBuilder> addStorageProvider,
4748
Action<TGrainStorageOptions, string>? configureTenantOptions = null,
49+
GrainStorageProviderParametersFactory<TGrainStorageOptions>? getProviderParameters = null,
4850
Action<OptionsBuilder<MultitenantStorageOptions>>? configureOptions = null)
4951
where TGrainStorage : IGrainStorage
5052
where TGrainStorageOptions : class, new()
@@ -53,6 +55,7 @@ public static ISiloBuilder AddMultitenantGrainStorageAsDefault<TGrainStorage, TG
5355
ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME,
5456
(services, name) => { _ = addStorageProvider(builder, name); return services; },
5557
configureTenantOptions,
58+
getProviderParameters,
5659
configureOptions);
5760

5861
/// <summary>
@@ -70,13 +73,15 @@ public static ISiloBuilder AddMultitenantGrainStorageAsDefault<TGrainStorage, TG
7073
/// 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
7174
/// </param>
7275
/// <param name="configureTenantOptions">Action to configure the supplied <typeparamref name="TGrainStorageOptions"/> based on the supplied tenant ID (e.g. use the tenant ID in a storage table name to realize separate storage per tenant)</param>
76+
/// <param name="getProviderParameters">Optional factory to transform or add constructor parameters for tenant grain storage providers; for details see <see cref="GrainStorageProviderParametersFactory{TGrainStorageOptions}"/><br />When omitted, only <typeparamref name="TGrainStorageOptions"/> is passed in</param>
7377
/// <param name="configureOptions">Action to configure the <see cref="MultitenantStorageOptions"/></param>
7478
/// <returns>The same instance of the <see cref="ISiloBuilder"/> for chaining</returns>
7579
public static ISiloBuilder AddMultitenantGrainStorage<TGrainStorage, TGrainStorageOptions, TGrainStorageOptionsValidator>(
7680
this ISiloBuilder builder,
7781
string name,
7882
Func<ISiloBuilder, string, ISiloBuilder> addStorageProvider,
7983
Action<TGrainStorageOptions, string>? configureTenantOptions = null,
84+
GrainStorageProviderParametersFactory<TGrainStorageOptions>? getProviderParameters = null,
8085
Action<OptionsBuilder<MultitenantStorageOptions>>? configureOptions = null)
8186
where TGrainStorage : IGrainStorage
8287
where TGrainStorageOptions : class, new()
@@ -85,6 +90,7 @@ public static ISiloBuilder AddMultitenantGrainStorage<TGrainStorage, TGrainStora
8590
name,
8691
(sevices, name) => { _ = addStorageProvider(builder, name); return sevices; },
8792
configureTenantOptions,
93+
getProviderParameters,
8894
configureOptions));
8995

9096
/// <summary>Configure silo to use a specific stream provider type as a named stream provider, with tenant separation</summary>
@@ -121,12 +127,14 @@ public static class ServiceCollectionExtensions
121127
/// 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
122128
/// </param>
123129
/// <param name="configureTenantOptions">Action to configure the supplied <typeparamref name="TGrainStorageOptions"/> based on the supplied tenant ID (e.g. use the tenant ID in a storage table name to realize separate storage per tenant)</param>
130+
/// <param name="getProviderParameters">Optional factory to transform or add constructor parameters for tenant grain storage providers; for details see <see cref="GrainStorageProviderParametersFactory{TGrainStorageOptions}"/><br />When omitted, only <typeparamref name="TGrainStorageOptions"/> is passed in</param>
124131
/// <param name="configureOptions">Action to configure the <see cref="MultitenantStorageOptions"/></param>
125132
/// <returns>The same instance of the <see cref="IServiceCollection"/> for chaining</returns>
126133
public static IServiceCollection AddMultitenantGrainStorageAsDefault<TGrainStorage, TGrainStorageOptions, TGrainStorageOptionsValidator>(
127134
this IServiceCollection services,
128135
Func<IServiceCollection, string, IServiceCollection> addStorageProvider,
129136
Action<TGrainStorageOptions, string>? configureTenantOptions = null,
137+
GrainStorageProviderParametersFactory<TGrainStorageOptions>? getProviderParameters = null,
130138
Action<OptionsBuilder<MultitenantStorageOptions>>? configureOptions = null)
131139
where TGrainStorage : IGrainStorage
132140
where TGrainStorageOptions : class, new()
@@ -135,6 +143,7 @@ public static IServiceCollection AddMultitenantGrainStorageAsDefault<TGrainStora
135143
ProviderConstants.DEFAULT_STORAGE_PROVIDER_NAME,
136144
addStorageProvider,
137145
configureTenantOptions,
146+
getProviderParameters,
138147
configureOptions);
139148

140149
/// <summary>
@@ -152,13 +161,15 @@ public static IServiceCollection AddMultitenantGrainStorageAsDefault<TGrainStora
152161
/// 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
153162
/// </param>
154163
/// <param name="configureTenantOptions">Action to configure the supplied <typeparamref name="TGrainStorageOptions"/> based on the supplied tenant ID (e.g. use the tenant ID in a storage table name to realize separate storage per tenant)</param>
164+
/// <param name="getProviderParameters">Optional factory to transform or add constructor parameters for tenant grain storage providers; for details see <see cref="GrainStorageProviderParametersFactory{TGrainStorageOptions}"/><br />When omitted, only <typeparamref name="TGrainStorageOptions"/> is passed in</param>
155165
/// <param name="configureOptions">Action to configure the <see cref="MultitenantStorageOptions"/></param>
156166
/// <returns>The same instance of the <see cref="IServiceCollection"/> for chaining</returns>
157167
public static IServiceCollection AddMultitenantGrainStorage<TGrainStorage, TGrainStorageOptions, TGrainStorageOptionsValidator>(
158168
this IServiceCollection services,
159169
string name,
160170
Func<IServiceCollection, string, IServiceCollection> addStorageProvider,
161171
Action<TGrainStorageOptions, string>? configureTenantOptions = null,
172+
GrainStorageProviderParametersFactory<TGrainStorageOptions>? getProviderParameters = null,
162173
Action<OptionsBuilder<MultitenantStorageOptions>>? configureOptions = null)
163174
where TGrainStorage : IGrainStorage
164175
where TGrainStorageOptions : class, new()
@@ -167,11 +178,29 @@ public static IServiceCollection AddMultitenantGrainStorage<TGrainStorage, TGrai
167178
ArgumentNullException.ThrowIfNull(addStorageProvider);
168179
return addStorageProvider(services, name).AddMultitenantGrainStorage(
169180
name,
170-
(services, name) => TenantGrainStorageFactoryFactory.Create<TGrainStorage, TGrainStorageOptions, TGrainStorageOptionsValidator>(services, name, configureTenantOptions),
181+
(services, name) => TenantGrainStorageFactoryFactory.Create<TGrainStorage, TGrainStorageOptions, TGrainStorageOptionsValidator>(services, name, configureTenantOptions, getProviderParameters),
171182
configureOptions);
172183
}
173184
}
174185

186+
/// <summary>
187+
/// Factory delegate, used to create parameters for a tenant grain storage provider constructor.<br />
188+
/// Allows to e.g. transform the options if the provider expects a different type than <typeparamref name="TGrainStorageOptions"/>,<br />
189+
/// or to retrieve an add extra parameters like <see cref="Configuration.ClusterOptions" />
190+
/// </summary>
191+
/// <typeparam name="TGrainStorageOptions">The provider-specific grain storage options type, e.g. Orleans.Storage.MemoryGrainStorageOptions or Orleans.Storage.AzureTableStorageOptions</typeparam>
192+
/// <param name="services">The silo services</param>
193+
/// <param name="providerName">The name - without the tenant id - of the provider; can be used to access named provider services that are not tenant specific</param>
194+
/// <param name="tenantProviderName">The name - including the tenant id - of the tenant provider; can be used to access named provider services that are tenant specific</param>
195+
/// <param name="options">The options to pass to the provider. Note that configureTenantOptions and options validation have already been executed on this</param>
196+
/// <returns>The tenant storage provider construction parameters to pass to DI. Don't include <paramref name="tenantProviderName"/> in these; it is added automatically</returns>
197+
public delegate object[] GrainStorageProviderParametersFactory<in TGrainStorageOptions>(
198+
IServiceProvider services,
199+
string providerName,
200+
string tenantProviderName,
201+
TGrainStorageOptions options
202+
);
203+
175204
public static class GrainExtensions
176205
{
177206
/// <summary>Get a tenant stream provider from within a <see cref="Grain"/>, for the tenant that this grain belongs to</summary>

0 commit comments

Comments
 (0)