diff --git a/samples/ConsoleApp/ConsoleApp.csproj b/samples/ConsoleApp/ConsoleApp.csproj index 5de1fbdff..e9ab58119 100644 --- a/samples/ConsoleApp/ConsoleApp.csproj +++ b/samples/ConsoleApp/ConsoleApp.csproj @@ -20,6 +20,7 @@ + diff --git a/samples/ConsoleAppMinimal/ConsoleAppMinimal.csproj b/samples/ConsoleAppMinimal/ConsoleAppMinimal.csproj index 5de1fbdff..e9ab58119 100644 --- a/samples/ConsoleAppMinimal/ConsoleAppMinimal.csproj +++ b/samples/ConsoleAppMinimal/ConsoleAppMinimal.csproj @@ -20,6 +20,7 @@ + diff --git a/samples/ExceptionPropertiesSample/ExceptionPropertiesSample.csproj b/samples/ExceptionPropertiesSample/ExceptionPropertiesSample.csproj index d24b2f473..ebc0466f2 100644 --- a/samples/ExceptionPropertiesSample/ExceptionPropertiesSample.csproj +++ b/samples/ExceptionPropertiesSample/ExceptionPropertiesSample.csproj @@ -22,7 +22,7 @@ + - diff --git a/samples/LargePayloadConsoleApp/LargePayloadConsoleApp.csproj b/samples/LargePayloadConsoleApp/LargePayloadConsoleApp.csproj index c7ac5d2ed..e1f3e5414 100644 --- a/samples/LargePayloadConsoleApp/LargePayloadConsoleApp.csproj +++ b/samples/LargePayloadConsoleApp/LargePayloadConsoleApp.csproj @@ -17,8 +17,7 @@ + - - diff --git a/samples/ScheduleConsoleApp/Orchestrators/StockPriceOrchestrator.cs b/samples/ScheduleConsoleApp/Orchestrators/StockPriceOrchestrator.cs index 3cc04a60f..18fb75322 100644 --- a/samples/ScheduleConsoleApp/Orchestrators/StockPriceOrchestrator.cs +++ b/samples/ScheduleConsoleApp/Orchestrators/StockPriceOrchestrator.cs @@ -18,7 +18,7 @@ public override async Task RunAsync(TaskOrchestrationContext context, st logger.LogInformation("Current price for {symbol} is ${price:F2}", symbol, currentPrice); - return $"Stock {symbol} price: ${currentPrice:F2} at {DateTime.UtcNow}"; + return $"Stock {symbol} price: ${currentPrice:F2} at {context.CurrentUtcDateTime}"; } catch (Exception ex) { diff --git a/samples/ScheduleConsoleApp/ScheduleConsoleApp.csproj b/samples/ScheduleConsoleApp/ScheduleConsoleApp.csproj index 5c356d93b..227ee159d 100644 --- a/samples/ScheduleConsoleApp/ScheduleConsoleApp.csproj +++ b/samples/ScheduleConsoleApp/ScheduleConsoleApp.csproj @@ -17,5 +17,6 @@ + diff --git a/samples/ScheduleWebApp/ScheduleWebApp.csproj b/samples/ScheduleWebApp/ScheduleWebApp.csproj index a505e3df4..3d426a2f7 100644 --- a/samples/ScheduleWebApp/ScheduleWebApp.csproj +++ b/samples/ScheduleWebApp/ScheduleWebApp.csproj @@ -18,5 +18,6 @@ + diff --git a/src/Analyzers/Orchestration/OrchestrationAnalyzer.cs b/src/Analyzers/Orchestration/OrchestrationAnalyzer.cs index a299d5777..bfffb57a2 100644 --- a/src/Analyzers/Orchestration/OrchestrationAnalyzer.cs +++ b/src/Analyzers/Orchestration/OrchestrationAnalyzer.cs @@ -27,11 +27,14 @@ public override void Initialize(AnalysisContext context) { KnownTypeSymbols knownSymbols = new(context.Compilation); - if (knownSymbols.FunctionOrchestrationAttribute == null || knownSymbols.FunctionNameAttribute == null || - knownSymbols.TaskOrchestratorInterface == null || - knownSymbols.DurableTaskRegistry == null) + // Check if at least one orchestration type can be detected + bool canAnalyzeDurableFunctions = knownSymbols.FunctionOrchestrationAttribute != null && knownSymbols.FunctionNameAttribute != null; + bool canAnalyzeTaskOrchestrator = knownSymbols.TaskOrchestratorInterface != null && knownSymbols.TaskOrchestrationContext != null; + bool canAnalyzeFuncOrchestrator = knownSymbols.DurableTaskRegistry != null; + + if (!canAnalyzeDurableFunctions && !canAnalyzeTaskOrchestrator && !canAnalyzeFuncOrchestrator) { - // symbols not available in this compilation, skip analysis + // no symbols available in this compilation, skip analysis return; } @@ -42,124 +45,133 @@ public override void Initialize(AnalysisContext context) } // look for Durable Functions Orchestrations - context.RegisterSyntaxNodeAction( - ctx => + if (canAnalyzeDurableFunctions) { - ctx.CancellationToken.ThrowIfCancellationRequested(); - - if (ctx.ContainingSymbol is not IMethodSymbol methodSymbol) + context.RegisterSyntaxNodeAction( + ctx => { - return; - } + ctx.CancellationToken.ThrowIfCancellationRequested(); - if (!methodSymbol.ContainsAttributeInAnyMethodArguments(knownSymbols.FunctionOrchestrationAttribute)) - { - return; - } + if (ctx.ContainingSymbol is not IMethodSymbol methodSymbol) + { + return; + } - if (!methodSymbol.TryGetSingleValueFromAttribute(knownSymbols.FunctionNameAttribute, out string functionName)) - { - return; - } + if (!methodSymbol.ContainsAttributeInAnyMethodArguments(knownSymbols.FunctionOrchestrationAttribute!)) + { + return; + } + + if (!methodSymbol.TryGetSingleValueFromAttribute(knownSymbols.FunctionNameAttribute!, out string functionName)) + { + return; + } - var rootMethodSyntax = (MethodDeclarationSyntax)ctx.Node; + var rootMethodSyntax = (MethodDeclarationSyntax)ctx.Node; - visitor.VisitDurableFunction(ctx.SemanticModel, rootMethodSyntax, methodSymbol, functionName, ctx.ReportDiagnostic); - }, - SyntaxKind.MethodDeclaration); + visitor.VisitDurableFunction(ctx.SemanticModel, rootMethodSyntax, methodSymbol, functionName, ctx.ReportDiagnostic); + }, + SyntaxKind.MethodDeclaration); + } // look for ITaskOrchestrator/TaskOrchestrator`2 Orchestrations - context.RegisterSyntaxNodeAction( - ctx => + if (canAnalyzeTaskOrchestrator) { - ctx.CancellationToken.ThrowIfCancellationRequested(); - - if (ctx.ContainingSymbol is not INamedTypeSymbol classSymbol) + context.RegisterSyntaxNodeAction( + ctx => { - return; - } + ctx.CancellationToken.ThrowIfCancellationRequested(); - bool implementsITaskOrchestrator = classSymbol.AllInterfaces.Any(i => i.Equals(knownSymbols.TaskOrchestratorInterface, SymbolEqualityComparer.Default)); - if (!implementsITaskOrchestrator) - { - return; - } + if (ctx.ContainingSymbol is not INamedTypeSymbol classSymbol) + { + return; + } - IEnumerable orchestrationMethods = classSymbol.GetMembers().OfType() - .Where(m => m.Parameters.Any(p => p.Type.Equals(knownSymbols.TaskOrchestrationContext, SymbolEqualityComparer.Default))); + bool implementsITaskOrchestrator = classSymbol.AllInterfaces.Any(i => i.Equals(knownSymbols.TaskOrchestratorInterface, SymbolEqualityComparer.Default)); + if (!implementsITaskOrchestrator) + { + return; + } - string functionName = classSymbol.Name; + IEnumerable orchestrationMethods = classSymbol.GetMembers().OfType() + .Where(m => m.Parameters.Any(p => p.Type.Equals(knownSymbols.TaskOrchestrationContext, SymbolEqualityComparer.Default))); - foreach (IMethodSymbol? methodSymbol in orchestrationMethods) - { - IEnumerable methodSyntaxes = methodSymbol.GetSyntaxNodes(); - foreach (MethodDeclarationSyntax rootMethodSyntax in methodSyntaxes) + string functionName = classSymbol.Name; + + foreach (IMethodSymbol? methodSymbol in orchestrationMethods) { - visitor.VisitTaskOrchestrator(ctx.SemanticModel, rootMethodSyntax, methodSymbol, functionName, ctx.ReportDiagnostic); + IEnumerable methodSyntaxes = methodSymbol.GetSyntaxNodes(); + foreach (MethodDeclarationSyntax rootMethodSyntax in methodSyntaxes) + { + visitor.VisitTaskOrchestrator(ctx.SemanticModel, rootMethodSyntax, methodSymbol, functionName, ctx.ReportDiagnostic); + } } - } - }, - SyntaxKind.ClassDeclaration); + }, + SyntaxKind.ClassDeclaration); + } // look for OrchestratorFunc Orchestrations - context.RegisterOperationAction( - ctx => + if (canAnalyzeFuncOrchestrator) { - if (ctx.Operation is not IInvocationOperation invocation) + context.RegisterOperationAction( + ctx => { - return; - } + if (ctx.Operation is not IInvocationOperation invocation) + { + return; + } - if (!SymbolEqualityComparer.Default.Equals(invocation.Type, knownSymbols.DurableTaskRegistry)) - { - return; - } + if (!SymbolEqualityComparer.Default.Equals(invocation.Type, knownSymbols.DurableTaskRegistry)) + { + return; + } - // there are 8 AddOrchestratorFunc overloads - if (invocation.TargetMethod.Name != "AddOrchestratorFunc") - { - return; - } + // there are 8 AddOrchestratorFunc overloads + if (invocation.TargetMethod.Name != "AddOrchestratorFunc") + { + return; + } - // all overloads have the parameter 'orchestrator', either as an Action or a Func - IArgumentOperation orchestratorArgument = invocation.Arguments.First(a => a.Parameter!.Name == "orchestrator"); - if (orchestratorArgument.Value is not IDelegateCreationOperation delegateCreationOperation) - { - return; - } + // all overloads have the parameter 'orchestrator', either as an Action or a Func + IArgumentOperation orchestratorArgument = invocation.Arguments.First(a => a.Parameter!.Name == "orchestrator"); + if (orchestratorArgument.Value is not IDelegateCreationOperation delegateCreationOperation) + { + return; + } - // obtains the method symbol from the delegate creation operation - IMethodSymbol? methodSymbol = null; - SyntaxNode? methodSyntax = null; - switch (delegateCreationOperation.Target) - { - case IAnonymousFunctionOperation lambdaOperation: - // use the containing symbol of the lambda (e.g. the class declaring it) as the method symbol - methodSymbol = ctx.ContainingSymbol as IMethodSymbol; - methodSyntax = delegateCreationOperation.Syntax; - break; - case IMethodReferenceOperation methodReferenceOperation: - // use the method reference as the method symbol - methodSymbol = methodReferenceOperation.Method; - methodSyntax = methodReferenceOperation.Method.DeclaringSyntaxReferences.First().GetSyntax(); - break; - default: - break; - } - - if (methodSymbol == null || methodSyntax == null) - { - return; - } + // obtains the method symbol from the delegate creation operation + IMethodSymbol? methodSymbol = null; + SyntaxNode? methodSyntax = null; + switch (delegateCreationOperation.Target) + { + case IAnonymousFunctionOperation _: + // use the containing symbol of the lambda (e.g. the class declaring it) as the method symbol + methodSymbol = ctx.ContainingSymbol as IMethodSymbol; + methodSyntax = delegateCreationOperation.Syntax; + break; + case IMethodReferenceOperation methodReferenceOperation: + // use the method reference as the method symbol + methodSymbol = methodReferenceOperation.Method; + methodSyntax = methodReferenceOperation.Method.DeclaringSyntaxReferences.First().GetSyntax(); + break; + default: + break; + } - // try to get the name of the orchestration from the method call, otherwise use the containing type name - IArgumentOperation nameArgument = invocation.Arguments.First(a => a.Parameter!.Name == "name"); - Optional name = nameArgument.GetConstantValueFromAttribute(ctx.Operation.SemanticModel!, ctx.CancellationToken); - string orchestrationName = name.Value?.ToString() ?? methodSymbol.Name; + if (methodSymbol == null || methodSyntax == null) + { + return; + } - visitor.VisitFuncOrchestrator(ctx.Operation.SemanticModel!, methodSyntax, methodSymbol, orchestrationName, ctx.ReportDiagnostic); - }, - OperationKind.Invocation); + // try to get the name of the orchestration from the method call, otherwise use the containing type name + IArgumentOperation nameArgument = invocation.Arguments.First(a => a.Parameter!.Name == "name"); + Optional name = nameArgument.GetConstantValueFromAttribute(ctx.Operation.SemanticModel!, ctx.CancellationToken); + string orchestrationName = name.Value?.ToString() ?? methodSymbol.Name; + + visitor.VisitFuncOrchestrator(ctx.Operation.SemanticModel!, methodSyntax, methodSymbol, orchestrationName, ctx.ReportDiagnostic); + }, + OperationKind.Invocation); + } }); } } diff --git a/test/Analyzers.Tests/Orchestration/DateTimeOrchestrationAnalyzerTests.cs b/test/Analyzers.Tests/Orchestration/DateTimeOrchestrationAnalyzerTests.cs index 4c5a1889e..4404dff85 100644 --- a/test/Analyzers.Tests/Orchestration/DateTimeOrchestrationAnalyzerTests.cs +++ b/test/Analyzers.Tests/Orchestration/DateTimeOrchestrationAnalyzerTests.cs @@ -453,6 +453,58 @@ public async Task FuncOrchestratorWithDateTimeOffsetHasDiag() await VerifyCS.VerifyDurableTaskCodeFixAsync(code, expected, fix); } + [Fact] + public async Task TaskOrchestratorSdkOnlyHasDiag() + { + // Tests that the analyzer works with SDK-only references (without Azure Functions assemblies) + string code = Wrapper.WrapTaskOrchestratorSdkOnly(@" +public class MyOrchestrator : TaskOrchestrator +{ + public override Task RunAsync(TaskOrchestrationContext context, string input) + { + return Task.FromResult({|#0:DateTime.Now|}); + } +} +"); + + string fix = Wrapper.WrapTaskOrchestratorSdkOnly(@" +public class MyOrchestrator : TaskOrchestrator +{ + public override Task RunAsync(TaskOrchestrationContext context, string input) + { + return Task.FromResult(context.CurrentUtcDateTime); + } +} +"); + + DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("RunAsync", "System.DateTime.Now", "MyOrchestrator"); + + await VerifyCS.VerifySdkOnlyCodeFixAsync(code, expected, fix); + } + + [Fact] + public async Task FuncOrchestratorSdkOnlyWithLambdaHasDiag() + { + // Tests that the analyzer works with SDK-only references (without Azure Functions assemblies) + string code = Wrapper.WrapFuncOrchestratorSdkOnly(@" +tasks.AddOrchestratorFunc(""HelloSequence"", context => +{ + return {|#0:DateTime.Now|}; +}); +"); + + string fix = Wrapper.WrapFuncOrchestratorSdkOnly(@" +tasks.AddOrchestratorFunc(""HelloSequence"", context => +{ + return context.CurrentUtcDateTime; +}); +"); + + DiagnosticResult expected = BuildDiagnostic().WithLocation(0).WithArguments("Main", "System.DateTime.Now", "HelloSequence"); + + await VerifyCS.VerifySdkOnlyCodeFixAsync(code, expected, fix); + } + static DiagnosticResult BuildDiagnostic() { return VerifyCS.Diagnostic(DateTimeOrchestrationAnalyzer.DiagnosticId); diff --git a/test/Analyzers.Tests/Verifiers/CSharpAnalyzerVerifier.Durable.cs b/test/Analyzers.Tests/Verifiers/CSharpAnalyzerVerifier.Durable.cs index 00253ef68..a68d8b5d1 100644 --- a/test/Analyzers.Tests/Verifiers/CSharpAnalyzerVerifier.Durable.cs +++ b/test/Analyzers.Tests/Verifiers/CSharpAnalyzerVerifier.Durable.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using Microsoft.CodeAnalysis.Diagnostics; @@ -30,4 +30,23 @@ public static async Task VerifyDurableTaskAnalyzerAsync(string source, Action + /// Runs analyzer test with SDK-only references (without Azure Functions assemblies). + /// Used to test orchestration detection in non-function scenarios. + /// + public static async Task VerifySdkOnlyAnalyzerAsync(string source, Action? configureTest = null, params DiagnosticResult[] expected) + { + Test test = new() + { + TestCode = source, + ReferenceAssemblies = References.SdkOnlyAssemblies, + }; + + test.ExpectedDiagnostics.AddRange(expected); + + configureTest?.Invoke(test); + + await test.RunAsync(CancellationToken.None); + } } diff --git a/test/Analyzers.Tests/Verifiers/CSharpCodeFixVerifier.Durable.cs b/test/Analyzers.Tests/Verifiers/CSharpCodeFixVerifier.Durable.cs index 139ee3c53..b69fae93b 100644 --- a/test/Analyzers.Tests/Verifiers/CSharpCodeFixVerifier.Durable.cs +++ b/test/Analyzers.Tests/Verifiers/CSharpCodeFixVerifier.Durable.cs @@ -1,4 +1,7 @@ -using Microsoft.CodeAnalysis.CodeFixes; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Testing; @@ -19,7 +22,7 @@ public static async Task VerifyDurableTaskAnalyzerAsync( await RunAsync(expected, new Test() { TestCode = source, - }, configureTest); + }, References.CommonAssemblies, configureTest); } public static Task VerifyDurableTaskCodeFixAsync( @@ -36,12 +39,59 @@ public static async Task VerifyDurableTaskCodeFixAsync( TestCode = source, FixedCode = fixedSource, }, - configureTest); + References.CommonAssemblies, configureTest); + } + + /// + /// Runs analyzer test with SDK-only references (without Azure Functions assemblies). + /// Used to test orchestration detection in non-function scenarios. + /// + public static Task VerifySdkOnlyAnalyzerAsync(string source, params DiagnosticResult[] expected) + { + return VerifySdkOnlyAnalyzerAsync(source, null, expected); + } + + /// + /// Runs analyzer test with SDK-only references (without Azure Functions assemblies). + /// Used to test orchestration detection in non-function scenarios. + /// + public static async Task VerifySdkOnlyAnalyzerAsync( + string source, Action? configureTest = null, params DiagnosticResult[] expected) + { + await RunAsync(expected, new Test() + { + TestCode = source, + }, References.SdkOnlyAssemblies, configureTest); + } + + /// + /// Runs code fix test with SDK-only references (without Azure Functions assemblies). + /// Used to test orchestration detection in non-function scenarios. + /// + public static Task VerifySdkOnlyCodeFixAsync( + string source, DiagnosticResult expected, string fixedSource, Action? configureTest = null) + { + return VerifySdkOnlyCodeFixAsync(source, [expected], fixedSource, configureTest); + } + + /// + /// Runs code fix test with SDK-only references (without Azure Functions assemblies). + /// Used to test orchestration detection in non-function scenarios. + /// + public static async Task VerifySdkOnlyCodeFixAsync( + string source, DiagnosticResult[] expected, string fixedSource, Action? configureTest = null) + { + await RunAsync(expected, new Test() + { + TestCode = source, + FixedCode = fixedSource, + }, + References.SdkOnlyAssemblies, configureTest); } - static async Task RunAsync(DiagnosticResult[] expected, Test test, Action? configureTest = null) + static async Task RunAsync(DiagnosticResult[] expected, Test test, ReferenceAssemblies referenceAssemblies, Action? configureTest = null) { - test.ReferenceAssemblies = References.CommonAssemblies; + test.ReferenceAssemblies = referenceAssemblies; test.ExpectedDiagnostics.AddRange(expected); configureTest?.Invoke(test); diff --git a/test/Analyzers.Tests/Verifiers/References.cs b/test/Analyzers.Tests/Verifiers/References.cs index 1df31e762..59de0ddae 100644 --- a/test/Analyzers.Tests/Verifiers/References.cs +++ b/test/Analyzers.Tests/Verifiers/References.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using Microsoft.CodeAnalysis.Testing; @@ -8,9 +8,16 @@ namespace Microsoft.DurableTask.Analyzers.Tests.Verifiers; public static class References { static readonly Lazy durableAssemblyReferences = new(() => BuildReferenceAssemblies()); + static readonly Lazy durableSdkOnlyReferences = new(() => BuildSdkOnlyReferenceAssemblies()); public static ReferenceAssemblies CommonAssemblies => durableAssemblyReferences.Value; + /// + /// Gets assembly references for non-function SDK tests (without Azure Functions assemblies). + /// Used to test orchestration detection in non-function scenarios. + /// + public static ReferenceAssemblies SdkOnlyAssemblies => durableSdkOnlyReferences.Value; + static ReferenceAssemblies BuildReferenceAssemblies() => ReferenceAssemblies.Net.Net60.AddPackages([ new PackageIdentity("Azure.Storage.Blobs", "12.17.0"), new PackageIdentity("Azure.Storage.Queues", "12.17.0"), @@ -20,4 +27,14 @@ static ReferenceAssemblies BuildReferenceAssemblies() => ReferenceAssemblies.Net new PackageIdentity("Microsoft.Azure.Functions.Worker.Extensions.DurableTask", "1.1.1"), new PackageIdentity("Microsoft.Data.SqlClient", "5.2.0"), ]); + + static ReferenceAssemblies BuildSdkOnlyReferenceAssemblies() => ReferenceAssemblies.Net.Net60.AddPackages([ + new PackageIdentity("Azure.Storage.Blobs", "12.17.0"), + new PackageIdentity("Azure.Storage.Queues", "12.17.0"), + new PackageIdentity("Azure.Data.Tables", "12.8.3"), + new PackageIdentity("Microsoft.Azure.Cosmos", "3.39.1"), + new PackageIdentity("Microsoft.Data.SqlClient", "5.2.0"), + new PackageIdentity("Microsoft.DurableTask.Abstractions", "1.3.0"), + new PackageIdentity("Microsoft.DurableTask.Worker", "1.3.0"), + ]); } diff --git a/test/Analyzers.Tests/Wrapper.cs b/test/Analyzers.Tests/Wrapper.cs index fb5df8708..a3b365230 100644 --- a/test/Analyzers.Tests/Wrapper.cs +++ b/test/Analyzers.Tests/Wrapper.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. +// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. namespace Microsoft.DurableTask.Analyzers.Tests; @@ -29,6 +29,43 @@ public static string WrapFuncOrchestrator(string code) return $@" {Usings()} +public class Program +{{ + public static void Main() + {{ + new ServiceCollection().AddDurableTaskWorker(builder => + {{ + builder.AddTasks(tasks => + {{ + {code} + }}); + }}); + }} +}} +"; + } + + /// + /// Wraps code for TaskOrchestrator tests without Azure Functions dependencies. + /// Used for SDK-only testing scenarios. + /// + public static string WrapTaskOrchestratorSdkOnly(string code) + { + return $@" +{UsingsForSdkOnly()} +{code} +"; + } + + /// + /// Wraps code for FuncOrchestrator tests without Azure Functions dependencies. + /// Used for SDK-only testing scenarios. + /// + public static string WrapFuncOrchestratorSdkOnly(string code) + { + return $@" +{UsingsForSdkOnly()} + public class Program {{ public static void Main() @@ -62,6 +99,24 @@ static string Usings() using Microsoft.DurableTask.Client; using Microsoft.DurableTask.Worker; using Microsoft.Extensions.DependencyInjection; +"; + } + + static string UsingsForSdkOnly() + { + return $@" +using Azure.Storage.Blobs; +using Azure.Storage.Queues; +using Azure.Data.Tables; +using System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Azure.Cosmos; +using Microsoft.Data.SqlClient; +using Microsoft.DurableTask; +using Microsoft.DurableTask.Worker; +using Microsoft.Extensions.DependencyInjection; "; } }