Skip to content

Commit 80b5eef

Browse files
authored
Merge branch 'main' into copilot/add-roslyn-analyzer-function-check
2 parents 864d8ae + 023f0ac commit 80b5eef

File tree

9 files changed

+376
-26
lines changed

9 files changed

+376
-26
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ To get started, add the [Microsoft.Azure.Functions.Worker.Extensions.DurableTask
3535
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" Version="1.2.2" />
3636
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.0.13" />
3737
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.7.0" OutputItemType="Analyzer" />
38-
<PackageReference Include="Microsoft.DurableTask.Generators" Version="1.0.0-preview.1" OutputItemType="Analyzer" />
38+
<PackageReference Include="Microsoft.DurableTask.Generators" Version="1.0.0" OutputItemType="Analyzer" />
3939
</ItemGroup>
4040
```
4141

samples/AzureFunctionsApp/AzureFunctionsApp.csproj

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,18 @@
55
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
66
<OutputType>Exe</OutputType>
77
<Nullable>enable</Nullable>
8+
<!-- Disable SDK's source generation to allow reflection-based discovery of source-generated functions -->
9+
<FunctionsEnableExecutorSourceGen>false</FunctionsEnableExecutorSourceGen>
10+
<FunctionsEnableWorkerIndexing>false</FunctionsEnableWorkerIndexing>
811
</PropertyGroup>
912

1013
<ItemGroup>
1114
<PackageReference Include="Microsoft.Azure.Functions.Worker" />
1215
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" />
1316
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" />
1417
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" OutputItemType="Analyzer" />
15-
<PackageReference Include="Microsoft.DurableTask.Generators" OutputItemType="Analyzer" />
18+
<!-- Reference the source generator project directly for local development -->
19+
<ProjectReference Include="..\..\src\Generators\Generators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
1620
</ItemGroup>
1721

1822
<ItemGroup>

samples/AzureFunctionsApp/Entities/Counter.cs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ namespace AzureFunctionsApp.Entities;
2929
/// Example on how to dispatch to an entity which directly implements TaskEntity<TState>. Using TaskEntity<TState> gives
3030
/// the added benefit of being able to use DI. When using TaskEntity<TState>, state is deserialized to the "State"
3131
/// property. No other properties on this type will be serialized/deserialized.
32+
///
33+
/// Source generators are used to generate the [Function] method with [EntityTrigger] binding automatically.
34+
/// The generated function will be named "Counter" (based on the class name or the DurableTask attribute value).
3235
/// </summary>
36+
[DurableTask(nameof(Counter))]
3337
public class Counter : TaskEntity<int>
3438
{
3539
readonly ILogger logger;
@@ -49,19 +53,21 @@ public int Add(int input)
4953

5054
public void Reset() => this.State = 0;
5155

52-
[Function("Counter")]
53-
public Task DispatchAsync([EntityTrigger] TaskEntityDispatcher dispatcher)
54-
{
55-
// Can dispatch to a TaskEntity<TState> by passing a instance.
56-
return dispatcher.DispatchAsync(this);
57-
}
56+
// Note: The [Function("Counter")] method is now auto-generated by the source generator.
57+
// The generated code will look like:
58+
// [Function(nameof(Counter))]
59+
// public static Task Counter([EntityTrigger] TaskEntityDispatcher dispatcher)
60+
// {
61+
// return dispatcher.DispatchAsync<Counter>();
62+
// }
5863

5964
[Function("Counter_Alt")]
6065
public static Task DispatchStaticAsync([EntityTrigger] TaskEntityDispatcher dispatcher)
6166
{
62-
// Can also dispatch to a TaskEntity<TState> by using a static method.
63-
// However, this is a different entity ID - "counter_alt" and not "counter". Even though it uses the same
64-
// entity implementation, the function attribute has a different name, which determines the entity ID.
67+
// This is kept as a manual example showing how to create an alternative entity function
68+
// with a different name. This creates a separate entity ID "counter_alt" vs "counter".
69+
// Even though it uses the same entity implementation, the function attribute has a different name,
70+
// which determines the entity ID.
6571
return dispatcher.DispatchAsync<Counter>();
6672
}
6773
}

samples/AzureFunctionsApp/Entities/Lifetime.cs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Net;
55
using Microsoft.Azure.Functions.Worker;
66
using Microsoft.Azure.Functions.Worker.Http;
7+
using Microsoft.DurableTask;
78
using Microsoft.DurableTask.Client;
89
using Microsoft.DurableTask.Client.Entities;
910
using Microsoft.DurableTask.Entities;
@@ -16,7 +17,10 @@ namespace AzureFunctionsApp.Entities;
1617
/// is considered deleted when <see cref="TaskEntity{TState}.State"/> is <c>null</c> at the end of an operation. It
1718
/// is also possible to design an entity which remains stateless by always returning <c>null</c> from
1819
/// <see cref="InitializeState"/> and never assigning a non-null state.
20+
///
21+
/// Source generators are used to generate the [Function] method with [EntityTrigger] binding automatically.
1922
/// </summary>
23+
[DurableTask(nameof(Lifetime))]
2024
public class Lifetime : TaskEntity<MyState>
2125
{
2226
readonly ILogger logger;
@@ -34,14 +38,13 @@ public Lifetime(ILogger<Lifetime> logger)
3438
/// </summary>
3539
protected override bool AllowStateDispatch => base.AllowStateDispatch;
3640

37-
// NOTE: when using TaskEntity<TState>, you cannot use "RunAsync" as the entity trigger name as this conflicts
38-
// with the base class method 'RunAsync'.
39-
[Function(nameof(Lifetime))]
40-
public Task DispatchAsync([EntityTrigger] TaskEntityDispatcher dispatcher)
41-
{
42-
this.logger.LogInformation("Dispatching entity");
43-
return dispatcher.DispatchAsync(this);
44-
}
41+
// Note: The [Function(nameof(Lifetime))] method is now auto-generated by the source generator.
42+
// The generated code will look like:
43+
// [Function(nameof(Lifetime))]
44+
// public static Task Lifetime([EntityTrigger] TaskEntityDispatcher dispatcher)
45+
// {
46+
// return dispatcher.DispatchAsync<Lifetime>();
47+
// }
4548

4649
public MyState Get() => this.State;
4750

samples/AzureFunctionsApp/Entities/User.cs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ public record UserUpdate(string? Name, int? Age);
1818

1919
/// <summary>
2020
/// This sample demonstrates how to bind to <see cref="TaskEntityContext"/> as well as dispatch to orchestrations.
21+
///
22+
/// Source generators are used to generate the [Function] method with [EntityTrigger] binding automatically.
23+
/// The generated function will be named "User" (based on the class name or the DurableTask attribute value).
2124
/// </summary>
25+
[DurableTask(nameof(User))]
2226
public class UserEntity : TaskEntity<User>
2327
{
2428
readonly ILogger logger;
@@ -68,12 +72,13 @@ public void Greet(TaskEntityContext context, string? message = null)
6872
// this.Context.ScheduleNewOrchestration(nameof(Greeting.GreetingOrchestration), input);
6973
}
7074

71-
[Function(nameof(User))]
72-
public Task DispatchAsync([EntityTrigger] TaskEntityDispatcher dispatcher)
73-
{
74-
// Can dispatch to a TaskEntity<TState> by passing a instance.
75-
return dispatcher.DispatchAsync(this);
76-
}
75+
// Note: The [Function(nameof(User))] method is now auto-generated by the source generator.
76+
// The generated code will look like:
77+
// [Function(nameof(User))]
78+
// public static Task User([EntityTrigger] TaskEntityDispatcher dispatcher)
79+
// {
80+
// return dispatcher.DispatchAsync<UserEntity>();
81+
// }
7782

7883
protected override User InitializeState(TaskEntityOperation entityOperation)
7984
{

src/Generators/DurableTaskSourceGenerator.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,15 @@ public static class GeneratedDurableTaskExtensions
270270
}
271271
}
272272

273+
foreach (DurableTaskTypeInfo entity in entities)
274+
{
275+
if (isDurableFunctions)
276+
{
277+
// Generate the function definition required to trigger entities in Azure Functions
278+
AddEntityFunctionDeclaration(sourceBuilder, entity);
279+
}
280+
}
281+
273282
// Activity function triggers are supported for code-gen (but not orchestration triggers)
274283
IEnumerable<DurableFunction> activityTriggers = allFunctions.Where(
275284
df => df.Kind == DurableFunctionKind.Activity);
@@ -316,6 +325,9 @@ static void AddOrchestratorFunctionDeclaration(StringBuilder sourceBuilder, Dura
316325
static void AddOrchestratorCallMethod(StringBuilder sourceBuilder, DurableTaskTypeInfo orchestrator)
317326
{
318327
sourceBuilder.AppendLine($@"
328+
/// <summary>
329+
/// Schedules a new instance of the <see cref=""{orchestrator.TypeName}""/> orchestrator.
330+
/// </summary>
319331
/// <inheritdoc cref=""IOrchestrationSubmitter.ScheduleNewOrchestrationInstanceAsync""/>
320332
public static Task<string> ScheduleNew{orchestrator.TaskName}InstanceAsync(
321333
this IOrchestrationSubmitter client, {orchestrator.InputParameter}, StartOrchestrationOptions? options = null)
@@ -327,6 +339,9 @@ static void AddOrchestratorCallMethod(StringBuilder sourceBuilder, DurableTaskTy
327339
static void AddSubOrchestratorCallMethod(StringBuilder sourceBuilder, DurableTaskTypeInfo orchestrator)
328340
{
329341
sourceBuilder.AppendLine($@"
342+
/// <summary>
343+
/// Calls the <see cref=""{orchestrator.TypeName}""/> sub-orchestrator.
344+
/// </summary>
330345
/// <inheritdoc cref=""TaskOrchestrationContext.CallSubOrchestratorAsync(TaskName, object?, TaskOptions?)""/>
331346
public static Task<{orchestrator.OutputType}> Call{orchestrator.TaskName}Async(
332347
this TaskOrchestrationContext context, {orchestrator.InputParameter}, TaskOptions? options = null)
@@ -338,6 +353,10 @@ static void AddSubOrchestratorCallMethod(StringBuilder sourceBuilder, DurableTas
338353
static void AddActivityCallMethod(StringBuilder sourceBuilder, DurableTaskTypeInfo activity)
339354
{
340355
sourceBuilder.AppendLine($@"
356+
/// <summary>
357+
/// Calls the <see cref=""{activity.TypeName}""/> activity.
358+
/// </summary>
359+
/// <inheritdoc cref=""TaskOrchestrationContext.CallActivityAsync(TaskName, object?, TaskOptions?)""/>
341360
public static Task<{activity.OutputType}> Call{activity.TaskName}Async(this TaskOrchestrationContext ctx, {activity.InputParameter}, TaskOptions? options = null)
342361
{{
343362
return ctx.CallActivityAsync<{activity.OutputType}>(""{activity.TaskName}"", input, options);
@@ -347,6 +366,10 @@ static void AddActivityCallMethod(StringBuilder sourceBuilder, DurableTaskTypeIn
347366
static void AddActivityCallMethod(StringBuilder sourceBuilder, DurableFunction activity)
348367
{
349368
sourceBuilder.AppendLine($@"
369+
/// <summary>
370+
/// Calls the <see cref=""{activity.FullTypeName}""/> activity.
371+
/// </summary>
372+
/// <inheritdoc cref=""TaskOrchestrationContext.CallActivityAsync(TaskName, object?, TaskOptions?)""/>
350373
public static Task<{activity.ReturnType}> Call{activity.Name}Async(this TaskOrchestrationContext ctx, {activity.Parameter}, TaskOptions? options = null)
351374
{{
352375
return ctx.CallActivityAsync<{activity.ReturnType}>(""{activity.Name}"", {activity.Parameter.Name}, options);
@@ -368,6 +391,17 @@ static void AddActivityFunctionDeclaration(StringBuilder sourceBuilder, DurableT
368391
}}");
369392
}
370393

394+
static void AddEntityFunctionDeclaration(StringBuilder sourceBuilder, DurableTaskTypeInfo entity)
395+
{
396+
// Generate the entity trigger function that dispatches to the entity implementation.
397+
sourceBuilder.AppendLine($@"
398+
[Function(nameof({entity.TaskName}))]
399+
public static Task {entity.TaskName}([EntityTrigger] TaskEntityDispatcher dispatcher)
400+
{{
401+
return dispatcher.DispatchAsync<{entity.TypeName}>();
402+
}}");
403+
}
404+
371405
/// <summary>
372406
/// Adds a custom ITaskActivityContext implementation used by code generated from <see cref="AddActivityFunctionDeclaration"/>.
373407
/// </summary>

src/Generators/Generators.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
<PropertyGroup>
2222
<!-- This file intentionally versions separately from the other packages. -->
2323
<VersionPrefix>1.0.0</VersionPrefix>
24-
<VersionSuffix>preview.1</VersionSuffix>
2524
</PropertyGroup>
2625

2726
<ItemGroup>

0 commit comments

Comments
 (0)