diff --git a/samples/AzureFunctionsApp/AzureFunctionsApp.csproj b/samples/AzureFunctionsApp/AzureFunctionsApp.csproj
index 25d824fa..727dd140 100644
--- a/samples/AzureFunctionsApp/AzureFunctionsApp.csproj
+++ b/samples/AzureFunctionsApp/AzureFunctionsApp.csproj
@@ -5,9 +5,6 @@
v4
Exe
enable
-
- false
- false
diff --git a/samples/AzureFunctionsApp/Program.cs b/samples/AzureFunctionsApp/Program.cs
index 3ec7a407..84357c1b 100644
--- a/samples/AzureFunctionsApp/Program.cs
+++ b/samples/AzureFunctionsApp/Program.cs
@@ -1,7 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using AzureFunctionsApp.Approval;
+using AzureFunctionsApp.Entities;
+using AzureFunctionsApp.Typed;
+using Microsoft.DurableTask;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
+
namespace AzureFunctionsApp;
public class Program
@@ -10,6 +16,20 @@ public static void Main()
{
IHost host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
+ .ConfigureServices(services =>
+ {
+ services.Configure(registry =>
+ {
+ registry
+ .AddOrchestrator()
+ .AddOrchestrator()
+ .AddActivity()
+ .AddActivity()
+ .AddEntity()
+ .AddEntity()
+ .AddEntity();
+ });
+ })
.Build();
host.Run();
diff --git a/samples/AzureFunctionsUnitTests/AzureFunctionsApp.Tests.csproj b/samples/AzureFunctionsUnitTests/AzureFunctionsApp.Tests.csproj
index e1e548ad..585e053a 100644
--- a/samples/AzureFunctionsUnitTests/AzureFunctionsApp.Tests.csproj
+++ b/samples/AzureFunctionsUnitTests/AzureFunctionsApp.Tests.csproj
@@ -4,7 +4,6 @@
net6.0;net8.0
enable
enable
-
false
true
diff --git a/src/Generators/DurableTaskSourceGenerator.cs b/src/Generators/DurableTaskSourceGenerator.cs
index 0116e568..6c515e26 100644
--- a/src/Generators/DurableTaskSourceGenerator.cs
+++ b/src/Generators/DurableTaskSourceGenerator.cs
@@ -272,6 +272,24 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
}
///
+ /// Determines if code generation should be skipped for Durable Functions scenarios.
+ /// Returns true if only entities exist and the runtime supports native class-based invocation,
+ /// since entities don't generate extension methods and the runtime handles their registration.
+ ///
+ static bool ShouldSkipGenerationForDurableFunctions(
+ bool supportsNativeClassBasedInvocation,
+ List orchestrators,
+ List activities,
+ ImmutableArray allEvents,
+ ImmutableArray allFunctions)
+ {
+ return supportsNativeClassBasedInvocation &&
+ orchestrators.Count == 0 &&
+ activities.Count == 0 &&
+ allEvents.Length == 0 &&
+ allFunctions.Length == 0;
+ }
+
/// Checks if a name is a valid C# identifier.
///
/// The name to validate.
@@ -322,6 +340,22 @@ static void Execute(
// Determine if we should generate Durable Functions specific code
bool isDurableFunctions = DetermineIsDurableFunctions(compilation, allFunctions, projectType);
+ // Check if the Durable Functions extension version supports native class-based invocation.
+ // This feature was introduced in PR #3229: https://github.com/Azure/azure-functions-durable-extension/pull/3229
+ // For the isolated worker extension (Microsoft.Azure.Functions.Worker.Extensions.DurableTask),
+ // native class-based invocation support was added in version 1.11.0.
+ bool supportsNativeClassBasedInvocation = false;
+ if (isDurableFunctions)
+ {
+ var durableFunctionsAssembly = compilation.ReferencedAssemblyNames.FirstOrDefault(
+ assembly => assembly.Name.Equals("Microsoft.Azure.Functions.Worker.Extensions.DurableTask", StringComparison.OrdinalIgnoreCase));
+
+ if (durableFunctionsAssembly != null && durableFunctionsAssembly.Version >= new Version(1, 11, 0))
+ {
+ supportsNativeClassBasedInvocation = true;
+ }
+ }
+
// Separate tasks into orchestrators, activities, and entities
// Skip tasks with invalid names to avoid generating invalid code
List orchestrators = new();
@@ -358,6 +392,16 @@ static void Execute(
return;
}
+ // With Durable Functions' native support for class-based invocations (PR #3229, v3.8.0+),
+ // we no longer generate [Function] definitions for class-based tasks when the runtime
+ // supports native invocation. If we have ONLY entities (no orchestrators, no activities,
+ // no events, no method-based functions), then there's nothing to generate for those
+ // scenarios since entities don't have extension methods.
+ if (ShouldSkipGenerationForDurableFunctions(supportsNativeClassBasedInvocation, orchestrators, activities, allEvents, allFunctions))
+ {
+ return;
+ }
+
StringBuilder sourceBuilder = new(capacity: found * 1024);
sourceBuilder.Append(@"//
#nullable enable
@@ -380,9 +424,11 @@ namespace Microsoft.DurableTask
{
public static class GeneratedDurableTaskExtensions
{");
- if (isDurableFunctions)
+
+ // Generate singleton orchestrator instances for older Durable Functions versions
+ // that don't have native class-based invocation support
+ if (isDurableFunctions && !supportsNativeClassBasedInvocation)
{
- // Generate a singleton orchestrator object instance that can be reused for all invocations.
foreach (DurableTaskTypeInfo orchestrator in orchestrators)
{
sourceBuilder.AppendLine($@"
@@ -390,11 +436,22 @@ public static class GeneratedDurableTaskExtensions
}
}
+ // Note: When targeting Azure Functions (Durable Functions scenarios) with native support
+ // for class-based invocations (PR #3229, v3.8.0+), we no longer generate [Function] attribute
+ // definitions for class-based orchestrators, activities, and entities (i.e., classes that
+ // implement ITaskOrchestrator, ITaskActivity, or ITaskEntity and are decorated with the
+ // [DurableTask] attribute). The Durable Functions runtime handles function registration
+ // for these types automatically in those scenarios. For older versions of Durable Functions
+ // (prior to v3.8.0) or non-Durable Functions scenarios (for example, ASP.NET Core using
+ // the Durable Task Scheduler), we continue to generate [Function] definitions.
+ // We always generate extension methods for type-safe invocation.
+
foreach (DurableTaskTypeInfo orchestrator in orchestrators)
{
- if (isDurableFunctions)
+ // Only generate [Function] definitions for Durable Functions if the runtime doesn't
+ // support native class-based invocation (versions prior to v3.8.0)
+ if (isDurableFunctions && !supportsNativeClassBasedInvocation)
{
- // Generate the function definition required to trigger orchestrators in Azure Functions
AddOrchestratorFunctionDeclaration(sourceBuilder, orchestrator);
}
@@ -406,18 +463,20 @@ public static class GeneratedDurableTaskExtensions
{
AddActivityCallMethod(sourceBuilder, activity);
- if (isDurableFunctions)
+ // Only generate [Function] definitions for Durable Functions if the runtime doesn't
+ // support native class-based invocation (versions prior to v3.8.0)
+ if (isDurableFunctions && !supportsNativeClassBasedInvocation)
{
- // Generate the function definition required to trigger activities in Azure Functions
AddActivityFunctionDeclaration(sourceBuilder, activity);
}
}
foreach (DurableTaskTypeInfo entity in entities)
{
- if (isDurableFunctions)
+ // Only generate [Function] definitions for Durable Functions if the runtime doesn't
+ // support native class-based invocation (versions prior to v3.8.0)
+ if (isDurableFunctions && !supportsNativeClassBasedInvocation)
{
- // Generate the function definition required to trigger entities in Azure Functions
AddEntityFunctionDeclaration(sourceBuilder, entity);
}
}
@@ -437,16 +496,19 @@ public static class GeneratedDurableTaskExtensions
AddEventSendMethod(sourceBuilder, eventInfo);
}
- if (isDurableFunctions)
+ // Note: The GeneratedActivityContext class is only needed for older versions of
+ // Durable Functions (prior to v3.8.0) that don't have native class-based invocation support.
+ // For v3.8.0+, the runtime handles class-based invocations natively.
+ if (isDurableFunctions && !supportsNativeClassBasedInvocation)
{
if (activities.Count > 0)
{
// Functions-specific helper class, which is only needed when
- // using the class-based syntax.
+ // using the class-based syntax with older Durable Functions versions.
AddGeneratedActivityContextClass(sourceBuilder);
}
}
- else
+ else if (!isDurableFunctions)
{
// ASP.NET Core-specific service registration methods
// Only generate if there are actually tasks to register
diff --git a/test/AzureFunctionsSmokeTests/AzureFunctionsSmokeTests.csproj b/test/AzureFunctionsSmokeTests/AzureFunctionsSmokeTests.csproj
index ccb904fc..c248ba8c 100644
--- a/test/AzureFunctionsSmokeTests/AzureFunctionsSmokeTests.csproj
+++ b/test/AzureFunctionsSmokeTests/AzureFunctionsSmokeTests.csproj
@@ -10,9 +10,6 @@
false
false
-
- false
- false
diff --git a/test/AzureFunctionsSmokeTests/Program.cs b/test/AzureFunctionsSmokeTests/Program.cs
index eddb3547..bd62e50b 100644
--- a/test/AzureFunctionsSmokeTests/Program.cs
+++ b/test/AzureFunctionsSmokeTests/Program.cs
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using Microsoft.DurableTask;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace AzureFunctionsSmokeTests;
@@ -11,6 +13,17 @@ public static void Main()
{
IHost host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
+ .ConfigureServices(services =>
+ {
+ services.Configure(registry =>
+ {
+ registry
+ .AddOrchestrator()
+ .AddOrchestrator()
+ .AddActivity()
+ .AddEntity();
+ });
+ })
.Build();
host.Run();
diff --git a/test/Generators.Tests/AzureFunctionsTests.cs b/test/Generators.Tests/AzureFunctionsTests.cs
index 3a02eeee..8ace515c 100644
--- a/test/Generators.Tests/AzureFunctionsTests.cs
+++ b/test/Generators.Tests/AzureFunctionsTests.cs
@@ -192,7 +192,8 @@ await TestHelpers.RunTestAsync(
///
/// Verifies that using the class-based activity syntax generates a
- /// extension method as well as an function definition.
+ /// extension method. With PR #3229, Durable Functions now natively handles class-based invocations,
+ /// so the generator no longer creates [Function] attribute definitions to avoid duplicates.
///
/// The activity input type.
/// The activity output type.
@@ -216,13 +217,6 @@ public class MyActivity : TaskActivity<{inputType}, {outputType}>
public override Task<{outputType}> RunAsync(TaskActivityContext context, {inputType} input) => Task.FromResult<{outputType}>(default!);
}}";
- // Build the expected InputParameter format (matches generator logic)
- string expectedInputParameter = inputType + " input";
- if (inputType.EndsWith('?'))
- {
- expectedInputParameter += " = default";
- }
-
string expectedOutput = TestHelpers.WrapAndFormat(
GeneratedClassName,
methodList: $@"
@@ -233,17 +227,7 @@ public class MyActivity : TaskActivity<{inputType}, {outputType}>
public static Task<{outputType}> CallMyActivityAsync(this TaskOrchestrationContext ctx, {inputType} input, TaskOptions? options = null)
{{
return ctx.CallActivityAsync<{outputType}>(""MyActivity"", input, options);
-}}
-
-[Function(nameof(MyActivity))]
-public static async Task<{outputType}> MyActivity([ActivityTrigger] {expectedInputParameter}, string instanceId, FunctionContext executionContext)
-{{
- ITaskActivity activity = ActivatorUtilities.GetServiceOrCreateInstance(executionContext.InstanceServices);
- TaskActivityContext context = new GeneratedActivityContext(""MyActivity"", instanceId);
- object? result = await activity.RunAsync(context, input);
- return ({outputType})result!;
-}}
-{TestHelpers.DeIndent(DurableTaskSourceGenerator.GetGeneratedActivityContextCode(), spacesToRemove: 8)}",
+}}",
isDurableFunctions: true);
await TestHelpers.RunTestAsync(
@@ -256,7 +240,8 @@ await TestHelpers.RunTestAsync(
///
/// Verifies that using the class-based syntax for authoring orchestrations generates
/// type-safe and
- /// extension methods as well as function triggers.
+ /// extension methods. With PR #3229, Durable Functions now natively handles class-based
+ /// invocations, so the generator no longer creates [Function] attribute definitions.
///
/// The activity input type.
/// The activity output type.
@@ -294,15 +279,6 @@ public class MyOrchestrator : TaskOrchestrator<{inputType}, {outputType}>
string expectedOutput = TestHelpers.WrapAndFormat(
GeneratedClassName,
methodList: $@"
-static readonly ITaskOrchestrator singletonMyOrchestrator = new MyNS.MyOrchestrator();
-
-[Function(nameof(MyOrchestrator))]
-public static Task<{outputType}> MyOrchestrator([OrchestrationTrigger] TaskOrchestrationContext context)
-{{
- return singletonMyOrchestrator.RunAsync(context, context.GetInput<{inputType}>())
- .ContinueWith(t => ({outputType})(t.Result ?? default({outputType})!), TaskContinuationOptions.ExecuteSynchronously);
-}}
-
///
/// Schedules a new instance of the orchestrator.
///
@@ -334,7 +310,8 @@ await TestHelpers.RunTestAsync(
///
/// Verifies that using the class-based syntax for authoring orchestrations generates
/// type-safe and
- /// extension methods as well as function triggers.
+ /// extension methods. With PR #3229, Durable Functions now natively handles class-based
+ /// invocations, so the generator no longer creates [Function] attribute definitions.
///
/// The activity input type.
/// The activity output type.
@@ -377,15 +354,6 @@ public abstract class MyOrchestratorBase : TaskOrchestrator<{inputType}, {output
string expectedOutput = TestHelpers.WrapAndFormat(
GeneratedClassName,
methodList: $@"
-static readonly ITaskOrchestrator singletonMyOrchestrator = new MyNS.MyOrchestrator();
-
-[Function(nameof(MyOrchestrator))]
-public static Task<{outputType}> MyOrchestrator([OrchestrationTrigger] TaskOrchestrationContext context)
-{{
- return singletonMyOrchestrator.RunAsync(context, context.GetInput<{inputType}>())
- .ContinueWith(t => ({outputType})(t.Result ?? default({outputType})!), TaskContinuationOptions.ExecuteSynchronously);
-}}
-
///
/// Schedules a new instance of the orchestrator.
///
@@ -415,8 +383,9 @@ await TestHelpers.RunTestAsync(
}
///
- /// Verifies that using the class-based syntax for authoring entities generates
- /// function triggers for Azure Functions.
+ /// Verifies that using the class-based syntax for authoring entities no longer generates
+ /// any code for Azure Functions. With PR #3229, Durable Functions now natively handles
+ /// class-based invocations. Entities don't have extension methods, so nothing is generated.
///
/// The entity state type.
[Theory]
@@ -439,26 +408,17 @@ public class MyEntity : TaskEntity<{stateType}>
}}
}}";
- string expectedOutput = TestHelpers.WrapAndFormat(
- GeneratedClassName,
- methodList: @"
-[Function(nameof(MyEntity))]
-public static Task MyEntity([EntityTrigger] TaskEntityDispatcher dispatcher)
-{
- return dispatcher.DispatchAsync();
-}",
- isDurableFunctions: true);
-
+ // With PR #3229, no code is generated for class-based entities in Durable Functions
await TestHelpers.RunTestAsync(
GeneratedFileName,
code,
- expectedOutput,
+ expectedOutputSource: null, // No output expected
isDurableFunctions: true);
}
///
- /// Verifies that using the class-based syntax for authoring entities with inheritance generates
- /// function triggers for Azure Functions.
+ /// Verifies that using the class-based syntax for authoring entities with inheritance no longer generates
+ /// any code for Azure Functions. With PR #3229, Durable Functions now natively handles class-based invocations.
///
/// The entity state type.
[Theory]
@@ -486,26 +446,17 @@ public abstract class MyEntityBase : TaskEntity<{stateType}>
}}
}}";
- string expectedOutput = TestHelpers.WrapAndFormat(
- GeneratedClassName,
- methodList: @"
-[Function(nameof(MyEntity))]
-public static Task MyEntity([EntityTrigger] TaskEntityDispatcher dispatcher)
-{
- return dispatcher.DispatchAsync();
-}",
- isDurableFunctions: true);
-
+ // With PR #3229, no code is generated for class-based entities in Durable Functions
await TestHelpers.RunTestAsync(
GeneratedFileName,
code,
- expectedOutput,
+ expectedOutputSource: null, // No output expected
isDurableFunctions: true);
}
///
- /// Verifies that using the class-based syntax for authoring entities with custom state types generates
- /// function triggers for Azure Functions.
+ /// Verifies that using the class-based syntax for authoring entities with custom state types no longer generates
+ /// any code for Azure Functions. With PR #3229, Durable Functions now natively handles class-based invocations.
///
[Fact]
public async Task Entities_ClassBasedSyntax_CustomStateType()
@@ -530,26 +481,19 @@ public class MyEntity : TaskEntity
}
}";
- string expectedOutput = TestHelpers.WrapAndFormat(
- GeneratedClassName,
- methodList: @"
-[Function(nameof(MyEntity))]
-public static Task MyEntity([EntityTrigger] TaskEntityDispatcher dispatcher)
-{
- return dispatcher.DispatchAsync();
-}",
- isDurableFunctions: true);
-
+ // With PR #3229, no code is generated for class-based entities in Durable Functions
await TestHelpers.RunTestAsync(
GeneratedFileName,
code,
- expectedOutput,
+ expectedOutputSource: null, // No output expected
isDurableFunctions: true);
}
///
/// Verifies that using the class-based syntax for authoring a mix of orchestrators, activities,
- /// and entities generates the appropriate function triggers for Azure Functions.
+ /// and entities generates the appropriate extension methods for Azure Functions.
+ /// With PR #3229, Durable Functions now natively handles class-based invocations,
+ /// so the generator no longer creates [Function] attribute definitions.
///
[Fact]
public async Task Mixed_OrchestratorActivityEntity_ClassBasedSyntax()
@@ -585,15 +529,6 @@ public class MyEntity : TaskEntity
string expectedOutput = TestHelpers.WrapAndFormat(
GeneratedClassName,
methodList: $@"
-static readonly ITaskOrchestrator singletonMyOrchestrator = new MyNS.MyOrchestrator();
-
-[Function(nameof(MyOrchestrator))]
-public static Task MyOrchestrator([OrchestrationTrigger] TaskOrchestrationContext context)
-{{
- return singletonMyOrchestrator.RunAsync(context, context.GetInput())
- .ContinueWith(t => (string)(t.Result ?? default(string)!), TaskContinuationOptions.ExecuteSynchronously);
-}}
-
///
/// Schedules a new instance of the orchestrator.
///
@@ -621,23 +556,7 @@ public static Task CallMyOrchestratorAsync(
public static Task CallMyActivityAsync(this TaskOrchestrationContext ctx, int input, TaskOptions? options = null)
{{
return ctx.CallActivityAsync(""MyActivity"", input, options);
-}}
-
-[Function(nameof(MyActivity))]
-public static async Task MyActivity([ActivityTrigger] int input, string instanceId, FunctionContext executionContext)
-{{
- ITaskActivity activity = ActivatorUtilities.GetServiceOrCreateInstance(executionContext.InstanceServices);
- TaskActivityContext context = new GeneratedActivityContext(""MyActivity"", instanceId);
- object? result = await activity.RunAsync(context, input);
- return (string)result!;
-}}
-
-[Function(nameof(MyEntity))]
-public static Task MyEntity([EntityTrigger] TaskEntityDispatcher dispatcher)
-{{
- return dispatcher.DispatchAsync();
-}}
-{TestHelpers.DeIndent(DurableTaskSourceGenerator.GetGeneratedActivityContextCode(), spacesToRemove: 8)}",
+}}",
isDurableFunctions: true);
await TestHelpers.RunTestAsync(
diff --git a/test/Generators.Tests/ProjectTypeConfigurationTests.cs b/test/Generators.Tests/ProjectTypeConfigurationTests.cs
index bcac0a3d..f559baf8 100644
--- a/test/Generators.Tests/ProjectTypeConfigurationTests.cs
+++ b/test/Generators.Tests/ProjectTypeConfigurationTests.cs
@@ -110,6 +110,8 @@ public Task ExplicitFunctionsMode_WithoutFunctionsReference_GeneratesFunctionsCo
{
// Test that explicit "Functions" configuration generates Functions code
// even without Functions references
+ // Note: With Durable Functions v1.11.0+, only extension methods are generated,
+ // not [Function] definitions, as the runtime handles class-based tasks natively
string code = @"
using System.Threading.Tasks;
using Microsoft.DurableTask;
@@ -120,7 +122,7 @@ class MyActivity : TaskActivity
public override Task RunAsync(TaskActivityContext context, int input) => Task.FromResult(string.Empty);
}";
- // With explicit "Functions", we should get Functions code (Activity trigger function)
+ // With explicit "Functions" and version >= 1.11.0, we only get extension methods
string expectedOutput = TestHelpers.WrapAndFormat(
GeneratedClassName,
methodList: @"
@@ -131,28 +133,6 @@ class MyActivity : TaskActivity
public static Task CallMyActivityAsync(this TaskOrchestrationContext ctx, int input, TaskOptions? options = null)
{
return ctx.CallActivityAsync(""MyActivity"", input, options);
-}
-
-[Function(nameof(MyActivity))]
-public static async Task MyActivity([ActivityTrigger] int input, string instanceId, FunctionContext executionContext)
-{
- ITaskActivity activity = ActivatorUtilities.GetServiceOrCreateInstance(executionContext.InstanceServices);
- TaskActivityContext context = new GeneratedActivityContext(""MyActivity"", instanceId);
- object? result = await activity.RunAsync(context, input);
- return (string)result!;
-}
-
-sealed class GeneratedActivityContext : TaskActivityContext
-{
- public GeneratedActivityContext(TaskName name, string instanceId)
- {
- this.Name = name;
- this.InstanceId = instanceId;
- }
-
- public override TaskName Name { get; }
-
- public override string InstanceId { get; }
}",
isDurableFunctions: true);
@@ -170,6 +150,8 @@ public GeneratedActivityContext(TaskName name, string instanceId)
public Task ExplicitFunctionsMode_OrchestratorTest()
{
// Test that "Functions" mode generates orchestrator Functions code
+ // Note: With Durable Functions v1.11.0+, only extension methods are generated,
+ // not [Function] definitions, as the runtime handles class-based tasks natively
string code = @"
using System.Threading.Tasks;
using Microsoft.DurableTask;
@@ -183,15 +165,6 @@ class MyOrchestrator : TaskOrchestrator
string expectedOutput = TestHelpers.WrapAndFormat(
GeneratedClassName,
methodList: @"
-static readonly ITaskOrchestrator singletonMyOrchestrator = new MyOrchestrator();
-
-[Function(nameof(MyOrchestrator))]
-public static Task MyOrchestrator([OrchestrationTrigger] TaskOrchestrationContext context)
-{
- return singletonMyOrchestrator.RunAsync(context, context.GetInput())
- .ContinueWith(t => (string)(t.Result ?? default(string)!), TaskContinuationOptions.ExecuteSynchronously);
-}
-
///
/// Schedules a new instance of the orchestrator.
///
@@ -225,6 +198,8 @@ public static Task CallMyOrchestratorAsync(
public Task AutoMode_WithFunctionsReference_GeneratesFunctionsCode()
{
// Test that "Auto" mode falls back to auto-detection
+ // Note: With Durable Functions v1.11.0+, only extension methods are generated,
+ // not [Function] definitions, as the runtime handles class-based tasks natively
string code = @"
using System.Threading.Tasks;
using Microsoft.DurableTask;
@@ -245,28 +220,6 @@ class MyActivity : TaskActivity
public static Task CallMyActivityAsync(this TaskOrchestrationContext ctx, int input, TaskOptions? options = null)
{
return ctx.CallActivityAsync(""MyActivity"", input, options);
-}
-
-[Function(nameof(MyActivity))]
-public static async Task MyActivity([ActivityTrigger] int input, string instanceId, FunctionContext executionContext)
-{
- ITaskActivity activity = ActivatorUtilities.GetServiceOrCreateInstance(executionContext.InstanceServices);
- TaskActivityContext context = new GeneratedActivityContext(""MyActivity"", instanceId);
- object? result = await activity.RunAsync(context, input);
- return (string)result!;
-}
-
-sealed class GeneratedActivityContext : TaskActivityContext
-{
- public GeneratedActivityContext(TaskName name, string instanceId)
- {
- this.Name = name;
- this.InstanceId = instanceId;
- }
-
- public override TaskName Name { get; }
-
- public override string InstanceId { get; }
}",
isDurableFunctions: true);
@@ -323,6 +276,8 @@ internal static DurableTaskRegistry AddAllGeneratedTasks(this DurableTaskRegistr
public Task UnrecognizedMode_WithFunctionsReference_FallsBackToAutoDetection()
{
// Test that unrecognized values fall back to auto-detection
+ // Note: With Durable Functions v1.11.0+, only extension methods are generated,
+ // not [Function] definitions, as the runtime handles class-based tasks natively
string code = @"
using System.Threading.Tasks;
using Microsoft.DurableTask;
@@ -343,28 +298,6 @@ class MyActivity : TaskActivity
public static Task CallMyActivityAsync(this TaskOrchestrationContext ctx, int input, TaskOptions? options = null)
{
return ctx.CallActivityAsync(""MyActivity"", input, options);
-}
-
-[Function(nameof(MyActivity))]
-public static async Task MyActivity([ActivityTrigger] int input, string instanceId, FunctionContext executionContext)
-{
- ITaskActivity activity = ActivatorUtilities.GetServiceOrCreateInstance(executionContext.InstanceServices);
- TaskActivityContext context = new GeneratedActivityContext(""MyActivity"", instanceId);
- object? result = await activity.RunAsync(context, input);
- return (string)result!;
-}
-
-sealed class GeneratedActivityContext : TaskActivityContext
-{
- public GeneratedActivityContext(TaskName name, string instanceId)
- {
- this.Name = name;
- this.InstanceId = instanceId;
- }
-
- public override TaskName Name { get; }
-
- public override string InstanceId { get; }
}",
isDurableFunctions: true);
@@ -421,6 +354,8 @@ internal static DurableTaskRegistry AddAllGeneratedTasks(this DurableTaskRegistr
public Task NullProjectType_WithFunctionsReference_GeneratesFunctionsCode()
{
// Test that null projectType (default) with Functions reference falls back to auto-detection
+ // Note: With Durable Functions v1.11.0+, only extension methods are generated,
+ // not [Function] definitions, as the runtime handles class-based tasks natively
string code = @"
using System.Threading.Tasks;
using Microsoft.DurableTask;
@@ -441,28 +376,6 @@ class MyActivity : TaskActivity
public static Task CallMyActivityAsync(this TaskOrchestrationContext ctx, int input, TaskOptions? options = null)
{
return ctx.CallActivityAsync(""MyActivity"", input, options);
-}
-
-[Function(nameof(MyActivity))]
-public static async Task MyActivity([ActivityTrigger] int input, string instanceId, FunctionContext executionContext)
-{
- ITaskActivity activity = ActivatorUtilities.GetServiceOrCreateInstance(executionContext.InstanceServices);
- TaskActivityContext context = new GeneratedActivityContext(""MyActivity"", instanceId);
- object? result = await activity.RunAsync(context, input);
- return (string)result!;
-}
-
-sealed class GeneratedActivityContext : TaskActivityContext
-{
- public GeneratedActivityContext(TaskName name, string instanceId)
- {
- this.Name = name;
- this.InstanceId = instanceId;
- }
-
- public override TaskName Name { get; }
-
- public override string InstanceId { get; }
}",
isDurableFunctions: true);
diff --git a/test/Generators.Tests/Utils/TestHelpers.cs b/test/Generators.Tests/Utils/TestHelpers.cs
index 92f3db86..f43ab2c1 100644
--- a/test/Generators.Tests/Utils/TestHelpers.cs
+++ b/test/Generators.Tests/Utils/TestHelpers.cs
@@ -15,7 +15,7 @@ static class TestHelpers
public static Task RunTestAsync(
string expectedFileName,
string inputSource,
- string expectedOutputSource,
+ string? expectedOutputSource,
bool isDurableFunctions) where TSourceGenerator : IIncrementalGenerator, new()
{
return RunTestAsync(
@@ -38,10 +38,6 @@ public static Task RunTestAsync(
TestState =
{
Sources = { inputSource },
- GeneratedSources =
- {
- (typeof(TSourceGenerator), expectedFileName, SourceText.From(expectedOutputSource, Encoding.UTF8, SourceHashAlgorithm.Sha256)),
- },
AdditionalReferences =
{
// Durable Task SDK
@@ -50,6 +46,13 @@ public static Task RunTestAsync(
},
};
+ // Only add generated source if expectedOutputSource is not null
+ if (expectedOutputSource != null)
+ {
+ test.TestState.GeneratedSources.Add(
+ (typeof(TSourceGenerator), expectedFileName, SourceText.From(expectedOutputSource, Encoding.UTF8, SourceHashAlgorithm.Sha256)));
+ }
+
if (isDurableFunctions)
{
// Durable Functions code generation is triggered by the presence of the