Thursday, April 17, 2025

C# 14.0: Introducing Extension Members and Null-Conditional Assignment

Visual Studio 17.14.0 Preview 3.0 got released earlier today and now you can try out two nice C# 14.0 features that got released with .NET 10 Preview 3.0.

Extension Members


We all know about Extension Methods which was there since C# 3.0.
public static class INumberExtensions
{
    public static IEnumerable<int> WhereGreaterThan(this IEnumerable<int> sourceint threshold)
    {
        return source.Where(x => x > threshold);
    }

    public static bool AnyGreaterThan(this IEnumerable<int> sourceint threshold)
    {
        return source.WhereGreaterThan(threshold).Any();
    }
}
So here I have some extension methods for IEnumerable<int> which I can call like below:
IEnumerable<int> numbers = [1, 2, 3, 4, 5];

IEnumerable<int> largeNumbers = numbers.WhereGreaterThan(3);
bool hasLargeNumbers = numbers.AnyGreaterThan(3);
With C# 14.0, I can write the same with following syntax. Note: there is no use of this.
public static class INumberExtensions
{
    extension(IEnumerable<int> source)
    {
        public IEnumerable<int> WhereGreaterThan(int threshold)
            => source.Where(x => x > threshold);

        public bool AnyGreaterThan(int threshold)
            => source.WhereGreaterThan(threshold).Any();
    }
}
It also supports generic types, something like following:
using System.Numerics;

public static class INumberExtensions
{
    extension<T>(IEnumerable<T> source)
        where T : INumber<T>
    {
        public IEnumerable<T> WhereGreaterThan(T threshold)
            => source.Where(x => x > threshold);

        public bool AnyGreaterThan(T threshold)
            => source.WhereGreaterThan(threshold).Any();
    }
}

Null-Conditional Assignment


Consider the below class.
public class Person
{
    public required string Name { getinit}

    public DateOnly? Birthdate { getset}

    public string? Address { getset}
}
And say we need to have a method to update person details.
static void UpdatePerson(Person? personDateOnly birthdatestring address)
{
    if (person is null)
    {
        return;
    }

    person.Birthdate = birthdate;
    person.Address = address;
}
Here we need to explicitly check whether the person is null before assignment. Now with C# 14.0, we can do this:
static void UpdatePerson(Person personDateOnly birthdatestring address)
{
    person?.Birthdate = birthdate;
    person?.Address = address;
}
And this is exactly same as previous code, values will only get set if the person is not null.

Hope this helps.

Happy Coding.

Regards,
Jaliya

Wednesday, April 9, 2025

Azure DevOps: NuGet: Command Not Found with Ubuntu-Latest (24.04)

Our builds started failing today and it's Microsoft Servicing Tuesday even though it's Wednesday over here.

We are using ubuntu-latest in almost all our builds and started seeing the following error:

nuget: command not found

And our build pipelines was using NuGet.

task: NuGetAuthenticate@1
  displayName: NuGet Authenticate

script: nuget restore
  displayName: NuGet Restore

And then saw the following warning:

##[warning] See https://aka.ms/azdo-ubuntu-24.04 for changes to the ubuntu-24.04 image. 
Some tools (e.g. Mono, NuGet, Terraform) are not available on the image. 
Therefore some tasks do not run or have reduced functionality.

Have been seen that warning, but didn't put much attention. The link explains the failure.

So it's time to use dotnet restore (too bad we are late to the party)

task: NuGetAuthenticate@1
  displayName: NuGet Authenticate

task: DotNetCoreCLI@2
  displayName: .NET Restore
  inputs:
    command: 'custom'
    custom: 'restore'
    projects: '**/*.csproj'

And that's it, we are back to successful builds.

Look out for this one in your builds as well!

Hope this helps.

Happy Coding.

Regards,
Jaliya

Monday, April 7, 2025

Migrating Azure Durable Function App to Use Durable Task Scheduler: Running in Azure

In my previous post I wrote about Migrating Azure Durable Function App to Use Durable Task Scheduler: Running Locally. And in this post, let's see how to run an Azure Durable Function App with DTS in Azure.

As of today DTS supports function apps that uses App Service Plan or Functions Premium plan. And also it's not supported in all the regions. You can run the following command to check the supported regions.

az provider show `
    --namespace Microsoft.DurableTask `
    --query "resourceTypes[?resourceType=='schedulers'].locations | [0]" `
    --out table
DTS: Supported Regions
So I have an Azure Durable Function App, the same application which is mentioned in the above post (except the changes done to use DTS). It uses an App Service Plan and is on East US 2. So we are all good to start migrating the Azure Durable Function App to use DTS.

Assuming you have az cli installed and logged in, let's add durabletask extension.
az extension add --name durabletask

# If you have it installed already, upgrade it to the latest version
# az extension add --upgrade --name durabletask
Now, let's create the scheduler,
az durabletask scheduler create `
    --name "<SCHEDULER_NAME>" `
    --resource-group "<RESOURCE_GROUP_NAME>" `
    --location "<REGION>" `
    --ip-allowlist "[0.0.0.0/0]" `
    --sku-name "dedicated" `
    --sku-capacity "1"
This is going to take some time to complete.
az durabletask scheduler create
Make note of the endpoint. Now let's create a taskhub:
az durabletask taskhub create `
    --name "default" `
    --resource-group "<REESOURCE_GROUP_NAME>" `
    --scheduler-name "<SCHEDULER_NAME>"
az durabletask taskhub create
You can also do this in Azure Portal by searching for Durable Task Scheduler and create.
Durable Task Scheduler
Durable Task Scheduler
Now the Scheduler and the TaskHub is created, next we need to grant our function app access to this Scheduler and/or TaskHub. DTS only supports either user-assigned or system-assigned managed identity authentication.

So let's do the following. 
  1. Create an user-assigned managed identity.
  2. Assign a role to managed identity. It can be one of the following:
    1. Durable Task Data Contributor: Role for all data access operations. This role is a superset of all other roles.
    2. Durable Task Worker: Role used by worker applications to interact with the durable task scheduler. Assign this role if your app is used only for processing orchestrations, activities, and entities.
    3. Durable Task Data Reader: Role to read all durable task scheduler data. Assign this role if you only need a list of orchestrations and entities payloads.
  3. Assign the identity to the function app.
# 1. Create an user-assigned managed identity
az identity create `
    --resource-group "<RESOURCE_GROUP_NAME>" `
    --name "mi-func-dts"

## get the identity id
$managedIdentityClientId = az identity show `
    --resource-group "<RESOURCE_GROUP_NAME>" `
    --name "mi-func-dts" `
    --query clientId `
    --output tsv

# 2. Assign a role to managed identity

## Scope can be either to the entire scheduler or to the specific task hub

### scheduler
#scope = "/subscriptions/<SUBSCRIPTION_ID>/resourceGroups/<RESOURCE_GROUP_NAME>/providers/Microsoft.DurableTask/schedulers/<SCHEDULER_NAME>"

### task hub
$scope = "/subscriptions/<SUBSCRIPTION_ID>/resourceGroups/<RESOURCE_GROUP_NAME>/providers/Microsoft.DurableTask/schedulers/<SCHEDULER_NAME>/taskHubs/<TASKHUB_NAME>"

az role assignment create `
  --assignee "$managedIdentityClientId" `
  --role "Durable Task Data Contributor" `
  --scope "$scope"

# 3. Assign the identity to the function app
$managedIdentityResourceId = az resource show `
    --resource-group "<RESOURCE_GROUP_NAME>" `
    --name "mi-func-dts" `
    --resource-type Microsoft.ManagedIdentity/userAssignedIdentities `
    --query id `
    --output tsv

az functionapp identity assign `
    --resource-group "<RESOURCE_GROUP_NAME" `
    --name "<FUNCTION_APP_NAME>" 
    --identities "$managedIdentityResourceId"
Now we are almost done.

Final step is deploying new code and updating app settings. Code changes (Migrating Azure Durable Function App to Use Durable Task Scheduler: Running Locally) are deployed and app settings are updated as follows:
{
  "name""DTS_CONNECTION_STRING",
  "value""Endpoint=<SCHEDULER_ENDPOINT>;Authentication=ManagedIdentity;ClientID=<MANAGED_IDENTITY_CLIENTID>",
  "slotSetting"false
},
{
  "name""TASKHUB_NAME",
  "value""default",
  "slotSetting"false
}
Now let's hit the endpoint and make sure it's working.
Test the function app
Wonderful.

Now we want to look at the DTS Dashboard. For that, let's grant our Azure account access to DTS.
# Get the current user id
$assignee = az ad signed-in-user show `
    --query id `
    --output tsv

# Set the scope (can be either scheduler or task hub level), I am giving the user scheduler level
$scope = "/subscriptions/<SUBSCRIPTION_ID>/resourceGroups/<RESOURCE_GROUP_NAME>/providers/Microsoft.DurableTask/schedulers/<SCHEDULER_NAME>"

# Assign the role to the user
az role assignment create `
  --assignee "$assignee" `
  --role "Durable Task Data Contributor" `
  --scope "$scope"
Now go to: https://dashboard.durabletask.io/, and fill out the details required.

And there it is.
DTS Dashboard
DTS Dashboard: Orchestration Detail

Happy Coding.

Regards,
Jaliya

Thursday, April 3, 2025

Migrating Azure Durable Function App to Use Durable Task Scheduler: Running Locally

Durable Task Scheduler (DTS) is the latest addition to Azure Durable Functions. The public preview is announced a couple of weeks ago, and you can find it here: Announcing the public preview launch of Azure Functions durable task scheduler. Also if you are using Azure Durable Functions with Netherite,  Netherite will no longer receive official support after 31 March 2028. The replacement is DTS, so it's time to get into the game.

In this post I am not going to be writing what DTS is, above post is very detailed. Instead here in this post, let's see how we can migrate an existing durable function app to use DTS as the backend.

I have the following .NET 9.0 Isolated simple durable function app. Here I also have a simple Durable Entity as I want to make sure DTS supports Durable Entities as well.
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.DurableTask;
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Entities;

namespace FunctionsPlayground;

public static class Function
{
    [Function(nameof(HttpStart))]
    public static async Task<HttpResponseData> HttpStart(
        [HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req,
        [DurableClient] DurableTaskClient client,
        FunctionContext executionContext)
    {
        string instanceId =  await client.ScheduleNewOrchestrationInstanceAsync(nameof(RunOrchestrator));

        return await client.CreateCheckStatusResponseAsync(reqinstanceId);
    }

    [Function(nameof(RunOrchestrator))]
    public static async Task<List<string>> RunOrchestrator( [OrchestrationTrigger] TaskOrchestrationContext context)
    {
        EntityInstanceId entityId = new(nameof(HelloHistoryEntity)"helloHistory");

        await context.Entities.CallEntityAsync<string>(entityId nameof(HelloHistoryEntity.Reset));

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

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

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

        List<string> outputs = await context.Entities.CallEntityAsync<List<string>>(entityId nameof(HelloHistoryEntity.Get));
        return outputs;
    }

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

public class HelloHistoryEntity : TaskEntity<List<string>>
{
    public void Add(string message) => State.Add(message);

    public void Reset() => State = [];

    public List<string> Get() => State;

    [Function(nameof(HelloHistoryEntity))]
    public Task RunEntityAsync([EntityTrigger] TaskEntityDispatcher dispatcher)
    {
        return dispatcher.DispatchAsync(this);
    }
}
Now this is running fine and I want to change the backend to use DTS.

First step is setting up DTS emulator locally. For that, I am running the following docker commands. Note: Docker is a prerequisite.
docker pull mcr.microsoft.com/dts/dts-emulator:v0.0.5
docker run -d -p 8080:8080 -p 8082:8082 mcr.microsoft.com/dts/dts-emulator:v0.0.5
DTS Emulator Running Locally
Here:
  • 8080: gRPC endpoint that allows an app to connect to the scheduler
  • 8082: Endpoint for durable task scheduler dashboard
I can navigate to http://localhost:8082/ in the browser and ensure it's all good.
DTS Dashboard
Next step is updating the project.

First I am adding the following package to the project.
dotnet add package Microsoft.Azure.Functions.Worker.Extensions.DurableTask.AzureManaged --prerelease
Then updating the host.json configuring the durableTask extension.
{
  "version""2.0",
  "extensions": {
    "durableTask": {
      "hubName""%TASKHUB_NAME%",
      "storageProvider": {
        "type""azureManaged",
        "connectionStringName""DTS_CONNECTION_STRING"
      }
    }
  }
}
Now finally setting the appsettings for DTS in local.settings.json.
{
  "IsEncrypted"false,
  "Values": {
    "AzureWebJobsStorage""UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME""dotnet-isolated",
    "DTS_CONNECTION_STRING""Endpoint=http://localhost:8080;Authentication=None",
    "TASKHUB_NAME""default"
  }
}
Now I am locally all set to test the durable function app backed by DTS.

I have executed the HTTP function, and checked the DTS dashboard:
DTS Dashboard
DTS Dashboard: Orchestration Detail
Look at that, this is amazing. 



Hope this helps and do start evaluating DTS. If you find any issues: please do report it here: https://github.com/Azure/Durable-Task-Scheduler

Happy Coding.

Regards,
Jaliya