Monday, February 2, 2026

.NET Isolated Azure Functions: Dependency Injection in Durable Entities

In this post, let's have a look at how we can use Dependency Injection (DI) with Azure Durable Entities in .NET 10 Isolated Durable Functions

Say we have the following service.
public interface ISomeService
{
    Task DoSomething();
}

public class SomeService : ISomeService
{
    public async Task DoSomething()
    {
        await Task.Delay(1000);
    }
}
And in Program.cs, this service is registered in DI container.

Using TaskEntity<T> with Constructor Injection


The TaskEntity<T> base class from Microsoft.Azure.Functions.Worker.Extensions.DurableTask provides a clean way to implement entities with DI support and is the recommended approach.

We can have a State and Entity like the following.
public class HelloHistoryState
{
    [JsonPropertyName("messages")]
    public List<string> Messages { get; set; } = [];
}

public class HelloHistoryEntity : TaskEntity<HelloHistoryState>
{
    private readonly ISomeService _someService;

    public HelloHistoryEntity(ISomeService cacheService)
    {
        _someService = cacheService;
    }

    public async Task Add(string message)
    {
        await _someService.DoSomething();

        State.Messages.Add(message);
    }

    // Async for future proofing
    public async Task Reset()
    {
        State = new HelloHistoryState();
    }

    // Async for future proofing
    public Task<HelloHistoryState> GetState() => Task.FromResult(State);

    [Function(nameof(HelloHistoryEntity))]
    public Task DispatchAsync([EntityTrigger] TaskEntityDispatcher dispatcher)
    {
        return dispatcher.DispatchAsync(this);
    }
}
In this approach:
  • The entity class inherits from TaskEntity<T> where T is the state type
  • Constructor injection works as expected
  • The State property provides access to the entity's state
  • The DispatchAsync method uses dispatcher.DispatchAsync(this) to dispatch to the entity instance
And we can call the entity as follows.
[Function(nameof(RunOrchestrator))]
public static async Task<HelloHistoryState> RunOrchestrator(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    EntityInstanceId entityId = new(nameof(HelloHistoryEntity), "helloHistory");

    await context.Entities.CallEntityAsync(entityId,
        nameof(HelloHistoryEntity.Reset));

    string result = await context.CallActivityAsync<string>(nameof(SayHello), "Tokyo");
    await context.Entities.CallEntityAsync(entityId,
        nameof(HelloHistoryEntity.Add),
        result);

    result = await context.CallActivityAsync<string>(nameof(SayHello), "Seattle");
    await context.Entities.CallEntityAsync(entityId,
        nameof(HelloHistoryEntity.Add),
        result);

    result = await context.CallActivityAsync<string>(nameof(SayHello), "London");
    await context.Entities.CallEntityAsync(entityId,
        nameof(HelloHistoryEntity.Add),
        result);

    HelloHistoryState state =
        await context.Entities.CallEntityAsync<HelloHistoryState>(entityId, nameof(HelloHistoryEntity.GetState));

    return state;
}

[Function(nameof(SayHello))]
public static string SayHello([ActivityTrigger] string name,
    FunctionContext executionContext)
{
    return $"Hello {name}!";
}

Using TaskEntity<T> with ActivatorUtilities


Now let's look at another approach where we need to pass additional parameters that are not registered in DI container. For example, CancellationToken from FunctionContext.

We can use ActivatorUtilities.CreateInstance<T>() to create the entity instance. It will resolve services from DI and use any manual arguments for the rest.
public class HelloHistoryEntity : TaskEntity<HelloHistoryState>
{
    private readonly ISomeService _someService;
    private readonly CancellationToken _cancellationToken;

    // Constructor: ISomeService from DI, CancellationToken as manual parameter
    public HelloHistoryEntity(ISomeService someService, CancellationToken cancellationToken)
    {
        _someService = someService;
        _cancellationToken = cancellationToken;
    }

    public async Task Add(string message)
    {
        _cancellationToken.ThrowIfCancellationRequested();

        await _someService.DoSomething();

        State.Messages.Add(message);
    }

    // Async for future proofing
    public async Task Reset()
    {
        State = new HelloHistoryState();
    }

    // Async for future proofing
    public Task<HelloHistoryState> GetState() => Task.FromResult(State);

    [Function(nameof(HelloHistoryEntity))]
    public static Task Run([EntityTrigger] TaskEntityDispatcher dispatcher, FunctionContext functionContext)
    {
        // Create entity using ActivatorUtilities
        // Resolving services from DI using FunctionContext and also can pass any manual args
        HelloHistoryEntity entity = ActivatorUtilities.CreateInstance<HelloHistoryEntity>(
            functionContext.InstanceServices,
            functionContext.CancellationToken);

        return dispatcher.DispatchAsync(entity);
    }
}
Note that the function method is now static. When the function method is non-static (like in the previous approach), the runtime needs to create an instance of the class to call the method, so it uses DI to resolve constructor dependencies. With a static method, no instance is needed, so we manually create it using ActivatorUtilities.

Hope this helps.

Happy Coding.

Regards,
Jaliya