Wednesday, February 4, 2026

Sending messages to Azure Service Bus from Azure API Management

With Azure API Management (APIM), now you can send/publish messages directly to Azure Service Bus. Note: at the time of writing this post, this feature is still in preview. I am very excited for this feature, because now we can have a simple architecture, we don't need a separate backend code/service to send/publish messages.

This is done via the send-service-bus-message policy.

Let's have a look.

I have a Service Bus namespace and a user assigned Managed Identity. The Managed Identity has the Azure Service Bus Data Sender role assigned to the namespace. In the Service Bus namespace, I have a topic: sbt-apim

In my APIM, I have an API with an operation configured with the following policy.
<!-- Add policies as children to the <inbound>, <outbound>, <backend>, and <on-error> elements -->
<policies>
  <!-- Throttle, authorize, validate, cache, or transform the requests -->
  <inbound>
    <send-service-bus-message 
      topic-name="sbt-apim" 
      namespace="<ServiceBusNamespaceName>.servicebus.windows.net" 
      client-id="<ManagedIdentityClientId>">
      <message-properties>
        <message-property name="TenantId">@(context.Request.Headers.GetValueOrDefault("x-tenantId",""))</message-property>
      </message-properties>
      <payload>
        @(context.Request.Body.As<string>(preserveContent: true))
      </payload>
    </send-service-bus-message>
    <return-response>
      <set-status code="202" reason="Accepted" />
      <set-header name="Content-Type" exists-action="override">
        <value>application/json</value>
      </set-header>
      <set-body>{ "status": "Message Accepted" }</set-body>
    </return-response>
  </inbound>
  <!-- Control if and how the requests are forwarded to services  -->
  <backend>
    <base />
  </backend>
  <!-- Customize the responses -->
  <outbound>
    <base />
  </outbound>
  <!-- Handle exceptions and customize error responses  -->
  <on-error>
    <base />
  </on-error>
</policies>
Here I am using the send-service-bus-message policy in the inbound section to publish messages to a Service Bus topic. The client-id refers to the user assigned Managed Identity. You can also add custom message properties. Here I am passing the value of x-tenantId header as a custom property for demo purposes. Since we don't need a backend service, I am using return-response to immediately return a 202 Accepted response.

Let's test this.
curl 'https://<APIM_BASE_URL>/messaging/publish' `
--header 'x-tenantId: cfecb449-b568-4d0f-b24f-ea9762fb2bf6' `
--header 'Content-Type: application/json' `
--data '{
    "firstName": "John",
    "lastName": "Doe"
}'
Test cURL
And I can see a message published under the topic.
Published Message
Hope this helps.

More read:

Happy Coding.

Regards,
Jaliya

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