Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions misc/misc.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<None Include="$(RepoRoot).gitignore" />
<None Include="$(RepoRoot)azure-pipelines-release.yml" />
<None Include="$(RepoRoot)Directory.Build.*" />
<None Include="$(RepoRoot)Directory.Packages.*" />
<None Include="$(RepoRoot)*.md" />
<None Include="$(RepoRoot)nuget.config" />
<None Include="$(EngRoot)**" LinkBase="eng" />
Expand Down
6 changes: 4 additions & 2 deletions src/Abstractions/DurableTaskAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@
namespace Microsoft.DurableTask;

/// <summary>
/// Indicates that the attributed class represents a durable task.
/// Indicates that the attributed class or method represents a durable task.
/// </summary>
/// <remarks>
/// This attribute is meant to be used on class definitions that derive from
/// <see cref="TaskOrchestrator{TInput, TOutput}"/>, <see cref="TaskActivity{TInput, TOutput}"/>,
/// or TaskEntity{TState} from the Microsoft.DurableTask.Entities namespace.
/// It can also be applied to methods used with <see cref="DurableTaskRegistry.AddOrchestratorFunc{TInput, TOutput}(System.Func{TaskOrchestrationContext, TInput, System.Threading.Tasks.Task{TOutput}})"/>
/// or similar overloads to specify a custom name for the orchestrator or activity.
/// It is used specifically by build-time source generators to generate type-safe methods for invoking
/// orchestrations, activities, or registering entities.
/// </remarks>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class DurableTaskAttribute : Attribute
{
/// <summary>
Expand Down
142 changes: 141 additions & 1 deletion src/Abstractions/DurableTaskRegistry.Activities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,17 @@ TaskName and TActivity generic parameter
ITaskActivity singleton
TaskName ITaskActivity singleton

by func/action:
by func/action (with explicit name):
Func{Context, Input, Task{Output}}
Func{Context, Input, Task}
Func{Context, Input, Output}
Func{Context, Task{Output}}
Func{Context, Task}
Func{Context, Output}
Action{Context, TInput}
Action{Context}

by func/action (name inferred from method or [DurableTask] attribute):
Func{Context, Input, Task{Output}}
Func{Context, Input, Task}
Func{Context, Input, Output}
Expand Down Expand Up @@ -219,4 +229,134 @@ public DurableTaskRegistry AddActivityFunc(TaskName name, Action<TaskActivityCon
return CompletedNullTask;
});
}

/// <summary>
/// Registers an activity factory, where the implementation is <paramref name="activity" />.
/// The name is inferred from a <see cref="DurableTaskAttribute"/> on the method, or the method name.
/// </summary>
/// <typeparam name="TInput">The activity input type.</typeparam>
/// <typeparam name="TOutput">The activity output type.</typeparam>
/// <param name="activity">The activity implementation.</param>
/// <returns>The same registry, for call chaining.</returns>
/// <exception cref="ArgumentException">
/// Thrown if the name cannot be inferred from the delegate.
/// </exception>
public DurableTaskRegistry AddActivityFunc<TInput, TOutput>(
Func<TaskActivityContext, TInput, Task<TOutput>> activity)
{
Check.NotNull(activity);
return this.AddActivityFunc(GetTaskNameFromDelegate(activity), activity);
}

/// <summary>
/// Registers an activity factory, where the implementation is <paramref name="activity" />.
/// The name is inferred from a <see cref="DurableTaskAttribute"/> on the method, or the method name.
/// </summary>
/// <typeparam name="TInput">The activity input type.</typeparam>
/// <typeparam name="TOutput">The activity output type.</typeparam>
/// <param name="activity">The activity implementation.</param>
/// <returns>The same registry, for call chaining.</returns>
/// <exception cref="ArgumentException">
/// Thrown if the name cannot be inferred from the delegate.
/// </exception>
public DurableTaskRegistry AddActivityFunc<TInput, TOutput>(
Func<TaskActivityContext, TInput, TOutput> activity)
{
Check.NotNull(activity);
return this.AddActivityFunc(GetTaskNameFromDelegate(activity), activity);
}

/// <summary>
/// Registers an activity factory, where the implementation is <paramref name="activity" />.
/// The name is inferred from a <see cref="DurableTaskAttribute"/> on the method, or the method name.
/// </summary>
/// <typeparam name="TInput">The activity input type.</typeparam>
/// <param name="activity">The activity implementation.</param>
/// <returns>The same registry, for call chaining.</returns>
/// <exception cref="ArgumentException">
/// Thrown if the name cannot be inferred from the delegate.
/// </exception>
public DurableTaskRegistry AddActivityFunc<TInput>(Func<TaskActivityContext, TInput, Task> activity)
{
Check.NotNull(activity);
return this.AddActivityFunc(GetTaskNameFromDelegate(activity), activity);
}

/// <summary>
/// Registers an activity factory, where the implementation is <paramref name="activity" />.
/// The name is inferred from a <see cref="DurableTaskAttribute"/> on the method, or the method name.
/// </summary>
/// <typeparam name="TOutput">The activity output type.</typeparam>
/// <param name="activity">The activity implementation.</param>
/// <returns>The same registry, for call chaining.</returns>
/// <exception cref="ArgumentException">
/// Thrown if the name cannot be inferred from the delegate.
/// </exception>
public DurableTaskRegistry AddActivityFunc<TOutput>(Func<TaskActivityContext, Task<TOutput>> activity)
{
Check.NotNull(activity);
return this.AddActivityFunc(GetTaskNameFromDelegate(activity), activity);
}

/// <summary>
/// Registers an activity factory, where the implementation is <paramref name="activity" />.
/// The name is inferred from a <see cref="DurableTaskAttribute"/> on the method, or the method name.
/// </summary>
/// <param name="activity">The activity implementation.</param>
/// <returns>The same registry, for call chaining.</returns>
/// <exception cref="ArgumentException">
/// Thrown if the name cannot be inferred from the delegate.
/// </exception>
public DurableTaskRegistry AddActivityFunc(Func<TaskActivityContext, Task> activity)
{
Check.NotNull(activity);
return this.AddActivityFunc(GetTaskNameFromDelegate(activity), activity);
}

/// <summary>
/// Registers an activity factory, where the implementation is <paramref name="activity" />.
/// The name is inferred from a <see cref="DurableTaskAttribute"/> on the method, or the method name.
/// </summary>
/// <typeparam name="TOutput">The activity output type.</typeparam>
/// <param name="activity">The activity implementation.</param>
/// <returns>The same registry, for call chaining.</returns>
/// <exception cref="ArgumentException">
/// Thrown if the name cannot be inferred from the delegate.
/// </exception>
public DurableTaskRegistry AddActivityFunc<TOutput>(Func<TaskActivityContext, TOutput> activity)
{
Check.NotNull(activity);
return this.AddActivityFunc(GetTaskNameFromDelegate(activity), activity);
}

/// <summary>
/// Registers an activity factory, where the implementation is <paramref name="activity" />.
/// The name is inferred from a <see cref="DurableTaskAttribute"/> on the method, or the method name.
/// </summary>
/// <typeparam name="TInput">The activity input type.</typeparam>
/// <param name="activity">The activity implementation.</param>
/// <returns>The same registry, for call chaining.</returns>
/// <exception cref="ArgumentException">
/// Thrown if the name cannot be inferred from the delegate.
/// </exception>
public DurableTaskRegistry AddActivityFunc<TInput>(Action<TaskActivityContext, TInput> activity)
{
Check.NotNull(activity);
return this.AddActivityFunc(GetTaskNameFromDelegate(activity), activity);
}

/// <summary>
/// Registers an activity factory, where the implementation is <paramref name="activity" />.
/// The name is inferred from a <see cref="DurableTaskAttribute"/> on the method, or the method name.
/// </summary>
/// <param name="activity">The activity implementation.</param>
/// <returns>The same registry, for call chaining.</returns>
/// <exception cref="ArgumentException">
/// Thrown if the name cannot be inferred from the delegate.
/// </exception>
public DurableTaskRegistry AddActivityFunc(Action<TaskActivityContext> activity)
{
Check.NotNull(activity);
return this.AddActivityFunc(GetTaskNameFromDelegate(activity), activity);
}
}
146 changes: 145 additions & 1 deletion src/Abstractions/DurableTaskRegistry.Orchestrators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,17 @@ TaskName and TOrchestrator generic parameter
ITaskOrchestrator singleton
TaskName and ITaskOrchestrator singleton

by func/action:
by func/action (with explicit name):
Func{Context, Input, Task{Output}}
Func{Context, Input, Task}
Func{Context, Input, Output}
Func{Context, Task{Output}}
Func{Context, Task}
Func{Context, Output}
Action{Context, TInput}
Action{Context}

by func/action (name inferred from method or [DurableTask] attribute):
Func{Context, Input, Task{Output}}
Func{Context, Input, Task}
Func{Context, Input, Output}
Expand Down Expand Up @@ -220,4 +230,138 @@ public DurableTaskRegistry AddOrchestratorFunc(TaskName name, Action<TaskOrchest
return CompletedNullTask;
});
}

/// <summary>
/// Registers an orchestrator factory, where the implementation is <paramref name="orchestrator" />.
/// The name is inferred from a <see cref="DurableTaskAttribute"/> on the method, or the method name.
/// </summary>
/// <typeparam name="TInput">The orchestrator input type.</typeparam>
/// <typeparam name="TOutput">The orchestrator output type.</typeparam>
/// <param name="orchestrator">The orchestrator implementation.</param>
/// <returns>The same registry, for call chaining.</returns>
/// <exception cref="ArgumentException">
/// Thrown if the name cannot be inferred from the delegate.
/// </exception>
public DurableTaskRegistry AddOrchestratorFunc<TInput, TOutput>(
Func<TaskOrchestrationContext, TInput, Task<TOutput>> orchestrator)
{
Check.NotNull(orchestrator);
return this.AddOrchestratorFunc(GetTaskNameFromDelegate(orchestrator), orchestrator);
}

/// <summary>
/// Registers an orchestrator factory, where the implementation is <paramref name="orchestrator" />.
/// The name is inferred from a <see cref="DurableTaskAttribute"/> on the method, or the method name.
/// </summary>
/// <typeparam name="TInput">The orchestrator input type.</typeparam>
/// <typeparam name="TOutput">The orchestrator output type.</typeparam>
/// <param name="orchestrator">The orchestrator implementation.</param>
/// <returns>The same registry, for call chaining.</returns>
/// <exception cref="ArgumentException">
/// Thrown if the name cannot be inferred from the delegate.
/// </exception>
public DurableTaskRegistry AddOrchestratorFunc<TInput, TOutput>(
Func<TaskOrchestrationContext, TInput, TOutput> orchestrator)
{
Check.NotNull(orchestrator);
return this.AddOrchestratorFunc(GetTaskNameFromDelegate(orchestrator), orchestrator);
}

/// <summary>
/// Registers an orchestrator factory, where the implementation is <paramref name="orchestrator" />.
/// The name is inferred from a <see cref="DurableTaskAttribute"/> on the method, or the method name.
/// </summary>
/// <typeparam name="TInput">The orchestrator input type.</typeparam>
/// <param name="orchestrator">The orchestrator implementation.</param>
/// <returns>The same registry, for call chaining.</returns>
/// <exception cref="ArgumentException">
/// Thrown if the name cannot be inferred from the delegate.
/// </exception>
public DurableTaskRegistry AddOrchestratorFunc<TInput>(
Func<TaskOrchestrationContext, TInput, Task> orchestrator)
{
Check.NotNull(orchestrator);
return this.AddOrchestratorFunc(GetTaskNameFromDelegate(orchestrator), orchestrator);
}

/// <summary>
/// Registers an orchestrator factory, where the implementation is <paramref name="orchestrator" />.
/// The name is inferred from a <see cref="DurableTaskAttribute"/> on the method, or the method name.
/// </summary>
/// <typeparam name="TOutput">The orchestrator output type.</typeparam>
/// <param name="orchestrator">The orchestrator implementation.</param>
/// <returns>The same registry, for call chaining.</returns>
/// <exception cref="ArgumentException">
/// Thrown if the name cannot be inferred from the delegate.
/// </exception>
public DurableTaskRegistry AddOrchestratorFunc<TOutput>(
Func<TaskOrchestrationContext, Task<TOutput>> orchestrator)
{
Check.NotNull(orchestrator);
return this.AddOrchestratorFunc(GetTaskNameFromDelegate(orchestrator), orchestrator);
}

/// <summary>
/// Registers an orchestrator factory, where the implementation is <paramref name="orchestrator" />.
/// The name is inferred from a <see cref="DurableTaskAttribute"/> on the method, or the method name.
/// </summary>
/// <param name="orchestrator">The orchestrator implementation.</param>
/// <returns>The same registry, for call chaining.</returns>
/// <exception cref="ArgumentException">
/// Thrown if the name cannot be inferred from the delegate.
/// </exception>
public DurableTaskRegistry AddOrchestratorFunc(Func<TaskOrchestrationContext, Task> orchestrator)
{
Check.NotNull(orchestrator);
return this.AddOrchestratorFunc(GetTaskNameFromDelegate(orchestrator), orchestrator);
}

/// <summary>
/// Registers an orchestrator factory, where the implementation is <paramref name="orchestrator" />.
/// The name is inferred from a <see cref="DurableTaskAttribute"/> on the method, or the method name.
/// </summary>
/// <typeparam name="TOutput">The orchestrator output type.</typeparam>
/// <param name="orchestrator">The orchestrator implementation.</param>
/// <returns>The same registry, for call chaining.</returns>
/// <exception cref="ArgumentException">
/// Thrown if the name cannot be inferred from the delegate.
/// </exception>
public DurableTaskRegistry AddOrchestratorFunc<TOutput>(
Func<TaskOrchestrationContext, TOutput> orchestrator)
{
Check.NotNull(orchestrator);
return this.AddOrchestratorFunc(GetTaskNameFromDelegate(orchestrator), orchestrator);
}

/// <summary>
/// Registers an orchestrator factory, where the implementation is <paramref name="orchestrator" />.
/// The name is inferred from a <see cref="DurableTaskAttribute"/> on the method, or the method name.
/// </summary>
/// <typeparam name="TInput">The orchestrator input type.</typeparam>
/// <param name="orchestrator">The orchestrator implementation.</param>
/// <returns>The same registry, for call chaining.</returns>
/// <exception cref="ArgumentException">
/// Thrown if the name cannot be inferred from the delegate.
/// </exception>
public DurableTaskRegistry AddOrchestratorFunc<TInput>(
Action<TaskOrchestrationContext, TInput> orchestrator)
{
Check.NotNull(orchestrator);
return this.AddOrchestratorFunc(GetTaskNameFromDelegate(orchestrator), orchestrator);
}

/// <summary>
/// Registers an orchestrator factory, where the implementation is <paramref name="orchestrator" />.
/// The name is inferred from a <see cref="DurableTaskAttribute"/> on the method, or the method name.
/// </summary>
/// <param name="orchestrator">The orchestrator implementation.</param>
/// <returns>The same registry, for call chaining.</returns>
/// <exception cref="ArgumentException">
/// Thrown if the name cannot be inferred from the delegate.
/// </exception>
public DurableTaskRegistry AddOrchestratorFunc(Action<TaskOrchestrationContext> orchestrator)
{
Check.NotNull(orchestrator);
return this.AddOrchestratorFunc(GetTaskNameFromDelegate(orchestrator), orchestrator);
}
}
35 changes: 35 additions & 0 deletions src/Abstractions/DurableTaskRegistry.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Reflection;
using Microsoft.DurableTask.Entities;

namespace Microsoft.DurableTask;
Expand Down Expand Up @@ -112,4 +113,38 @@ public DurableTaskRegistry AddEntity(TaskName name, Func<IServiceProvider, ITask
this.Entities.Add(name, factory);
return this;
}

/// <summary>
/// Gets the task name from a delegate by checking for a <see cref="DurableTaskAttribute"/>
/// or falling back to the method name.
/// </summary>
/// <param name="delegate">The delegate to extract the name from.</param>
/// <returns>The task name.</returns>
/// <exception cref="ArgumentException">
/// Thrown if the name cannot be inferred from the delegate.
/// </exception>
static TaskName GetTaskNameFromDelegate(Delegate @delegate)
{
MethodInfo method = @delegate.Method;

// Check for DurableTaskAttribute on the method
DurableTaskAttribute? attribute = method.GetCustomAttribute<DurableTaskAttribute>();
string attributeName = attribute?.Name.Name ?? string.Empty;
if (!string.IsNullOrEmpty(attributeName))
{
return new TaskName(attributeName);
}

// Fall back to method name
string? methodName = method.Name;
if (string.IsNullOrEmpty(methodName) || methodName.StartsWith("<", StringComparison.Ordinal))
{
throw new ArgumentException(
"Cannot infer task name from the delegate. The delegate must either have a " +
"[DurableTask] attribute with a name, or be a named method (not a lambda or anonymous delegate).",
nameof(@delegate));
}

return new TaskName(methodName);
}
}
Loading
Loading