Thursday, April 18, 2024

Session: App Service Networking Features for Developers at 2024 Global Azure, Auckland

Delivered a session today at 2024 Global Azure, Auckland, the title of the talk was App Service Networking Features for Developers.
 App Service Networking Features for Developers at 2024 Global Azure, Auckland
In the session, I demoed and talked through the following.
  • How we can add Private Endpoints to Azure Services (Azure Storage in this case)
  • How we can integrate an App Service to a VNET and verify it's communicating with a private endpoint-enabled Azure Service via internal IP
  • How App Service in another VNET can communicate with the above private endpoint-enabled Azure Service (via network peering)
Basically how to migrate from something like the below,
Public
To something like the below.
Restricted
Happy Coding.

Regards,
Jaliya

Wednesday, April 17, 2024

Azure API Management: Replace Backend Service URLs in Response Body

In this post, let's see how we can replace backend API URLs in the response body from an Azure API Management (APIM) policy.

Say, we have a backend API endpoint that has the following code.

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

WebApplication app = builder.Build();

app.UseHttpsRedirection();

app.MapGet("/endpoints", (HttpContext httpContext) =>
{
    string baseUrl = $"{httpContext.Request.Scheme}://{httpContext.Request.Host}";

    return new
    {
        StatusQueryUri = $"{baseUrl}/status",
        HealthQueryUri = $"{baseUrl}/health",
    };
})
.WithName("GetEndpoints")
.WithOpenApi(); // other endpoints

And it would work as follows.

Response from Backend API
Now if we are exposing this API via Azure APIM, we can't be returning internal endpoints. We need to replace the Base URL with the corresponding APIM API endpoints.

To achieve that we can use set-body policy and do something like the following.

<policies>
  <inbound>
    <base />
  </inbound>
  <backend>
    <base />
  </backend>
  <outbound>
    <base />
    <set-body>
    @{
        string urlToReplace = context.Request.Url.Scheme + "://" + context.Request.Url.Host;
        string urlToReplaceWith = context.Request.OriginalUrl.Scheme
          + "://" + context.Request.OriginalUrl.Host 
          + context.Api.Path;
          
        string response = context.Response.Body.As<string>();
        return response.Replace(urlToReplace, urlToReplaceWith);
    }
    </set-body>
  </outbound>
  <on-error>
    <base />
  </on-error>
</policies>

Here,

The output is as follows:

Response from APIM
Hope this helps.

Happy Coding.

Regards,
Jaliya

Tuesday, April 16, 2024

.NET Isolated Azure Durable Functions: Wait for Any Event and Wait for All the Events

In this post, let's see how we can wait for any event and wait for all the events in .NET Isolated Azure Durable Functions.

Let's consider the below code.

using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.DurableTask;
using Microsoft.DurableTask.Client;
using Microsoft.Extensions.Logging;
using System.Text.Json.Nodes;

namespace FunctionApp1;

public static class Function1
{
    [Function(nameof(HttpStart))]
    public static async Task<HttpResponseData> HttpStart(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
        [DurableClient] DurableTaskClient client,
        FunctionContext executionContext)
    {
        ILogger logger = executionContext.GetLogger(nameof(HttpStart));

        string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(nameof(Orchestrator));

        return client.CreateCheckStatusResponse(req, instanceId);
    }

    [Function(nameof(Orchestrator))]
    public static async Task<Dictionary<string, JsonObject>> Orchestrator(
        [OrchestrationTrigger] TaskOrchestrationContext context)
    {
        ILogger logger = context.CreateReplaySafeLogger(nameof(Orchestrator));

        List<string> events = [
            "Event1",
            "Event2",
            "Event3"
        ];

        Dictionary<string, Task<JsonObject>> tasks = [];

        foreach (string eventName in events)
        {
            logger.LogInformation($"http://localhost:7137/runtime/webhooks/durabletask/instances/{context.InstanceId}/raiseEvent/{eventName}");

            Task<JsonObject> task = context.WaitForExternalEvent<JsonObject>(eventName);
            tasks.Add(eventName, task);
        }

        // TODO: Handle WhenAny

        // TODO: Handle WhenAll
    }
}

Here I have a list of WaitForExternalEvent tasks, that we are going to listen to. The first scenario is we are going to resume execution when any of the events occur. The second scenario is we are going to resume execution when all of the events have occurred.

WhenAny

We can use the Task Parallel Libraries' (TPL) Task.WhenAny for this scenario.

// Handling WhenAny
Task<JsonObject> result = await Task.WhenAny(tasks.Values);

KeyValuePair<string, Task<JsonObject>> winner = tasks.Single(x => x.Value == result);
return new Dictionary<string, JsonObject>
{
    { winner.Key, await result }
};

Say for example, we are triggering "Event2", at that time the execution will resume.

WhenAny

WhenAll

We can use the Task Parallel Libraries' (TPL) Task.WhenAll for this scenario.

// Handling WhenAll
JsonObject[] results = await Task.WhenAll(tasks.Values);

Dictionary<string, JsonObject> resultsMap = tasks
    .Zip(results, (x, y) => new { x.Key, y })
    .ToDictionary(x => x.Key, x => x.y);

return resultsMap;
In this case, the execution won't resume until all the events are triggered.
WhenAll
Hope this helps.

Happy Coding.

Regards,
Jaliya

Sunday, April 14, 2024

.NET Isolated Azure Durable Functions: Specifying SubOrchestrator Instance Id

In Azure Durable Functions, there are times we need to run SubOrchestrations using a specific Instance Id

As far as I can recall in In Process durable functions, when calling a SubOrchestrator there was a specific overload to specify the instance Id. With Isolated Durable Functions, we do have an overload, but I feel it's not that intuitive, hence this small post.

In .NET Isolated Azure Durable Functions, this is how we can specify SubOrchestrator InstanceId.

Passing SubOrchestratorOptions
Here, for TaskOptions, we can pass SubOrchestrationOptions, a derived type of TaskOptions as follows.

await context.CallSubOrchestratorAsync("SubOrchestrator1", new SubOrchestrationOptions
{
    InstanceId = "SomeInstanceId",
});

Hope this helps.

Happy Coding.

Regards,
Jaliya

Monday, April 8, 2024

Middleware in .NET Isolated Azure Functions

In this post, let's have a look at Middleware in .NET Isolated Azure Functions. .NET Isolated functions supports middleware registration, following a similar model as in ASP.NET Core. With middleware, we can inject logic into the invocation pipeline, and before and after functions execute.

The ConfigureFunctionsWorkerDefaults method has an overload that we can register middleware as follows.
IHost host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults((context, builder) =>
    {
        // Register Middleware

        // This middleware is applied to all functions
        builder.UseMiddleware<MyCustomMiddleware>();

        // This middleware is only applied when running integration tests
        IConfiguration configuration = builder.Services.BuildServiceProvider().GetService<IConfiguration>();
        bool isRunningIntegrationTests = configuration.GetValue<bool>("IsRunningIntegrationTests");
         builder.UseWhen<IntegrationTestsFunctionMiddleware>((context) => isRunningIntegrationTests);
    })
    .Build();
To implement a middleware, you need to implement the interface IFunctionsWorkerMiddleware
internal sealed class IntegrationTestsFunctionMiddleware : IFunctionsWorkerMiddleware
{
    internal static class FunctionBindingTypes
    {
        public const string ServiceBus = "serviceBus";
    }

    public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
    {
        // If the function has a service bus output binding, short-circuit the execution of the function
        if (context.FunctionDefinition.OutputBindings.Values.Any(a => a.Type == FunctionBindingTypes.ServiceBus))
        {
            return;
        }

        await next(context);
    }
}
Above IntegrationTestsFunctionMiddleware short-circuits the execution of the function if the function has a serviceBus output binding.

Hope this helps.

Happy Coding.

Regards,
Jaliya