Jaliya
Jaliya's Blog
Wednesday, March 26, 2025
Azure DevOps Classic Release Pipelines: Deploying .NET 9 Isolated Azure Function App on Linux
Jaliya
Wednesday, March 19, 2025
Azure Functions Isolated: SQL Trigger
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(changes, new JsonSerializerOptions
{
WriteIndented = true,
Converters =
{
new JsonStringEnumConverter()
}
}));
}
}
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
-- 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
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"SqlConnectionString": "<SqlConnectionString>",
"WEBSITE_SITE_NAME": "func-sqltriggerdemo-local"
}
}
Azure SQL trigger for Functions
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 { get; set; }
}
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.
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 { get; set; }
/// <summary>
/// Domain name: Ex: "{your-tenant-name}.onmicrosoft.com"
/// </summary>
/// <remarks>Required</remarks>
public string Domain { get; set; }
/// <summary>
/// Client Application Id
/// </summary>
/// <remarks>Required</remarks>
public string ClientId { get; set; }
/// <summary>
/// Your Azure AD B2C Application Policy Id
/// </summary>
/// <remarks>Required</remarks>
public string SignUpSignInPolicyId { get; set; }
}
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.
Wednesday, February 26, 2025
.NET 10 Preview 1.0: Numeric Ordering for String Comparison
List<string> strings =
[
"3",
"5",
"05",
"10",
"11"
];
IOrderedEnumerable<string> orderedStrings = strings.Order();
05
10
11
3
5
// The new numerical string comparer
StringComparer numericStringComparer = StringComparer.Create(CultureInfo.CurrentCulture, CompareOptions.NumericOrdering);
IOrderedEnumerable<string> orderedStrings = strings.Order(numericStringComparer);
3
5
05
10
11
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 |
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.
Monday, February 10, 2025
Visual Studio 2022: HTTP Files and Request Variables
@HostAddress = http://localhost:5200
### CREATE
# @name createEmployee
POST {{HostAddress}}/employees
Content-Type: application/json
{
"firstName": "John",
"lastName": "Doe"
}
// @name createEmployee
{{<requestName>.(response|request).(body|headers).(*|JSONPath|XPath|<headerName>)}}
### GET by Id
GET {{HostAddress}}/employees/{{createEmployee.response.body.$.id}}
Accept: application/json
### UPDATE
PUT {{HostAddress}}/employees/{{createEmployee.response.body.$.id}}
Content-Type: application/json
{
"id": "{{createEmployee.response.body.$.id}}",
"firstName": "{{createEmployee.response.body.$.firstName}}",
"lastName": "Bloggs"
}
Use .http files in Visual Studio 2022
Wednesday, February 5, 2025
Azure DevOps Classic Release Pipelines: Using Task Group Parameters to Control Task Execution
Task Group Parameter |
Control Options -> Run this task -> Custom conditions |
Write-Host "##vso[task.setvariable variable=varIsSkipTask]$(IsSkipTask)"
and(succeeded(), ne(variables['varIsSkipTask'], 'true'))
Hope this helps.
Friday, January 24, 2025
.NET: How to Configure JsonSerializerOptions Correctly in a Builder ServiceCollection
FunctionsApplicationBuilder builder = FunctionsApplication.CreateBuilder(args);
builder.Services.Configure<JsonSerializerOptions>(options =>
{
options = new JsonSerializerOptions
{
WriteIndented = true,
};
});
JsonSerializerOptions configuredJsonSerializerOptions = builder.Services.BuildServiceProvider().GetService<IOptions<JsonSerializerOptions>>().Value;
// This will be false.
bool isWriteIndented = configuredJsonSerializerOptions.WriteIndented;
builder.Services.Configure<JsonSerializerOptions>(options =>
{
options.WriteIndented = true;
});
JsonSerializerOptions configuredJsonSerializerOptions = builder.Services.BuildServiceProvider().GetService<IOptions<JsonSerializerOptions>>().Value;
// This will be true.
bool isWriteIndented = configuredJsonSerializerOptions.WriteIndented;
Monday, January 20, 2025
EF Core 9.0: Reading EntityConfigurations from Classes with Non-Public Constructors
public record Employee
{
public int Id { get; set; }
public string Name { get; set; }
private class EmployeeConfiguration : IEntityTypeConfiguration<Employee>
{
private EmployeeConfiguration() { }
public void Configure(EntityTypeBuilder<Employee> builder)
{
builder.Property(e => e.Name)
.IsRequired()
.HasMaxLength(50);
}
}
}
Jaliya