Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor to use primary constructors from .net8 #244

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 2 additions & 9 deletions samples/AzureFunctionsApp/Entities/Counter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,11 @@ namespace AzureFunctionsApp.Entities;
/// the added benefit of being able to use DI. When using TaskEntity<TState>, state is deserialized to the "State"
/// property. No other properties on this type will be serialized/deserialized.
/// </summary>
public class Counter : TaskEntity<int>
public class Counter(ILogger<Counter> logger) : TaskEntity<int>
{
readonly ILogger logger;

public Counter(ILogger<Counter> logger)
{
this.logger = logger;
}

public int Add(int input)
{
this.logger.LogInformation("Adding {Input} to {State}", input, this.State);
logger.LogInformation("Adding {Input} to {State}", input, this.State);
return this.State += input;
}

Expand Down
11 changes: 2 additions & 9 deletions samples/AzureFunctionsApp/Entities/Lifetime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,8 @@ namespace AzureFunctionsApp.Entities;
/// is also possible to design an entity which remains stateless by always returning <c>null</c> from
/// <see cref="InitializeState"/> and never assigning a non-null state.
/// </summary>
public class Lifetime : TaskEntity<MyState>
public class Lifetime(ILogger<Lifetime> logger) : TaskEntity<MyState>
{
readonly ILogger logger;

public Lifetime(ILogger<Lifetime> logger)
{
this.logger = logger;
}

/// <summary>
/// Optional property to override. When 'true', this will allow dispatching of operations to the <see cref="State">
/// object if there is no matching method on the entity. Default is 'false'.
Expand All @@ -39,7 +32,7 @@ public Lifetime(ILogger<Lifetime> logger)
[Function(nameof(Lifetime))]
public Task DispatchAsync([EntityTrigger] TaskEntityDispatcher dispatcher)
{
this.logger.LogInformation("Dispatching entity");
logger.LogInformation("Dispatching entity");
return dispatcher.DispatchAsync(this);
}

Expand Down
16 changes: 4 additions & 12 deletions samples/AzureFunctionsApp/Entities/User.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,29 +19,21 @@ public record UserUpdate(string? Name, int? Age);
/// <summary>
/// This sample demonstrates how to bind to <see cref="TaskEntityContext"/> as well as dispatch to orchestrations.
/// </summary>
public class UserEntity : TaskEntity<User>
public class UserEntity(ILogger<UserEntity> logger) : TaskEntity<User>
{
readonly ILogger logger;

public UserEntity(ILogger<UserEntity> logger)
{
this.logger = logger;
}

public void Set(User user)
{
User previous = this.State;
this.State = user;
this.logger.LogInformation("User {Id} set {Old} -> {New}", this.Context.Id.Key, previous, this.State);
logger.LogInformation("User {Id} set {Old} -> {New}", this.Context.Id.Key, previous, this.State);
}

public void Update(UserUpdate update)
{
(string n, int a) = (update.Name ?? this.State.Name, update.Age ?? this.State.Age);
User previous = this.State;
this.State = previous with { Name = n, Age = a };

this.logger.LogInformation("User {Id} updated {Old} -> {New}", this.Context.Id.Key, previous, this.State);
logger.LogInformation("User {Id} updated {Old} -> {New}", this.Context.Id.Key, previous, this.State);
}

/// <summary>
Expand All @@ -55,7 +47,7 @@ public void Greet(TaskEntityContext context, string? message = null)
{
if (this.State.Name is null)
{
this.logger.LogError("User is not in a valid state for a greet operation.");
logger.LogError("User is not in a valid state for a greet operation.");
throw new InvalidOperationException("User has not been initialized.");
}

Expand Down
30 changes: 12 additions & 18 deletions samples/AzureFunctionsApp/HelloCitiesTyped.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,28 +63,22 @@ public async override Task<string> RunAsync(TaskOrchestrationContext context, st
/// Class-based activity function implementation. Source generators are used to a generate an activity function
/// definition that creates an instance of this class and invokes its <see cref="OnRun"/> method.
/// </summary>
/// <remarks>
/// Initializes a new instance of the <see cref="SayHelloTyped"/> class.
/// This class is initialized once for every activity execution.
/// </remarks>
/// <remarks>
/// Activity class constructors support constructor-based dependency injection.
/// The injected services are provided by the function's <see cref="FunctionContext.InstanceServices"/> property.
/// </remarks>
/// <param name="logger">The logger injected by the Azure Functions runtime.</param>
[DurableTask(nameof(SayHelloTyped))]
public class SayHelloTyped : TaskActivity<string, string>
public class SayHelloTyped(ILogger<SayHelloTyped> logger)
: TaskActivity<string, string>
{
readonly ILogger logger;

/// <summary>
/// Initializes a new instance of the <see cref="SayHelloTyped"/> class.
/// This class is initialized once for every activity execution.
/// </summary>
/// <remarks>
/// Activity class constructors support constructor-based dependency injection.
/// The injected services are provided by the function's <see cref="FunctionContext.InstanceServices"/> property.
/// </remarks>
/// <param name="logger">The logger injected by the Azure Functions runtime.</param>
public SayHelloTyped(ILogger<SayHelloTyped> logger)
{
this.logger = logger;
}

public override Task<string> RunAsync(TaskActivityContext context, string cityName)
{
this.logger.LogInformation("Saying hello to {name}", cityName);
logger.LogInformation("Saying hello to {name}", cityName);
return Task.FromResult($"Hello, {cityName}!");
}
}
15 changes: 4 additions & 11 deletions samples/AzureFunctionsApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,9 @@
// Licensed under the MIT License.

using Microsoft.Extensions.Hosting;
namespace AzureFunctionsApp;

public class Program
{
public static void Main()
{
IHost host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.Build();
IHost host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.Build();

host.Run();
}
}
host.Run();
13 changes: 3 additions & 10 deletions samples/WebAPI/Controllers/OrderProcessingController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,8 @@ namespace WebAPI.Controllers;

[Route("orders")]
[ApiController]
public class OrderProcessingController : ControllerBase
public class OrderProcessingController(DurableTaskClient durableTaskClient) : ControllerBase
{
readonly DurableTaskClient durableTaskClient;

public OrderProcessingController(DurableTaskClient durableTaskClient)
{
this.durableTaskClient = durableTaskClient;
}

// HTTPie command:
// http POST http://localhost:8080/orders/new Item=catfood Quantity=5 Price=600
[HttpPost("new")]
Expand All @@ -31,7 +24,7 @@ public async Task<ActionResult> CreateOrder([FromBody] OrderInfo orderInfo)

// Generate an order ID and start the order processing workflow orchestration
string orderId = $"{orderInfo.Item}-{Guid.NewGuid().ToString()[..4]}";
await this.durableTaskClient.ScheduleNewProcessOrderOrchestratorInstanceAsync(
await durableTaskClient.ScheduleNewProcessOrderOrchestratorInstanceAsync(
orderInfo, new StartOrchestrationOptions() { InstanceId = orderId });

// Return 202 with a link to the GetOrderStatus API
Expand All @@ -45,7 +38,7 @@ await this.durableTaskClient.ScheduleNewProcessOrderOrchestratorInstanceAsync(
[HttpGet("{orderId}")]
public async Task<ActionResult> GetOrderStatus(string orderId)
{
OrchestrationMetadata? metadata = await this.durableTaskClient.GetInstancesAsync(
OrchestrationMetadata? metadata = await durableTaskClient.GetInstancesAsync(
instanceId: orderId,
getInputsAndOutputs: true);

Expand Down
12 changes: 2 additions & 10 deletions samples/WebAPI/Orchestrations/ChargeCustomerActivity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,11 @@
namespace WebAPI.Orchestrations;

[DurableTask("ChargeCustomer")]
public class ChargeCustomerActivity : TaskActivity<OrderInfo, object?>
public class ChargeCustomerActivity(ILogger<ChargeCustomerActivity> logger) : TaskActivity<OrderInfo, object?>
{
readonly ILogger logger;

// Dependencies are injected from ASP.NET host service container
public ChargeCustomerActivity(ILogger<ChargeCustomerActivity> logger)
{
this.logger = logger;
}

public override async Task<object?> RunAsync(TaskActivityContext context, OrderInfo orderInfo)
{
this.logger.LogInformation(
logger.LogInformation(
"{instanceId}: Charging customer {price:C}'...",
context.InstanceId,
orderInfo?.Price ?? 0.0);
Expand Down
36 changes: 12 additions & 24 deletions samples/WebAPI/Orchestrations/CheckInventoryActivity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,19 @@
using Microsoft.DurableTask;
using WebAPI.Models;

namespace WebAPI.Orchestrations
namespace WebAPI.Orchestrations;

[DurableTask("CheckInventory")]
public class CheckInventoryActivity(ILogger<CheckInventoryActivity> logger)
: TaskActivity<OrderInfo, bool>
{
[DurableTask("CheckInventory")]
public class CheckInventoryActivity : TaskActivity<OrderInfo, bool>
public override Task<bool> RunAsync(TaskActivityContext context, OrderInfo orderInfo)
{
readonly ILogger logger;

// Dependencies are injected from ASP.NET host service container
public CheckInventoryActivity(ILogger<CheckInventoryActivity> logger)
{
this.logger = logger;
}

public override Task<bool> RunAsync(TaskActivityContext context, OrderInfo orderInfo)
{
if (orderInfo == null)
{
throw new ArgumentException("Failed to read order info!");
}

this.logger.LogInformation(
"{instanceId}: Checking inventory for '{item}'...found some!",
context.InstanceId,
orderInfo.Item);
return Task.FromResult(true);
}
ArgumentNullException.ThrowIfNull(context);
logger.LogInformation(
"{instanceId}: Checking inventory for '{item}'...found some!",
context.InstanceId,
orderInfo.Item);
return Task.FromResult(true);
}
}
13 changes: 3 additions & 10 deletions samples/WebAPI/Orchestrations/CreateShipmentActivity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,12 @@
namespace WebAPI.Orchestrations;

[DurableTask("CreateShipment")]
public class CreateShipmentActivity : TaskActivity<OrderInfo, object?>
public class CreateShipmentActivity(ILogger<CreateShipmentActivity> logger)
: TaskActivity<OrderInfo, object?>
{
readonly ILogger logger;

// Dependencies are injected from ASP.NET host service container
public CreateShipmentActivity(ILogger<CreateShipmentActivity> logger)
{
this.logger = logger;
}

public override async Task<object?> RunAsync(TaskActivityContext context, OrderInfo orderInfo)
{
this.logger.LogInformation(
logger.LogInformation(
"{instanceId}: Shipping customer order of {quantity} {item}(s)...",
context.InstanceId,
orderInfo?.Quantity ?? 0,
Expand Down
17 changes: 6 additions & 11 deletions src/Abstractions/Converters/JsonDataConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,19 @@ namespace Microsoft.DurableTask.Converters;
/// <summary>
/// An implementation of <see cref="DataConverter"/> that uses System.Text.Json APIs for data serialization.
/// </summary>
public class JsonDataConverter : DataConverter
/// <remarks>
/// Initializes a new instance of the <see cref="JsonDataConverter"/> class.
/// </remarks>
jviau marked this conversation as resolved.
Show resolved Hide resolved
/// <param name="options">The serializer options.</param>
public class JsonDataConverter(JsonSerializerOptions? options = null) : DataConverter
{
// WARNING: Changing default serialization options could potentially be breaking for in-flight orchestrations.
static readonly JsonSerializerOptions DefaultOptions = new()
{
IncludeFields = true,
};

readonly JsonSerializerOptions? options;

/// <summary>
/// Initializes a new instance of the <see cref="JsonDataConverter"/> class.
/// </summary>
/// <param name="options">The serializer options.</param>
public JsonDataConverter(JsonSerializerOptions? options = null)
{
this.options = options ?? DefaultOptions;
}
readonly JsonSerializerOptions? options = options ?? DefaultOptions;

/// <summary>
/// Gets an instance of the <see cref="JsonDataConverter"/> with default configuration.
Expand Down
22 changes: 8 additions & 14 deletions src/Abstractions/DurableTaskAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,17 @@ namespace Microsoft.DurableTask;
/// It is used specifically by build-time source generators to generate type-safe methods for invoking
/// orchestrations or activities.
/// </remarks>
/// <remarks>
/// Initializes a new instance of the <see cref="DurableTaskAttribute"/> class.
/// </remarks>
/// <param name="name">
/// The name of the durable task. If not specified, the class name is used as the implied name of the durable task.
/// </param>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public sealed class DurableTaskAttribute : Attribute
public sealed class DurableTaskAttribute(string? name = null) : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="DurableTaskAttribute"/> class.
/// </summary>
/// <param name="name">
/// The name of the durable task. If not specified, the class name is used as the implied name of the durable task.
/// </param>
public DurableTaskAttribute(string? name = null)
{
// This logic cannot become too complex as code-generator relies on examining the constructor arguments.
this.Name = string.IsNullOrEmpty(name) ? default : new TaskName(name!);
}

/// <summary>
/// Gets the name of the durable task.
/// </summary>
public TaskName Name { get; }
public TaskName Name { get; } = string.IsNullOrEmpty(name) ? default : new TaskName(name!);
}
32 changes: 13 additions & 19 deletions src/Abstractions/Entities/EntityOperationFailedException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,36 +10,30 @@ namespace Microsoft.DurableTask.Entities;
/// Detailed information associated with a particular operation failure, including exception details, can be found in the
/// <see cref="FailureDetails"/> property.
/// </remarks>
public sealed class EntityOperationFailedException : Exception
/// <remarks>
/// Initializes a new instance of the <see cref="EntityOperationFailedException"/> class.
/// </remarks>
jviau marked this conversation as resolved.
Show resolved Hide resolved
/// <param name="operationName">The operation name.</param>
/// <param name="entityId">The entity ID.</param>
/// <param name="failureDetails">The failure details.</param>
public sealed class EntityOperationFailedException(
EntityInstanceId entityId, string operationName, TaskFailureDetails failureDetails)
: Exception(GetExceptionMessage(operationName, entityId, failureDetails))
{
/// <summary>
/// Initializes a new instance of the <see cref="EntityOperationFailedException"/> class.
/// </summary>
/// <param name="operationName">The operation name.</param>
/// <param name="entityId">The entity ID.</param>
/// <param name="failureDetails">The failure details.</param>
public EntityOperationFailedException(EntityInstanceId entityId, string operationName, TaskFailureDetails failureDetails)
: base(GetExceptionMessage(operationName, entityId, failureDetails))
{
this.EntityId = entityId;
this.OperationName = operationName;
this.FailureDetails = failureDetails;
}

/// <summary>
/// Gets the ID of the entity.
/// </summary>
public EntityInstanceId EntityId { get; }
public EntityInstanceId EntityId { get; } = entityId;

/// <summary>
/// Gets the name of the operation.
/// </summary>
public string OperationName { get; }
public string OperationName { get; } = operationName;

/// <summary>
/// <summary>
/// Gets the details of the task failure, including exception information.
/// </summary>
public TaskFailureDetails FailureDetails { get; }
public TaskFailureDetails FailureDetails { get; } = failureDetails;

static string GetExceptionMessage(string operationName, EntityInstanceId entityId, TaskFailureDetails failureDetails)
{
Expand Down
Loading
Loading