From ac5eb3269e94fa9ca65cdd76c0c2cc33914bae59 Mon Sep 17 00:00:00 2001 From: Jacob Viau Date: Wed, 11 Oct 2023 09:43:56 -0700 Subject: [PATCH] Update samples with new packages (#211) --- nuget.config | 1 - .../AzureFunctionsApp.csproj | 4 +- .../Entities/Counter.Entity.cs | 89 ---------- .../Entities/Counter.State.cs | 65 -------- samples/AzureFunctionsApp/Entities/Counter.cs | 157 ++++++++++++++++++ .../Entities/StateManagement.cs | 2 +- src/Worker/Core/DurableTaskWorkerOptions.cs | 4 +- 7 files changed, 162 insertions(+), 160 deletions(-) delete mode 100644 samples/AzureFunctionsApp/Entities/Counter.Entity.cs delete mode 100644 samples/AzureFunctionsApp/Entities/Counter.State.cs create mode 100644 samples/AzureFunctionsApp/Entities/Counter.cs diff --git a/nuget.config b/nuget.config index 6b515b73..1dfe90ff 100644 --- a/nuget.config +++ b/nuget.config @@ -5,6 +5,5 @@ - \ No newline at end of file diff --git a/samples/AzureFunctionsApp/AzureFunctionsApp.csproj b/samples/AzureFunctionsApp/AzureFunctionsApp.csproj index a3fb81be..efe42b22 100644 --- a/samples/AzureFunctionsApp/AzureFunctionsApp.csproj +++ b/samples/AzureFunctionsApp/AzureFunctionsApp.csproj @@ -9,9 +9,9 @@ - + - + diff --git a/samples/AzureFunctionsApp/Entities/Counter.Entity.cs b/samples/AzureFunctionsApp/Entities/Counter.Entity.cs deleted file mode 100644 index e6a0cea5..00000000 --- a/samples/AzureFunctionsApp/Entities/Counter.Entity.cs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.Azure.Functions.Worker; -using Microsoft.Azure.Functions.Worker.Http; -using Microsoft.DurableTask; -using Microsoft.DurableTask.Client; -using Microsoft.DurableTask.Entities; -using Microsoft.Extensions.Logging; - -namespace AzureFunctionsApp.Entities.Entity; - -/// -/// Example on how to dispatch to an entity which directly implements TaskEntity. Using TaskEntity gives -/// the added benefit of being able to use DI. When using TaskEntity, state is deserialized to the "State" -/// property. No other properties on this type will be serialized/deserialized. -/// -public class Counter : TaskEntity -{ - readonly ILogger logger; - - public Counter(ILogger logger) - { - this.logger = logger; - } - - public int Add(int input) - { - this.logger.LogInformation("Adding {Input} to {State}", input, this.State); - return this.State += input; - } - - public int OperationWithContext(int input, TaskEntityContext context) - { - // Get access to TaskEntityContext by adding it as a parameter. Can be with or without an input parameter. - // Order does not matter. - context.StartOrchestration("SomeOrchestration", "SomeInput"); - - // When using TaskEntity, the TaskEntityContext can also be accessed via this.Context. - this.Context.StartOrchestration("SomeOrchestration", "SomeInput"); - return this.Add(input); - } - - public int Get() => this.State; - - [Function("Counter2")] - public Task RunEntityAsync([EntityTrigger] TaskEntityDispatcher dispatcher) - { - // Can dispatch to a TaskEntity by passing a instance. - return dispatcher.DispatchAsync(this); - } - - [Function("Counter3")] - public static Task RunEntityStaticAsync([EntityTrigger] TaskEntityDispatcher dispatcher) - { - // Can also dispatch to a TaskEntity by using a static method. - return dispatcher.DispatchAsync(); - } - - [Function("StartCounter2")] - public static async Task StartAsync( - [HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData request, - [DurableClient] DurableTaskClient client) - { - Payload? payload = await request.ReadFromJsonAsync(); - string id = await client.ScheduleNewOrchestrationInstanceAsync("CounterOrchestration2", payload); - return client.CreateCheckStatusResponse(request, id); - } - - [Function("CounterOrchestration2")] - public static async Task RunOrchestrationAsync( - [OrchestrationTrigger] TaskOrchestrationContext context, Payload input) - { - ILogger logger = context.CreateReplaySafeLogger(); - int result = await context.Entities.CallEntityAsync( - new EntityInstanceId("Counter2", input.Key), "add", input.Add); - - logger.LogInformation("Counter value: {Value}", result); - return result; - } - - protected override int InitializeState() - { - // Optional method to override to customize initialization of state for a new instance. - return base.InitializeState(); - } - - public record Payload(string Key, int Add); -} diff --git a/samples/AzureFunctionsApp/Entities/Counter.State.cs b/samples/AzureFunctionsApp/Entities/Counter.State.cs deleted file mode 100644 index d286ac84..00000000 --- a/samples/AzureFunctionsApp/Entities/Counter.State.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using Microsoft.Azure.Functions.Worker; -using Microsoft.Azure.Functions.Worker.Http; -using Microsoft.DurableTask; -using Microsoft.DurableTask.Client; -using Microsoft.DurableTask.Entities; -using Microsoft.Extensions.Logging; - -namespace AzureFunctionsApp.Entities.State; - -/// -/// Example on how to dispatch to a POCO as the entity implementation. When using POCO, the entire object is serialized -/// and deserialized. -/// -public class Counter -{ - public int Value { get; set; } - - public int Add(int input) => this.Value += input; - - public int OperationWithContext(int input, TaskEntityContext context) - { - // Get access to TaskEntityContext by adding it as a parameter. Can be with or without an input parameter. - // Order does not matter. - context.StartOrchestration("SomeOrchestration", "SomeInput"); - return this.Add(input); - } - - public int Get() => this.Value; - - [Function("Counter1")] - public static Task RunEntityAsync([EntityTrigger] TaskEntityDispatcher dispatcher) - { - // Using the dispatch to a state object will deserialize the state directly to that instance and dispatch to an - // appropriate method. - // Can only dispatch to a state object via generic argument. - return dispatcher.DispatchAsync(); - } - - [Function("StartCounter1")] - public static async Task StartAsync( - [HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData request, - [DurableClient] DurableTaskClient client) - { - Payload? payload = await request.ReadFromJsonAsync(); - string id = await client.ScheduleNewOrchestrationInstanceAsync("CounterOrchestration1", payload); - return client.CreateCheckStatusResponse(request, id); - } - - [Function("CounterOrchestration1")] - public static async Task RunOrchestrationAsync( - [OrchestrationTrigger] TaskOrchestrationContext context, Payload input) - { - ILogger logger = context.CreateReplaySafeLogger(); - int result = await context.Entities.CallEntityAsync( - new EntityInstanceId("Counter1", input.Key), "add", input.Add); - - logger.LogInformation("Counter value: {Value}", result); - return result; - } - - public record Payload(string Key, int Add); -} diff --git a/samples/AzureFunctionsApp/Entities/Counter.cs b/samples/AzureFunctionsApp/Entities/Counter.cs new file mode 100644 index 00000000..97584977 --- /dev/null +++ b/samples/AzureFunctionsApp/Entities/Counter.cs @@ -0,0 +1,157 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; +using Microsoft.DurableTask; +using Microsoft.DurableTask.Client; +using Microsoft.DurableTask.Entities; +using Microsoft.Extensions.Logging; + +namespace AzureFunctionsApp.Entities; + +/// +/// Example on how to dispatch to an entity which directly implements TaskEntity. Using TaskEntity gives +/// the added benefit of being able to use DI. When using TaskEntity, state is deserialized to the "State" +/// property. No other properties on this type will be serialized/deserialized. +/// +public class Counter : TaskEntity +{ + readonly ILogger logger; + + public Counter(ILogger logger) + { + this.logger = logger; + } + + public int Add(int input) + { + this.logger.LogInformation("Adding {Input} to {State}", input, this.State); + return this.State += input; + } + + public int OperationWithContext(int input, TaskEntityContext context) + { + // Get access to TaskEntityContext by adding it as a parameter. Can be with or without an input parameter. + // Order does not matter. + context.ScheduleNewOrchestration("SomeOrchestration", "SomeInput"); + + // When using TaskEntity, the TaskEntityContext can also be accessed via this.Context. + this.Context.ScheduleNewOrchestration("SomeOrchestration", "SomeInput"); + return this.Add(input); + } + + public int Get() => this.State; + + [Function("Counter")] + public Task RunEntityAsync([EntityTrigger] TaskEntityDispatcher dispatcher) + { + // Can dispatch to a TaskEntity by passing a instance. + return dispatcher.DispatchAsync(this); + } + + [Function("Counter_Alt")] + public static Task RunEntityStaticAsync([EntityTrigger] TaskEntityDispatcher dispatcher) + { + // Can also dispatch to a TaskEntity by using a static method. + return dispatcher.DispatchAsync(); + } + + protected override int InitializeState(TaskEntityOperation operation) + { + // Optional method to override to customize initialization of state for a new instance. + return base.InitializeState(operation); + } +} + +/// +/// Example on how to dispatch to a POCO as the entity implementation. When using POCO, the entire object is serialized +/// and deserialized. +/// +/// +/// Note there is a structural difference between and . In the former, +/// the state is declared as . In the later, state is the type itself (). +/// This means they have a different state serialization structure. The former is just "int", while the later is +/// "{ \"Value\": int }". +/// +public class StateCounter +{ + public int Value { get; set; } + + public int Add(int input) => this.Value += input; + + public int OperationWithContext(int input, TaskEntityContext context) + { + // Get access to TaskEntityContext by adding it as a parameter. Can be with or without an input parameter. + // Order does not matter. + context.ScheduleNewOrchestration("SomeOrchestration", "SomeInput"); + return this.Add(input); + } + + public int Get() => this.Value; + + [Function("StateCounter")] + public static Task RunEntityAsync([EntityTrigger] TaskEntityDispatcher dispatcher) + { + // Using the dispatch to a state object will deserialize the state directly to that instance and dispatch to an + // appropriate method. + // Can only dispatch to a state object via generic argument. + return dispatcher.DispatchAsync(); + } +} + +public static class CounterHelpers +{ + /// + /// Usage: + /// Add to : + /// POST /api/counter/{id}?value={value-to-add} + /// POST /api/counter/{id}?value={value-to-add}&mode=0 + /// POST /api/counter/{id}?value={value-to-add}&mode=entity + /// + /// Add to + /// POST /api/counter/{id}?value={value-to-add}&mode=1 + /// POST /api/counter/{id}?value={value-to-add}&mode=state + /// + /// + /// + /// + /// + [Function("StartCounter")] + public static async Task StartAsync( + [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "counter/{id}")] HttpRequestData request, + [DurableClient] DurableTaskClient client, + string id) + { + _ = int.TryParse(request.Query["value"], out int value); + + string name = "counter"; + + // switch to StateCounter if ?mode=1 or ?mode=state is supplied. + string? mode = request.Query["mode"]; + if (int.TryParse(mode, out int m) && m == 1) + { + name = "state"; + } + else if (mode == "state") + { + name = "statecounter"; + } + + string instanceId = await client.ScheduleNewOrchestrationInstanceAsync( + "CounterOrchestration", new Payload(new EntityInstanceId(name, id), value)); + return client.CreateCheckStatusResponse(request, instanceId); + } + + [Function("CounterOrchestration")] + public static async Task RunOrchestrationAsync( + [OrchestrationTrigger] TaskOrchestrationContext context, Payload input) + { + ILogger logger = context.CreateReplaySafeLogger(); + int result = await context.Entities.CallEntityAsync(input.Id, "add", input.Add); + logger.LogInformation("Counter value: {Value}", result); + return result; + } + + public record Payload(EntityInstanceId Id, int Add); +} diff --git a/samples/AzureFunctionsApp/Entities/StateManagement.cs b/samples/AzureFunctionsApp/Entities/StateManagement.cs index b590af4d..86871469 100644 --- a/samples/AzureFunctionsApp/Entities/StateManagement.cs +++ b/samples/AzureFunctionsApp/Entities/StateManagement.cs @@ -43,7 +43,7 @@ public void Delete() this.State = null!; } - protected override MyState InitializeState() + protected override MyState InitializeState(TaskEntityOperation operation) { // This method allows for customizing the default state value for a new entity. return new("Default", 10); diff --git a/src/Worker/Core/DurableTaskWorkerOptions.cs b/src/Worker/Core/DurableTaskWorkerOptions.cs index bc1e7fc4..2fa44d78 100644 --- a/src/Worker/Core/DurableTaskWorkerOptions.cs +++ b/src/Worker/Core/DurableTaskWorkerOptions.cs @@ -45,8 +45,8 @@ public DataConverter DataConverter } /// - /// Gets or sets a value indicating whether this client should support entities. If true, all instance ids starting with '@' are reserved for entities, - /// and validation checks are performed where appropriate. + /// Gets or sets a value indicating whether this client should support entities. If true, all instance ids starting + /// with '@' are reserved for entities, and validation checks are performed where appropriate. /// public bool EnableEntitySupport { get; set; }