Monday, May 27, 2024

.NET 9 and ASP.NET Core: Built-in Support for OpenAPI Document Generation

With .NET 9, ASP.NET Core now has built-in support for OpenAPI document generation in both controller-based and minimal APIs. For as long as I can remember, ASP.NET Core has been using Swagger to generate the Open API document. Now we have Microsoft.AspNetCore.OpenApi package, and technically we can get rid of using Swagger. The new package still doesn't support a rich UI like Swagger UI, and that's something to look forward to.

Now let's see how this works.

Install the package: Microsoft.AspNetCore.OpenApi, note: it has to be the latest preview as of today and that is 9.0.0-preview.4.24267.6 (or any newer version than this).
<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0-preview.4.24267.6" />
  </ItemGroup>

</Project>
Now we can create a simple API something like follows:
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenApi();

WebApplication app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.MapGet("/hello", () => "Hello world!")
    .WithDescription("Returns Hello");

app.Run();
You can access the OpenAPI document at: https://localhost:<port>/openapi/v1.json
OpenAPI Document
Read the following documentation to learn all the different customization options:

Hope this helps.

Happy Coding.

Regards,
Jaliya

Wednesday, May 22, 2024

C# 13.0: params Improvements

It's another exciting time of the year when Microsoft Build is happening, a lot of exciting announcements.

In this post, let's have a look at a C# 13.0 feature that is now available with the latest Visual Studio 2022 Version 17.11 Preview 1.

C# 13.0 is supported on .NET 9. To try this feature, make sure you are using the preview language version.
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net9.0</TargetFramework>
    <LangVersion>preview</LangVersion>
  </PropertyGroup>

</Project>
Prior to C# 13.0, when we are using params, the parameter must be a single-dimensional array, something like follows:
void DoSomething(params string[] items)
{
    
// do something with items
}
And we can call it as follows:
DoSomething("apple", "orange"); // comma separated list
DoSomething(["apple", "orange"]); // array
DoSomething(); // empty, the length of the params list is zero
With C# 13.0, params parameter type can be any recognized collection type, like List<T>, Span<T>, IEnumerable<T>, etc. You can even use your own collection types if they follow special rules.

So from C# 13.0, the following is totally valid.
void DoSomething(params IEnumerable<string> items)
{
    
// do something with items
}
That's pretty cool.

If you still haven't registered for Microsoft Build 2024, you are still not late. Register now for free and access all the exciting content.

Happy Coding.

Regards,
Jaliya

Tuesday, May 14, 2024

C# 12.0: .. Spread Element or Spread Operator

C# 12.0 introduced .. and its terminology is a bit confusing, some call it Spread Element and some call it Spread Operator.

Before going through the correct terminology, first, let's see what it does.

IEnumerable<string> sweetFruits = ["Apple", "Banana", "Mango", "Pineapple"];
IEnumerable<string> sourFruits = ["Orange", "Grapefruit", "Lemon", "Lime"];

IEnumerable<string> fruits = [.. sweetFruits, .. sourFruits];
// Output: Apple, Banana, Mango, Pineapple, Orange, Grapefruit, Lemon, Lime

Here .. is spreading the elements in a collection. We are spreading the sweetFruits and sourFruits, and then combining those to create fruits.

And we can use this feature in different ways.

For an example consider this.

IEnumerable<Employee> employees =
[
    new Employee("John Doe", "Contract"),
    new Employee("Jane Doe", "Permanent")
];

List<Employee> permanentEmployees = employees
    .Where(e => e.Type == "Permanent")
    .ToList();

We can use .. and filter permanentEmployees as follows and not do .ToList().

List<Employee> permanentEmployees =
[
    .. employees.Where(e => e.Type == "Permanent")
];

Now what do we call it? 

There is a nice explanation given in this post: .NET Blog: Refactor your code with C# collection expressions

Spread Element
I personally agree with the explanation and even the feature specification uses the term Spread Element. But there are some places in official .NET documentation (like here: C# 12: Collection Expressions) that refer .. as Spread Operator.

Hopefully, we can get this terminology consistent across.

Hope this helps.

Happy Coding.

Regards,
Jaliya


Update 15/05/2024:

Reached out to .NET team and they are already in the process of addressing inconsistencies, both in the feature spec and the docs. It is going to be called the Spread Element and not Spread Operator.

Also, note that .. is used in three different places in the language: in collection expressions to indicate a spread element, in list patterns to indicate a slice pattern and as the range operator. The only location where it’s an operator is the range operator.

Wednesday, May 1, 2024

Visual Studio: New Durable Functions Project: System.InvalidOperationException: Synchronous operations are disallowed

When creating a new Azure Function App Project and selecting Durable Functions Orchestration, right now the basic functionality is erroring out when the default Http Function is triggered.

Error: System.InvalidOperationException: Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.


Unfortunately, this is a known issue (https://github.com/Azure/azure-functions-dotnet-worker/issues/2425) with the templates and hopefully, the templates will get updated soon.

For the time being, you can update the code in the HttpStart function to use CreateCheckStatusResponseAsync as follows.
[Function("Function1_HttpStart")]
public static async Task<HttpResponseData> HttpStart(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
    [DurableClient] DurableTaskClient client,
    FunctionContext executionContext)
{
    ILogger logger = executionContext.GetLogger("Function1_HttpStart");

    // Function input comes from the request content.
    string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(
        nameof(Function1));

    logger.LogInformation("Started orchestration with ID = '{instanceId}'.", instanceId);

    
// Returns an HTTP 202 response with an instance management payload.
    // See https://learn.microsoft.com/azure/azure-functions/durable/durable-functions-http-api#start-orchestration

    return await client.CreateCheckStatusResponseAsync(req, instanceId);
}
Hope this helps.

Happy Coding.

Regards,
Jaliya

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

Tuesday, March 19, 2024

App Service Outbound Traffic through VNet Gets 403 When Trying to Access Another App Service with Public Network Access Enabled, but has a Private Endpoint

In this post, let's go through an interesting scenario related to App Service networking.

- App A: is integrated into VNet A.
App A: Networking
- App B: has Public network access enabled with no access restrictions. But it has a Private Endpoint in VNet B.
App B: Networking
- App C: has Public network access enabled with no access restrictions. No private endpoints.
App C: Networking
Now the interesting part. I am seeing the following access behavior.
  • App A -> App B: 403 Forbidden. From anywhere else -> App B: 200.
  • App A -> App C: 200
I was scratching my head for a couple of days trying to understand why is that App A -> App B: 403 Forbidden. Because App B has Public network access enabled and also a private endpoint and public access can co-exist on an app.

I can fix this by peering VNet A and VNet B. But I still needed to figure out why App A isn't reaching App B on the default endpoint (not on the private endpoint) as it's like App B is receiving traffic from any other source.

Finally, I got an explanation from Mads Wolff Damg√•rd (a Principal Product Manager at Microsoft).

This is happening because I had Service Endpoint registered to Microsoft.Web in VNet As' integration subnet for App A
VNet As' integration subnet for App A
When we have a service endpoint registered, the traffic is sent over the public channel, but as service endpoint traffic. This uses the same protocol as the private endpoint and will then try to parse it as a private endpoint call, but since VNet A has no knowledge about the private endpoint, the traffic fails with 403.

Once I removed the service endpoint registration, App A was able to reach App B without any issues.

So hope someone finds this helpful!

Happy Coding.

Regards,
Jaliya