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