Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Dec 18, 2025

Summary

What changed?

  • Added AddTasksAsServices extension method on IDurableTaskWorkerBuilder that registers all task types (activities, orchestrators, entities) as transient services in the DI container
  • Added internal HashSet<Type> collections in DurableTaskRegistry to track types registered via type-based methods (AddActivity(Type), AddOrchestrator(Type), AddEntity(Type))
  • Added 7 unit tests covering all scenarios: type registration, singleton instances, function-based tasks, and worker integration

Why is this change needed?

This enables DurableTask types to participate in DI container validation, allowing dependency resolution errors to be detected at startup rather than at runtime. This follows the same pattern as ASP.NET Core's AddControllersAsServices() extension.

Issues / work items


Project checklist

  • Release notes are not required for the next release
  • Backport is not required
  • All required tests have been added/updated (unit tests, E2E tests)
  • Breaking change? No

AI-assisted code disclosure (required)

Was an AI tool used? (select one)

  • No
  • Yes, AI helped write parts of this PR (e.g., GitHub Copilot)
  • Yes, an AI agent generated most of this PR

If AI was used:

  • Tool(s): GitHub Copilot Agent
  • AI-assisted areas/files:
    • src/Abstractions/DurableTaskRegistry.cs - Added HashSet tracking for registered types
    • src/Worker/Core/DependencyInjection/DurableTaskWorkerBuilderExtensions.cs - Added AddTasksAsServices extension
    • test/Worker/Core.Tests/DependencyInjection/DurableTaskWorkerBuilderExtensionsTests.cs - Added comprehensive unit tests
  • What you changed after AI output:
    • Updated HashSet initialization from collection expression [] to new HashSet<Type>() to new() based on code review feedback
    • Added test for singleton instance registration behavior based on code review feedback

AI verification (required if AI was used):

  • I understand the code and can explain it
  • I verified referenced APIs/types exist and are correct
  • I reviewed edge cases/failure paths (timeouts, retries, cancellation, exceptions)
  • I reviewed concurrency/async behavior
  • I checked for unintended breaking or behavior changes

Testing

Automated tests

  • Result: Passed (75 Worker.Tests pass including 7 new AddTasksAsServices tests)

Manual validation (only if runtime/behavior changed)

  • N/A (unit tests provide sufficient coverage)

Notes for reviewers

  • Only types registered via type-based methods (e.g., AddActivity<T>(), AddOrchestrator<T>()) are registered in DI
  • Types registered via factory methods or singleton instances are NOT registered in DI (by design)
  • The extension also calls AddTasks() internally, so tasks are registered with both DI and the worker
  • Usage example:
builder.Services.AddDurableTaskWorker()
    .AddTasksAsServices(tasks =>
    {
        tasks.AddActivity<MyActivity>();
        tasks.AddOrchestrator<MyOrchestrator>();
        tasks.AddEntity<MyEntity>();
    });
Original prompt

This section details on the original issue you should resolve

<issue_title>[Request] Add extension to register all functions as services in DI container</issue_title>
<issue_description># Proposal

Introduce a new AddFuncitonsAsServices extension method on IFunctionsWorkerApplicationBuilder that automatically registers every function class detected by the functions framework in the dependency injection container.

This allows all function classes to participate in the container's self-checking logic during startup of the project, preventing runtime errors much later when actually executing specific functions if dependencies cannot be resolved.

Background

AspNetCore MVC provides a AddControllersAsServices() extension that registers all controllers in the container automatically.

This not only enables things such as being able to decorate controller classes via the container, but more importantly it allows controller classes to participate in the Microsoft DI container self-checking mechanism, which iterates through all services registered in the container and checks whether or not their dependencies can be resolved. This mechanism is enabled by default while in debug mode and allows developers to catch DI-related errors much earlier in the pipeline: the validation error is raised during container construction, instead of only during a specific call to the affected controller action.

My proposal is that a similar extension be created for functions/durable functions, which would allow consumers to easily add all functions as services. A potential name for such extension could be AddFuncitonsAsServices(), for consistency.

While consumers can register functions manually themselves and still have them participate in the container self-check process, this approach is error-prone, creates coupling with the durable tasks library, and potentially duplicates logic: users would be tasked with creating their own logic to "detect" all function classes, which is something that the functions framework already does to perform its main job. The alternative is to manually register each individual function as separate calls, which not only doesn't scale well, but is fairly noisy, leads to situations where some functions are forgotten to be registered (which could then potentially lead to runtime issues being detected only after deployment), and also forces developers to remember to add a registration whenever they create a new function.

If the team is resistant to adding such helper extension, the framework should at least expose discovered functions in some sort of callback so that consumers could then register those types themselves without having to replicate the function detection logic.

This is a spin-off of:

Comments on the Issue (you are @copilot in this section)

@jviau Oh, your request is for Azure Functions as a whole and not just Durable. This request would belong in https://github.com/Azure/azure-functions-dotnet-worker

However, there would be an applicable request for Durable as well, for registering all tasks on DurableTaskRegistry to the IServiceCollection. This issue can be repurposed for that functionality request.</comment_new>


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 2 commits December 18, 2025 11:50
Co-authored-by: YunchuWang <12449837+YunchuWang@users.noreply.github.com>
Co-authored-by: YunchuWang <12449837+YunchuWang@users.noreply.github.com>
Copilot AI changed the title [WIP] Add extension to register all functions as services in DI container Add AddTasksAsServices extension for registering DurableTask types in DI container Dec 18, 2025
Copilot AI requested a review from YunchuWang December 18, 2025 11:58
@YunchuWang YunchuWang marked this pull request as ready for review December 31, 2025 20:43
Copilot AI review requested due to automatic review settings December 31, 2025 20:43
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds the AddTasksAsServices extension method to enable DurableTask types (activities, orchestrators, entities) to participate in DI container validation, allowing dependency resolution errors to be detected at startup rather than at runtime.

Key Changes:

  • Added internal HashSet collections in DurableTaskRegistry to track types registered via type-based methods
  • Implemented AddTasksAsServices extension that extracts registered types and registers them as transient services
  • Added comprehensive test coverage for the new functionality

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/Abstractions/DurableTaskRegistry.cs Added internal HashSet properties to track activity, orchestrator, and entity types
src/Abstractions/DurableTaskRegistry.Activities.cs Modified AddActivity(TaskName, Type) to track type in ActivityTypes collection
src/Abstractions/DurableTaskRegistry.Orchestrators.cs Modified AddOrchestrator(TaskName, Type) to track type in OrchestratorTypes collection
src/Abstractions/DurableTaskRegistry.Entities.cs Modified AddEntity(TaskName, Type) to track type in EntityTypes collection
src/Worker/Core/DependencyInjection/DurableTaskWorkerBuilderExtensions.cs Added AddTasksAsServices extension method with XML documentation and implementation
test/Worker/Core.Tests/DependencyInjection/DurableTaskWorkerBuilderExtensionsTests.cs Added comprehensive test suite including tests for activities, orchestrators, entities, function-based tasks, and worker registration

Comment on lines 37 to 47
internal HashSet<Type> ActivityTypes { get; } = new HashSet<Type>();

/// <summary>
/// Gets the types of registered orchestrators.
/// </summary>
internal HashSet<Type> OrchestratorTypes { get; } = new HashSet<Type>();

/// <summary>
/// Gets the types of registered entities.
/// </summary>
internal HashSet<Type> EntityTypes { get; } = new HashSet<Type>();
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HashSet initialization should use target-typed new() syntax instead of the explicit type for consistency with other similar patterns in the codebase (e.g., GrpcDurableTaskWorkerOptions.cs:32). This follows the C# 9.0 target-typed new expressions pattern that's already used in this codebase.

Suggested change
internal HashSet<Type> ActivityTypes { get; } = new HashSet<Type>();
/// <summary>
/// Gets the types of registered orchestrators.
/// </summary>
internal HashSet<Type> OrchestratorTypes { get; } = new HashSet<Type>();
/// <summary>
/// Gets the types of registered entities.
/// </summary>
internal HashSet<Type> EntityTypes { get; } = new HashSet<Type>();
internal HashSet<Type> ActivityTypes { get; } = new();
/// <summary>
/// Gets the types of registered orchestrators.
/// </summary>
internal HashSet<Type> OrchestratorTypes { get; } = new();
/// <summary>
/// Gets the types of registered entities.
/// </summary>
internal HashSet<Type> EntityTypes { get; } = new();

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was already addressed in commit b490b9c which changed the HashSet initialization to use target-typed new() syntax.

Comment on lines 37 to 47
internal HashSet<Type> ActivityTypes { get; } = new HashSet<Type>();

/// <summary>
/// Gets the types of registered orchestrators.
/// </summary>
internal HashSet<Type> OrchestratorTypes { get; } = new HashSet<Type>();

/// <summary>
/// Gets the types of registered entities.
/// </summary>
internal HashSet<Type> EntityTypes { get; } = new HashSet<Type>();
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HashSet initialization should use target-typed new() syntax instead of the explicit type for consistency with other similar patterns in the codebase (e.g., GrpcDurableTaskWorkerOptions.cs:32). This follows the C# 9.0 target-typed new expressions pattern that's already used in this codebase.

Suggested change
internal HashSet<Type> ActivityTypes { get; } = new HashSet<Type>();
/// <summary>
/// Gets the types of registered orchestrators.
/// </summary>
internal HashSet<Type> OrchestratorTypes { get; } = new HashSet<Type>();
/// <summary>
/// Gets the types of registered entities.
/// </summary>
internal HashSet<Type> EntityTypes { get; } = new HashSet<Type>();
internal HashSet<Type> ActivityTypes { get; } = new();
/// <summary>
/// Gets the types of registered orchestrators.
/// </summary>
internal HashSet<Type> OrchestratorTypes { get; } = new();
/// <summary>
/// Gets the types of registered entities.
/// </summary>
internal HashSet<Type> EntityTypes { get; } = new();

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was already addressed in commit b490b9c which changed the HashSet initialization to use target-typed new() syntax.

Comment on lines +142 to +159
[Fact]
public void AddTasksAsServices_DoesNotRegisterFunctionBasedTasks()
{
// Arrange
ServiceCollection services = new();
DefaultDurableTaskWorkerBuilder builder = new("test", services);

// Act
builder.AddTasksAsServices(registry =>
{
registry.AddActivityFunc("testFunc", (TaskActivityContext ctx) => Task.CompletedTask);
});

// Assert - No exception should be thrown and no types should be registered
IServiceProvider provider = services.BuildServiceProvider();
// There should be no issue building the service provider
provider.Should().NotBeNull();
}
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a test case for singleton instance registration to verify that instances registered via AddActivity(ITaskActivity) are not added to the DI container, similar to the existing test AddTasksAsServices_DoesNotRegisterFunctionBasedTasks. This would provide complete coverage of the documented behavior that only type-based registrations are included.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added test AddTasksAsServices_DoesNotRegisterSingletonInstances to verify singleton instances are not registered in DI container. Commit: 88c4998

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@YunchuWang
Copy link
Member

@copilot address comments

@YunchuWang
Copy link
Member

@copilot update pr description to follow .github/PULL_REQUEST_TEMPLATE.md

Co-authored-by: YunchuWang <12449837+YunchuWang@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Jan 1, 2026

@copilot address comments

All comments have been addressed:

  1. HashSet initialization already uses target-typed new() syntax (commit b490b9c)
  2. Added test for singleton instance registration behavior (commit 88c4998)

All 7 AddTasksAsServices tests pass.

Copy link
Contributor Author

Copilot AI commented Jan 1, 2026

@copilot update pr description to follow .github/PULL_REQUEST_TEMPLATE.md

Updated PR description to follow the template in .github/PULL_REQUEST_TEMPLATE.md:

Summary

What changed?

  • Added AddTasksAsServices extension method on IDurableTaskWorkerBuilder that registers all task types (activities, orchestrators, entities) as transient services in the DI container
  • Added internal HashSet<Type> collections in DurableTaskRegistry to track types registered via type-based methods (AddActivity(Type), AddOrchestrator(Type), AddEntity(Type))
  • Added 7 unit tests covering all scenarios: type registration, singleton instances, function-based tasks, and worker integration

Why is this change needed?

This enables DurableTask types to participate in DI container validation, allowing dependency resolution errors to be detected at startup rather than at runtime. This follows the same pattern as ASP.NET Core's AddControllersAsServices() extension.

Issues / work items


Project checklist

  • Release notes are not required for the next release
  • Backport is not required
  • All required tests have been added/updated (unit tests, E2E tests)
  • Breaking change? No

AI-assisted code disclosure (required)

Was an AI tool used? (select one)

  • No
  • Yes, AI helped write parts of this PR (e.g., GitHub Copilot)
  • Yes, an AI agent generated most of this PR

If AI was used:

  • Tool(s): GitHub Copilot Agent
  • AI-assisted areas/files:
    • src/Abstractions/DurableTaskRegistry.cs - Added HashSet tracking for registered types
    • src/Worker/Core/DependencyInjection/DurableTaskWorkerBuilderExtensions.cs - Added AddTasksAsServices extension
    • test/Worker/Core.Tests/DependencyInjection/DurableTaskWorkerBuilderExtensionsTests.cs - Added comprehensive unit tests
  • What you changed after AI output:
    • Updated HashSet initialization from collection expression [] to new HashSet<Type>() to new() based on code review feedback
    • Added test for singleton instance registration behavior based on code review feedback

AI verification (required if AI was used):

  • I understand the code and can explain it
  • I verified referenced APIs/types exist and are correct
  • I reviewed edge cases/failure paths (timeouts, retries, cancellation, exceptions)
  • I reviewed concurrency/async behavior
  • I checked for unintended breaking or behavior changes

Testing

Automated tests

  • Result: Passed (75 Worker.Tests pass including 7 new AddTasksAsServices tests)

Manual validation (only if runtime/behavior changed)

  • N/A (unit tests provide sufficient coverage)

Notes for reviewers

  • Only types registered via type-based methods (e.g., AddActivity<T>(), AddOrchestrator<T>()) are registered in DI
  • Types registered via factory methods or singleton instances are NOT registered in DI (by design)
  • The extension also calls AddTasks() internally, so tasks are registered with both DI and the worker
  • Usage example:
builder.Services.AddDurableTaskWorker()
    .AddTasksAsServices(tasks =>
    {
        tasks.AddActivity<MyActivity>();
        tasks.AddOrchestrator<MyOrchestrator>();
        tasks.AddEntity<MyEntity>();
    });

@YunchuWang
Copy link
Member

image verified working as expected

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Request] Add extension to register all functions as services in DI container

2 participants