mirror of https://github.com/dapr/dotnet-sdk.git
Optional DI lifecycle change (#1408)
* Added mechanism to allow the service lifetime to be overridden from a singleton (default) to another lifetime Signed-off-by: Whit Waldo <whit.waldo@innovian.net> * Added unit tests - updated dependencies accordingly Signed-off-by: Whit Waldo <whit.waldo@innovian.net> * Added service lifetime to DaprClient as well Signed-off-by: Whit Waldo <whit.waldo@innovian.net> * Added update to DaprClient to pass service lifetime through Signed-off-by: Whit Waldo <whit.waldo@innovian.net> * Added documentation indicating how to register DaprWorkflowClient with different lifecycle options. Signed-off-by: Whit Waldo <whit.waldo@innovian.net> * Removed unnecessary line from csproj Signed-off-by: Whit Waldo <whit.waldo@innovian.net> * Simplified registrations Signed-off-by: Whit Waldo <whit.waldo@innovian.net> * Called out an important point about registrations Signed-off-by: Whit Waldo <whit.waldo@innovian.net> --------- Signed-off-by: Whit Waldo <whit.waldo@innovian.net>
This commit is contained in:
parent
f769eb1205
commit
ef04cad901
|
@ -28,8 +28,8 @@
|
|||
<PackageVersion Include="Microsoft.DurableTask.Worker.Grpc" Version="1.3.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="6.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="6.0.0" />
|
||||
|
|
7
all.sln
7
all.sln
|
@ -145,6 +145,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Jobs", "Jobs", "{D9697361-2
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JobsSample", "examples\Jobs\JobsSample\JobsSample.csproj", "{9CAF360E-5AD3-4C4F-89A0-327EEB70D673}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapr.Workflow.Test", "test\Dapr.Workflow.Test\Dapr.Workflow.Test.csproj", "{E90114C6-86FC-43B8-AE5C-D9273CF21FE4}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -377,6 +379,10 @@ Global
|
|||
{9CAF360E-5AD3-4C4F-89A0-327EEB70D673}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9CAF360E-5AD3-4C4F-89A0-327EEB70D673}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9CAF360E-5AD3-4C4F-89A0-327EEB70D673}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E90114C6-86FC-43B8-AE5C-D9273CF21FE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E90114C6-86FC-43B8-AE5C-D9273CF21FE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E90114C6-86FC-43B8-AE5C-D9273CF21FE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E90114C6-86FC-43B8-AE5C-D9273CF21FE4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -446,6 +452,7 @@ Global
|
|||
{BF9828E9-5597-4D42-AA6E-6E6C12214204} = {DD020B34-460F-455F-8D17-CF4A949F100B}
|
||||
{D9697361-232F-465D-A136-4561E0E88488} = {D687DDC4-66C5-4667-9E3A-FD8B78ECAA78}
|
||||
{9CAF360E-5AD3-4C4F-89A0-327EEB70D673} = {D9697361-232F-465D-A136-4561E0E88488}
|
||||
{E90114C6-86FC-43B8-AE5C-D9273CF21FE4} = {DD020B34-460F-455F-8D17-CF4A949F100B}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {65220BF2-EAE1-4CB2-AA58-EBE80768CB40}
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
---
|
||||
type: docs
|
||||
title: "DaprWorkflowClient usage"
|
||||
linkTitle: "DaprWorkflowClient usage"
|
||||
weight: 100000
|
||||
description: Essential tips and advice for using DaprWorkflowClient
|
||||
---
|
||||
|
||||
## Lifetime management
|
||||
|
||||
A `DaprWorkflowClient` holds access to networking resources in the form of TCP sockets used to communicate with the Dapr sidecar as well
|
||||
as other types used in the management and operation of Workflows. `DaprWorkflowClient` implements `IAsyncDisposable` to support eager
|
||||
cleanup of resources.
|
||||
|
||||
## Dependency Injection
|
||||
|
||||
The `AddDaprWorkflow()` method will register the Dapr workflow services with ASP.NET Core dependency injection. This method
|
||||
requires an options delegate that defines each of the workflows and activities you wish to register and use in your application.
|
||||
|
||||
{{% alert title="Note" color="primary" %}}
|
||||
|
||||
This method will attempt to register a `DaprClient` instance, but this will only work if it hasn't already been registered with another
|
||||
lifetime. For example, an earlier call to `AddDaprClient()` with a singleton lifetime will always use a singleton regardless of the
|
||||
lifetime chose for the workflow client. The `DaprClient` instance will be used to communicate with the Dapr sidecar and if it's not
|
||||
yet registered, the lifetime provided during the `AddDaprWorkflow()` registration will be used to register the `DaprWorkflowClient`
|
||||
as well as its own dependencies.
|
||||
|
||||
{{% /alert %}}
|
||||
|
||||
### Singleton Registration
|
||||
By default, the `AddDaprWorkflow` method will register the `DaprWorkflowClient` and associated services using a singleton lifetime. This means
|
||||
that the services will be instantiated only a single time.
|
||||
|
||||
The following is an example of how registration of the `DaprWorkflowClient` as it would appear in a typical `Program.cs` file:
|
||||
|
||||
```csharp
|
||||
builder.Services.AddDaprWorkflow(options => {
|
||||
options.RegisterWorkflow<YourWorkflow>();
|
||||
options.RegisterActivity<YourActivity>();
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
await app.RunAsync();
|
||||
```
|
||||
|
||||
### Scoped Registration
|
||||
|
||||
While this may generally be acceptable in your use case, you may instead wish to override the lifetime specified. This is done by passing a `ServiceLifetime`
|
||||
argument in `AddDaprWorkflow`. For example, you may wish to inject another scoped service into your ASP.NET Core processing pipeline
|
||||
that needs context used by the `DaprClient` that wouldn't be available if the former service were registered as a singleton.
|
||||
|
||||
This is demonstrated in the following example:
|
||||
|
||||
```csharp
|
||||
builder.Services.AddDaprWorkflow(options => {
|
||||
options.RegisterWorkflow<YourWorkflow>();
|
||||
options.RegisterActivity<YourActivity>();
|
||||
}, ServiceLifecycle.Scoped);
|
||||
|
||||
var app = builder.Build();
|
||||
await app.RunAsync();
|
||||
```
|
||||
|
||||
### Transient Registration
|
||||
|
||||
Finally, Dapr services can also be registered using a transient lifetime meaning that they will be initialized every time they're injected. This
|
||||
is demonstrated in the following example:
|
||||
|
||||
```csharp
|
||||
builder.Services.AddDaprWorkflow(options => {
|
||||
options.RegisterWorkflow<YourWorkflow>();
|
||||
options.RegisterActivity<YourActivity>();
|
||||
}, ServiceLifecycle.Transient);
|
||||
|
||||
var app = builder.Build();
|
||||
await app.RunAsync();
|
||||
```
|
|
@ -32,16 +32,32 @@ public static class DaprServiceCollectionExtensions
|
|||
/// </summary>
|
||||
/// <param name="services">The <see cref="IServiceCollection" />.</param>
|
||||
/// <param name="configure"></param>
|
||||
public static void AddDaprClient(this IServiceCollection services, Action<DaprClientBuilder>? configure = null)
|
||||
/// <param name="lifetime">The lifetime of the registered services.</param>
|
||||
public static void AddDaprClient(this IServiceCollection services, Action<DaprClientBuilder>? configure = null,
|
||||
ServiceLifetime lifetime = ServiceLifetime.Singleton)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services, nameof(services));
|
||||
|
||||
services.TryAddSingleton(serviceProvider =>
|
||||
var registration = new Func<IServiceProvider, DaprClient>((serviceProvider) =>
|
||||
{
|
||||
var builder = CreateDaprClientBuilder(serviceProvider);
|
||||
configure?.Invoke(builder);
|
||||
return builder.Build();
|
||||
});
|
||||
|
||||
switch (lifetime)
|
||||
{
|
||||
case ServiceLifetime.Scoped:
|
||||
services.TryAddScoped(registration);
|
||||
break;
|
||||
case ServiceLifetime.Transient:
|
||||
services.TryAddTransient(registration);
|
||||
break;
|
||||
case ServiceLifetime.Singleton:
|
||||
default:
|
||||
services.TryAddSingleton(registration);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -50,17 +66,32 @@ public static class DaprServiceCollectionExtensions
|
|||
/// </summary>
|
||||
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
|
||||
/// <param name="configure"></param>
|
||||
/// <param name="lifetime">The lifetime of the registered services.</param>
|
||||
public static void AddDaprClient(this IServiceCollection services,
|
||||
Action<IServiceProvider, DaprClientBuilder> configure)
|
||||
Action<IServiceProvider, DaprClientBuilder> configure, ServiceLifetime lifetime = ServiceLifetime.Singleton)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services, nameof(services));
|
||||
|
||||
services.TryAddSingleton(serviceProvider =>
|
||||
|
||||
var registration = new Func<IServiceProvider, DaprClient>((serviceProvider) =>
|
||||
{
|
||||
var builder = CreateDaprClientBuilder(serviceProvider);
|
||||
configure?.Invoke(serviceProvider, builder);
|
||||
return builder.Build();
|
||||
});
|
||||
|
||||
switch (lifetime)
|
||||
{
|
||||
case ServiceLifetime.Singleton:
|
||||
services.TryAddSingleton(registration);
|
||||
break;
|
||||
case ServiceLifetime.Scoped:
|
||||
services.TryAddScoped(registration);
|
||||
break;
|
||||
case ServiceLifetime.Transient:
|
||||
default:
|
||||
services.TryAddTransient(registration);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static DaprClientBuilder CreateDaprClientBuilder(IServiceProvider serviceProvider)
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
<!-- NuGet configuration -->
|
||||
<PropertyGroup>
|
||||
<!-- NOTE: Workflows targeted .NET 7 (whereas other packages did not, so we must continue until .NET 7 EOL). -->
|
||||
<TargetFrameworks>net6;net7;net8</TargetFrameworks>
|
||||
<Nullable>enable</Nullable>
|
||||
<PackageId>Dapr.Workflow</PackageId>
|
||||
<Title>Dapr Workflow Authoring SDK</Title>
|
||||
|
|
|
@ -29,25 +29,48 @@ public static class WorkflowServiceCollectionExtensions
|
|||
/// </summary>
|
||||
/// <param name="serviceCollection">The <see cref="IServiceCollection"/>.</param>
|
||||
/// <param name="configure">A delegate used to configure actor options and register workflow functions.</param>
|
||||
/// <param name="lifetime">The lifetime of the registered services.</param>
|
||||
public static IServiceCollection AddDaprWorkflow(
|
||||
this IServiceCollection serviceCollection,
|
||||
Action<WorkflowRuntimeOptions> configure)
|
||||
Action<WorkflowRuntimeOptions> configure,
|
||||
ServiceLifetime lifetime = ServiceLifetime.Singleton)
|
||||
{
|
||||
if (serviceCollection == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(serviceCollection));
|
||||
}
|
||||
|
||||
serviceCollection.TryAddSingleton<WorkflowRuntimeOptions>();
|
||||
serviceCollection.AddDaprClient(lifetime: lifetime);
|
||||
serviceCollection.AddHttpClient();
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete - keeping around temporarily - replaced by DaprWorkflowClient
|
||||
serviceCollection.TryAddSingleton<WorkflowEngineClient>();
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
serviceCollection.AddHostedService<WorkflowLoggingService>();
|
||||
serviceCollection.TryAddSingleton<DaprWorkflowClient>();
|
||||
serviceCollection.AddDaprClient();
|
||||
|
||||
|
||||
switch (lifetime)
|
||||
{
|
||||
case ServiceLifetime.Singleton:
|
||||
#pragma warning disable CS0618 // Type or member is obsolete - keeping around temporarily - replaced by DaprWorkflowClient
|
||||
serviceCollection.TryAddSingleton<WorkflowEngineClient>();
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
serviceCollection.TryAddSingleton<DaprWorkflowClient>();
|
||||
serviceCollection.TryAddSingleton<WorkflowRuntimeOptions>();
|
||||
break;
|
||||
case ServiceLifetime.Scoped:
|
||||
#pragma warning disable CS0618 // Type or member is obsolete - keeping around temporarily - replaced by DaprWorkflowClient
|
||||
serviceCollection.TryAddScoped<WorkflowEngineClient>();
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
serviceCollection.TryAddScoped<DaprWorkflowClient>();
|
||||
serviceCollection.TryAddScoped<WorkflowRuntimeOptions>();
|
||||
break;
|
||||
case ServiceLifetime.Transient:
|
||||
#pragma warning disable CS0618 // Type or member is obsolete - keeping around temporarily - replaced by DaprWorkflowClient
|
||||
serviceCollection.TryAddTransient<WorkflowEngineClient>();
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
serviceCollection.TryAddTransient<DaprWorkflowClient>();
|
||||
serviceCollection.TryAddTransient<WorkflowRuntimeOptions>();
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(lifetime), lifetime, null);
|
||||
}
|
||||
|
||||
serviceCollection.AddOptions<WorkflowRuntimeOptions>().Configure(configure);
|
||||
|
||||
//Register the factory and force resolution so the Durable Task client and worker can be registered
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="xunit" />
|
||||
<PackageReference Include="xunit.extensibility.core" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Dapr.Workflow\Dapr.Workflow.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,58 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Dapr.Workflow.Test;
|
||||
|
||||
public class WorkflowServiceCollectionExtensionsTests
|
||||
{
|
||||
[Fact]
|
||||
public void RegisterWorkflowClient_ShouldRegisterSingleton_WhenLifetimeIsSingleton()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
|
||||
services.AddDaprWorkflow(options => { }, ServiceLifetime.Singleton);
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
var daprWorkflowClient1 = serviceProvider.GetService<DaprWorkflowClient>();
|
||||
var daprWorkflowClient2 = serviceProvider.GetService<DaprWorkflowClient>();
|
||||
|
||||
Assert.NotNull(daprWorkflowClient1);
|
||||
Assert.NotNull(daprWorkflowClient2);
|
||||
|
||||
Assert.Same(daprWorkflowClient1, daprWorkflowClient2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RegisterWorkflowClient_ShouldRegisterScoped_WhenLifetimeIsScoped()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
|
||||
services.AddDaprWorkflow(options => { }, ServiceLifetime.Scoped);
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
await using var scope1 = serviceProvider.CreateAsyncScope();
|
||||
var daprWorkflowClient1 = scope1.ServiceProvider.GetService<DaprWorkflowClient>();
|
||||
|
||||
await using var scope2 = serviceProvider.CreateAsyncScope();
|
||||
var daprWorkflowClient2 = scope2.ServiceProvider.GetService<DaprWorkflowClient>();
|
||||
|
||||
Assert.NotNull(daprWorkflowClient1);
|
||||
Assert.NotNull(daprWorkflowClient2);
|
||||
Assert.NotSame(daprWorkflowClient1, daprWorkflowClient2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RegisterWorkflowClient_ShouldRegisterTransient_WhenLifetimeIsTransient()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
|
||||
services.AddDaprWorkflow(options => { }, ServiceLifetime.Transient);
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
var daprWorkflowClient1 = serviceProvider.GetService<DaprWorkflowClient>();
|
||||
var daprWorkflowClient2 = serviceProvider.GetService<DaprWorkflowClient>();
|
||||
|
||||
Assert.NotNull(daprWorkflowClient1);
|
||||
Assert.NotNull(daprWorkflowClient2);
|
||||
Assert.NotSame(daprWorkflowClient1, daprWorkflowClient2);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue