Wednesday, March 26, 2025

Azure DevOps Classic Release Pipelines: Deploying .NET 9 Isolated Azure Function App on Linux

If you are using Azure DevOps classic releases, you might have noticed, that we still don't have Runtime stack support for DOTNET-ISOLATED|9.0 in Azure Functions Deploy task.
Azure Functions Deploy
So how can we use this to deploy a .NET 9 Isolated Azure Function App on Linux.

It's quite easy, you can just type in DOTNET-ISOLATED|9.0 as the Runtime stack, and upon deployment the correct .NET Version will get set.
Azure Function App on Linux: .NET Version
Hope this helps.

Happy Coding.

Regards,
Jaliya

Wednesday, March 19, 2025

Azure Functions Isolated: SQL Trigger

In this post, let's see how we can use an Isolated Azure Function to monitor a SQL table for changes.

Let's start by creating an new Azure Functions project. I am choosing a Function template as SQL trigger. 
Isolated Azure Function: SQL trigger
Once the project is created, it's going to look more or less like the below (I changed the default ToDoItem POCO class to match my table)
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Extensions.Sql;
using Microsoft.Extensions.Logging;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace FunctionApp1;

public class Function1
{
    private readonly ILogger _logger;

    public Function1(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<Function1>();
    }

    [Function("Function1")]
    public void Run(
        [SqlTrigger("[dbo].[Users]""SqlConnectionString")] IReadOnlyList<SqlChange<User>> changes,
            FunctionContext context)
    {
        _logger.LogInformation("SQL Changes: {ChangeSet}",
            JsonSerializer.Serialize(changesnew JsonSerializerOptions
            {
                WriteIndented = true,
                Converters =
                {
                    new JsonStringEnumConverter()
                }
            }));
    }
}

public class User
{
    public int Id { getset}

    public string FirstName { getset}

    public string LastName { getset}
}
The SQL trigger uses Change Tracking (SQL Server) functionality to monitor a SQL table for changes. And once it detects a change which can be either when a row created, updated, or deleted, the function is triggered.

So before running the project, we need to enable Change Tracking in our targeted table in the database.
-- 1. enable change tracking for the database
ALTER DATABASE ['<DatabaseName>']
SET CHANGE_TRACKING = ON
(CHANGE_RETENTION = 2 DAYS, AUTO_CLEANUP = ON);
 
-- 2. enable change tracking for the table
ALTER TABLE <TableName>
ENABLE CHANGE_TRACKING;

---- disable change tracking for the table
--ALTER TABLE <TableName>
--DISABLE CHANGE_TRACKING;

---- disable change tracking for the database
--ALTER DATABASE ['<DatabaseName>']
--SET CHANGE_TRACKING = OFF  
And now since we are going to be doing a test run of this function locally first, we need to add a special environment variable: WEBSITE_SITE_NAME to local.settings.json.
{
  "IsEncrypted"false,
  "Values": {
    "AzureWebJobsStorage""UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME""dotnet-isolated",
    "SqlConnectionString""<SqlConnectionString>",
    "WEBSITE_SITE_NAME""func-sqltriggerdemo-local"
  }
}
And now we can run Azure Function project and let's make a change to the data in the table.
SQL trigger
More read:
   Azure SQL trigger for Functions

Hope this helps.

Happy Coding.

Regards,
Jaliya

Thursday, March 13, 2025

C# 14.0: Introducing the field keyword

In this post, let's have a look at a nice feature that is coming with C# 14.0

Currently it's still on preview, so you can try this feature out with .NET 9.0 (C# 14.0 is supported on .NET 10 though) or .NET 10 .0 with LangVersion set to preview.

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

or

<PropertyGroup>
  <TargetFramework>net10.0</TargetFramework>
  <LangVersion>preview</LangVersion>
  ...
</PropertyGroup>

Now let's have a look.

Consider the following class.

public class Employee
{
    // Other properties

    public required DateOnly Birthday { getset}
}

Say you have a requirement, where Employees Age needs to be >= 21. We can introduce that validation when setting a value for the Birthday, but in order to do that, and just for that, we need to add a backing field for Birthday, something like below:

public class Employee
{
    private const int MinAge = 21;

    // Other properties

    private DateOnly _birthday;

    public required DateOnly Birthday
    {
        get => _birthday;
        set
        {
            ArgumentOutOfRangeException.ThrowIfLessThan(value DateOnly.FromDateTime(DateTime.Now.AddYears(-MinAge)));

            _birthday = value;
        }
    }
}

But what if we can do the same without using the backing field? C# 14.0 introduces a new contextual keyword field.

public class Employee
{
    private const int MinAge = 21;

    // Other properties

    public required DateOnly Birthday
    {
        get;
        set
        {
            ArgumentOutOfRangeException.ThrowIfLessThan(value DateOnly.FromDateTime(DateTime.Now.AddYears(-MinAge)));

            field = value;
        }
    }
}

So here just as a value contextual keyword, we now have a field contextual keyword. We no longer need the backing field, therefore we also don't need to provide a body for the get accessor.

I am loving it.

Keep watching this space for more C# 14.0 features:
   What's new in C# 14

Happy Coding.

Regards,
Jaliya

Sunday, March 2, 2025

ASP.NET Core: Configuring Authentication with Azure AD B2C using Explicit Configuration

In this post let's see how we can configure Authentication in an ASP.NET Core Web API with Azure AD B2C and most importantly using explicit configuration.

The standard approach is something like below.

services
    .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(Configuration, "AzureAdB2C");

This I am highly against with, because we are picking up Configuration by the section named "AzureAdB2C" and it's not explicit. There are be many options that can be specified in section, and when we bound it like this, we don't know which are required and which are not.

So I personally always prefer being explicit over implicit.

public class AzureAdB2COptions
{
    /// <summary>
    /// Instance name: Ex: "https://{your-tenant-name}.b2clogin.com"
    /// </summary>
    /// <remarks>Required</remarks>
    public string Instance { getset}

    /// <summary>
    /// Domain name: Ex: "{your-tenant-name}.onmicrosoft.com"
    /// </summary>
    /// <remarks>Required</remarks>
    public string Domain { getset}

    /// <summary>
    /// Client Application Id
    /// </summary>
    /// <remarks>Required</remarks>
    public string ClientId { getset}

    /// <summary>
    /// Your Azure AD B2C Application Policy Id
    /// </summary>
    /// <remarks>Required</remarks>
    public string SignUpSignInPolicyId { getset}
}

And then, use the following overload.

AzureAdB2COptions azureAdB2COptions = 
    Configuration.GetSection("AzureAdB2C").Get<AzureAdB2COptions>();

services
    .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(jwtBearerOptions =>
    {
        jwtBearerOptions.Audience = azureAdB2COptions.ClientId;
    },
    identityOptions =>
    {
        identityOptions.Instance = azureAdB2COptions.Instance;
        identityOptions.Domain = azureAdB2COptions.Domain;
        identityOptions.ClientId = azureAdB2COptions.ClientId;
        identityOptions.SignUpSignInPolicyId = azureAdB2COptions.SignUpSignInPolicyId;
    });

Hope this helps.

Happy Coding.

Regards,
Jaliya

Wednesday, February 26, 2025

.NET 10 Preview 1.0: Numeric Ordering for String Comparison

After so much of waiting, the first preview of .NET 10 is now available. This comes with new .NET libraries, some new C# 14 features and many more.

In this post let's have a look a new .NET library feature that was initially requested back in 2015, and finally that's made into .NET 10.

Consider the following list of strings.
List<string> strings =
[
    "3",
    "5",
    "05",
    "10",
    "11"
];
If you order these by doing something below,
IOrderedEnumerable<string> orderedStrings = strings.Order();
The order would be,
05
10
11
3
5
These are lexicographically ordered. But numerically, "3" is less than "05" and also "5" and "05" are equal.

Now we have new NumericOrdering comparer for strings.
// The new numerical string comparer
StringComparer numericStringComparer =  StringComparer.Create(CultureInfo.CurrentCulture, CompareOptions.NumericOrdering);
IOrderedEnumerable<string> orderedStrings = strings.Order(numericStringComparer);
And now the output would be as we expect.
3
5
05
10
11
Love it.

Read more:

Happy Coding.

Regards,
Jaliya

Monday, February 17, 2025

Azure DevOps Classic Release Pipelines: Read Variables in a Variable Group and Update Azure App Service AppSettings

In this post let's see how to read variables in a Variable Group and deploy them to Azure App Service as app settings from a Classic Azure DevOps Release Pipeline.

In the release pipeline I have Azure CLI task added and the release pipeline is running on Azure Hosted windows-latest agent.

Release Pipeline
In the Azure CLI task, I am doing the following.

Acquire a PAT (Personal Access Token) and set it.

$PAT = "<YOUR_PAT>"
$env:AZURE_DEVOPS_EXT_PAT = $PAT

Now set the default organization and project for az devops command.

az devops configure --defaults `
    organization=https://dev.azure.com/<YOUR_ORGANIZATION>/ `
    project=<YOUR_PROJECT>

Get list of variables in the variable group by Variable Group Id. You can find Variable Group Id in the URL of the Variable Group detail page.

$variablesJson = az pipelines variable-group variable list `
    --group-id <YOUR_VARIABLE_GROUP_ID> `
    --org https://dev.azure.com/<YOUR_ORGANIZATION>/ `
    --project <YOUR_PROJECT>

If we output $variablesJson, it would be something like following.

{
  "SomeOptions__Key1": {
    "isSecret"null,
    "value""<Value1>"
  },
  "SomeOptions__Key2": {
    "isSecret"null,
    "value""<Value2>"
  }
}

Convert the $variablesJson to app settings format that Azure App Service expects.

$variablesAppSettings = $variablesJson `
    | ConvertFrom-Json `
    | ForEach-Object { $_.PSObject.Properties } `
    | ForEach-Object ` {
        $key = $_.Name
        $value = $_.Value.value
        @{ 
            name = $key;
            slotSetting = $false;
            value = $value 
        }
}

Save the app settings to a temporary file.

ConvertTo-Json $variablesAppSettings | Out-File "$(System.DefaultWorkingDirectory)\appsettings-updated.json"

appsettings-updated.json would look like below.

[
  {
    "name""SomeOptions__Key1",
    "value""<Value1>",
    "slotSetting"false
  },
  {
    "name""SomeOptions__Key2",
    "value""<Value2>",
    "slotSetting"false
  }
]

Now finally update the app settings in the web app.

$resourceGroup = "<resourceGroup>"
$webAppName = "<webAppName>"

az webapp config appsettings set `
    --resource-group $resourceGroup `
    --name $webAppName `
    --settings "@$(System.DefaultWorkingDirectory)\appsettings-updated.json"

Hope this helps.

Happy Coding.

Regards,
Jaliya

Monday, February 10, 2025

Visual Studio 2022: HTTP Files and Request Variables

It's a very common scenario that we want to call an endpoint and use the result in subsequent requests. In this post, let's see how we can make use of Request Variables in .http files in Visual Studio 2022 to achieve that. Request Variable is a special kind of a variable (see my previous post for use of variables Visual Studio 2022: HTTP Files and Variables).

So let's start. The first step is to give a request a name.
@HostAddress = http://localhost:5200

### CREATE
# @name createEmployee

POST {{HostAddress}}/employees
Content-Type: application/json
{
   "firstName": "John",
   "lastName": "Doe"
}
Here, the comment: # @name createEmployee which is located just before the request specifies the name of the request. You can use following syntax if you prefer, which is also valid.
// @name createEmployee
Now we can reference this particular named request (createEmployee) using following syntax.
{{<requestName>.(response|request).(body|headers).(*|JSONPath|XPath|<headerName>)}}
For example, say the above endpoint is returning a JSON response, something like the following.
POST: Employee
Now I can use the returned id to Get the employee by Id.
### GET by Id
GET {{HostAddress}}/employees/{{createEmployee.response.body.$.id}}
Accept: application/json
GET: Employee
I can even use the request variable in the request body. Say I want to update the existing employee with a different lastName.
### UPDATE
PUT {{HostAddress}}/employees/{{createEmployee.response.body.$.id}}
Content-Type: application/json
{
    "id": "{{createEmployee.response.body.$.id}}",
    "firstName": "{{createEmployee.response.body.$.firstName}}",
    "lastName": "Bloggs"
}
PUT: Employee

Hope this helps.

Happy Coding.

Regards,
Jaliya

Wednesday, February 5, 2025

Azure DevOps Classic Release Pipelines: Using Task Group Parameters to Control Task Execution

In this post let's see how we can use Parameters in a Task Group to control Task execution in Azure DevOps Classic Release Pipelines.

Let's say we have a Task Group that accepts following parameter.
Task Group Parameter
Now based on the value (true/false) passed in for this parameter, say I want to skip a particular Task. For that, we can use tasks Control Options -> Run this task -> Custom conditions.
Control Options -> Run this task -> Custom conditions
First step is initializing a release level variable with the value of the parameter. Note: I couldn't figure out how to access parameters directly in the condition, hence using a variable. If you find out a way, please do leave a comment. 

We can add in a PoweShell Task and do follows to initialize a variable.
Write-Host "##vso[task.setvariable variable=varIsSkipTask]$(IsSkipTask)"
Set Variable
And now, we can use the variable in custom condition as follows.
and(succeeded(), ne(variables['varIsSkipTask'], 'true'))
Control Options -> Run this task -> Custom conditions: Skip Task
And that's it.

Now when I run a release with IsSkipTask = true ,
IsSkipTask = true
Task is Skipped.
Task is skipped
Else,
Task is not skipped
Task is not getting skipped.

Hope this helps.

Happy Coding.

Regards,
Jaliya

Friday, January 24, 2025

.NET: How to Configure JsonSerializerOptions Correctly in a Builder ServiceCollection

Recently saw an issue in one of our applications that Configures JsonSerializerOptions as part of the builders ServiceCollection (FunctionsApplicationBuilderWebApplicationBuilder etc). 

To give a minimal example, say we want to configure JsonSerializerOptions.WriteIndented to true (in configuration it's false by default).
FunctionsApplicationBuilder builder = FunctionsApplication.CreateBuilder(args);

builder.Services.Configure<JsonSerializerOptions>(options =>
{
    options = new JsonSerializerOptions
    {
        WriteIndented = true,
    };
});
Above seems correct and could be easily unnoticeable in reviewing a PR.

But above is actually incorrect. If you resolve the JsonSerializerOptions and inspect the value of WriteIndented, it would still be false.
JsonSerializerOptions configuredJsonSerializerOptions = builder.Services.BuildServiceProvider().GetService<IOptions<JsonSerializerOptions>>().Value;

// This will be false.
bool isWriteIndented = configuredJsonSerializerOptions.WriteIndented;
The correct way is,
builder.Services.Configure<JsonSerializerOptions>(options =>
{
    options.WriteIndented = true;
});
That is rather than initializing a new JsonSerializerOptions with required configurations, update the existing JsonSerializerOptions that was supplied in the delegate.

Now JsonSerializerOptions will have the correct value.
JsonSerializerOptions configuredJsonSerializerOptions = builder.Services.BuildServiceProvider().GetService<IOptions<JsonSerializerOptions>>().Value;

// This will be true.
bool isWriteIndented = configuredJsonSerializerOptions.WriteIndented;
This is a simple mistake that could potentially cause a lot of issues, so hope this helps.

Happy Coding.

Regards,
Jaliya

Monday, January 20, 2025

EF Core 9.0: Reading EntityConfigurations from Classes with Non-Public Constructors

In this post let's have a look at another feature in EF Core 9.0.

Consider the following code example.
public record Employee
{
    public int Id { getset}

    public string Name { getset}

    private class EmployeeConfiguration : IEntityTypeConfiguration<Employee>
    {
        private EmployeeConfiguration() { }

        public void Configure(EntityTypeBuilder<Employee> builder)
        {
            builder.Property(e => e.Name)
                .IsRequired()
                .HasMaxLength(50);
        }
    }
}
Prior to EF Core 9.0, if you try to ApplyConfigurationsFromAssembly, above EmployeeConfiguration will not be discovered. Because the entity configurations had to be types with a public, parameterless constructor.

But with EF Core 9.0, above EmployeeConfiguration will be discovered and we don't need to have a have a public type anymore.

While maintaining Entity Configuration along with Entity seem polluting the domain, I can certainly see an advantage. We now no longer have to look at a different file to see a particular entities configurations, everything is in one place. 

At least now we don't have a code constraint, the developer can decide which approach (public type vs private type) they are going to take.

Happy Coding.

Regards,
Jaliya