Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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.
/// 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
176 changes: 175 additions & 1 deletion src/Abstractions/DurableTaskRegistry.Activities.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.Extensions.DependencyInjection;

namespace Microsoft.DurableTask;
Expand All @@ -21,7 +22,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 +230,167 @@ 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(GetActivityNameFromDelegate(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(GetActivityNameFromDelegate(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(GetActivityNameFromDelegate(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(GetActivityNameFromDelegate(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(GetActivityNameFromDelegate(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(GetActivityNameFromDelegate(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(GetActivityNameFromDelegate(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(GetActivityNameFromDelegate(activity), activity);
}

/// <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 GetActivityNameFromDelegate(Delegate @delegate)
{
MethodInfo method = @delegate.Method;

// Check for DurableTaskAttribute on the method
DurableTaskAttribute? attribute = method.GetCustomAttribute<DurableTaskAttribute>();
if (attribute?.Name.Name is not null and not "")
{
return attribute.Name;
}

// Fall back to method name
string? methodName = method.Name;
if (string.IsNullOrEmpty(methodName) || methodName.StartsWith("<", StringComparison.Ordinal))
{
throw new ArgumentException(
"Cannot infer activity 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