// ------------------------------------------------------------------------ // Copyright 2022 The Dapr Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // http://www.apache.org/licenses/LICENSE-2.0 // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ------------------------------------------------------------------------ namespace Dapr.Workflow { using System; using Grpc.Net.Client; using Microsoft.DurableTask.Client; using Microsoft.DurableTask.Worker; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using System.Net.Http; using Dapr; /// /// Contains extension methods for using Dapr Workflow with dependency injection. /// public static class WorkflowServiceCollectionExtensions { /// /// Adds Dapr Workflow support to the service collection. /// /// The . /// A delegate used to configure actor options and register workflow functions. public static IServiceCollection AddDaprWorkflow( this IServiceCollection serviceCollection, Action configure) { if (serviceCollection == null) { throw new ArgumentNullException(nameof(serviceCollection)); } serviceCollection.TryAddSingleton(); #pragma warning disable CS0618 // Type or member is obsolete - keeping around temporarily - replaced by DaprWorkflowClient serviceCollection.TryAddSingleton(); #pragma warning restore CS0618 // Type or member is obsolete serviceCollection.AddHostedService(); serviceCollection.TryAddSingleton(); serviceCollection.AddDaprClient(); serviceCollection.AddDaprWorkflowClient(); serviceCollection.AddOptions().Configure(configure); serviceCollection.AddDurableTaskWorker(builder => { WorkflowRuntimeOptions options = new(); configure?.Invoke(options); if (TryGetGrpcAddress(out string address)) { var daprApiToken = DaprDefaults.GetDefaultDaprApiToken(); if (!string.IsNullOrEmpty(daprApiToken)) { var client = new HttpClient(); client.DefaultRequestHeaders.Add("Dapr-Api-Token", daprApiToken); builder.UseGrpc(CreateChannel(address, client)); } else { builder.UseGrpc(address); } } else { builder.UseGrpc(); } builder.AddTasks(registry => options.AddWorkflowsAndActivitiesToRegistry(registry)); }); return serviceCollection; } /// /// Adds Dapr Workflow client support to the service collection. /// /// /// Use this extension method if you want to use in your app /// but don't wish to define any workflows or activities. /// /// The . public static IServiceCollection AddDaprWorkflowClient(this IServiceCollection services) { services.TryAddSingleton(); services.AddDurableTaskClient(builder => { if (TryGetGrpcAddress(out string address)) { var daprApiToken = DaprDefaults.GetDefaultDaprApiToken(); if (!string.IsNullOrEmpty(daprApiToken)) { var client = new HttpClient(); client.DefaultRequestHeaders.Add("Dapr-Api-Token", daprApiToken); builder.UseGrpc(CreateChannel(address, client)); } else { builder.UseGrpc(address); } } else { builder.UseGrpc(); } builder.RegisterDirectly(); }); return services; } static bool TryGetGrpcAddress(out string address) { // TODO: Ideally we should be using DaprDefaults.cs for this. However, there are two blockers: // 1. DaprDefaults.cs uses 127.0.0.1 instead of localhost, which prevents testing with Dapr on WSL2 and the app on Windows // 2. DaprDefaults.cs doesn't compile when the project has C# nullable reference types enabled. // If the above issues are fixed (ensuring we don't regress anything) we should switch to using the logic in DaprDefaults.cs. var daprEndpoint = DaprDefaults.GetDefaultGrpcEndpoint(); if (!String.IsNullOrEmpty(daprEndpoint)) { address = daprEndpoint; return true; } var daprPortStr = Environment.GetEnvironmentVariable("DAPR_GRPC_PORT"); if (int.TryParse(daprPortStr, out int daprGrpcPort)) { // There is a bug in the Durable Task SDK that requires us to change the format of the address // depending on the version of .NET that we're targeting. For now, we work around this manually. #if NET6_0_OR_GREATER address = $"http://localhost:{daprGrpcPort}"; #else address = $"localhost:{daprGrpcPort}"; #endif return true; } address = string.Empty; return false; } static GrpcChannel CreateChannel(string address, HttpClient client) { GrpcChannelOptions options = new() { HttpClient = client}; var daprEndpoint = DaprDefaults.GetDefaultGrpcEndpoint(); if (!String.IsNullOrEmpty(daprEndpoint)) { return GrpcChannel.ForAddress(daprEndpoint, options); } var daprPortStr = Environment.GetEnvironmentVariable("DAPR_GRPC_PORT"); if (int.TryParse(daprPortStr, out int daprGrpcPort)) { // If there is no address passed in, we default to localhost if (String.IsNullOrEmpty(address)) { // There is a bug in the Durable Task SDK that requires us to change the format of the address // depending on the version of .NET that we're targeting. For now, we work around this manually. #if NET6_0_OR_GREATER address = $"http://localhost:{daprGrpcPort}"; #else address = $"localhost:{daprGrpcPort}"; #endif } } return GrpcChannel.ForAddress(address, options); } } }