Showing posts with label C#. Show all posts
Showing posts with label C#. Show all posts

Thursday, 18 December 2025

C# 14 Features You’ll Actually Use (With Real-Life Scenarios + Runnable Demo)

 If you learn best by seeing code run, this post is for you.

In this article we’ll walk through a small, runnable console demo that showcases a handful of high-signal C# 14 features:

At the end, we’ll compare why C# 14 feels nicer than earlier versions for these scenarios.

Prerequisites

  • .NET SDK 10.x

1) field keyword: validation on auto-properties (without a manual backing field)

Real-life scenario

You’re building a customer profile page. You want:

  • DisplayName to be trimmed
  • DisplayName to reject blank input

Historically, once you needed custom setter logic, you’d drop the auto-property and add a backing field.

Before (typical pre-C# 14 style)

private string _displayName = "";
public string DisplayName
{
get => _displayName;
set => _displayName = string.IsNullOrWhiteSpace(value)
? throw new ArgumentException("DisplayName cannot be blank.", nameof(value))
: value.Trim();
}

With C# 14 (field backed properties)

public required string DisplayName
{
get;
set => field = string.IsNullOrWhiteSpace(value)
? throw new ArgumentException("DisplayName cannot be blank.", nameof(value))
: value.Trim();
}

Why it’s better

  • You keep the clarity of auto-properties
  • You add logic without introducing another member (_displayName) and all the naming/consistency overhead

2) Null-conditional assignment: “set this only if the object exists”

Real-life scenario

You look up a customer in a cache. Cache misses are normal.

  • If the customer exists, set the loyalty tier
  • If it doesn’t exist, do nothing (no exceptions, no if block)

Before

if (customer is not null)
{
customer.LoyaltyTier = "Gold";
}

With C# 14

customer?.LoyaltyTier = "Gold";

Why it’s better

  • Expresses intent directly: “maybe update”
  • Reduces boilerplate and reduces the chance you forget the null check during refactors

3) nameof with unbound generics: logging the “shape” of a generic type

Real-life scenario

In telemetry/logging you often want to record which generic type definition you’re dealing with (the “shape”), not the exact T.

Example: you care that something is a Dictionary<,> in general — not whether it’s Dictionary<string, int> or Dictionary<Guid, decimal>.

Before (common workaround)

People often did one of these:

  • log a specific closed generic: nameof(Dictionary<string, int>) (not ideal)
  • use reflection: typeof(Dictionary<,>).Name (often yields names like Dictionary\2`)
Console.WriteLine(nameof(List<>)); // "Compile error"
Console.WriteLine(typeof(List<>).Name); // "List`1"
Console.WriteLine(nameof(Dictionary<string, int>)); // "Dictionary"
Console.WriteLine(typeof(Dictionary<,>).Name); // "Dictionary`2"

With C# 14

Console.WriteLine(nameof(List<>));        // "List"
Console.WriteLine(nameof(Dictionary<,>)); // "Dictionary"
Console.WriteLine(nameof(Dictionary<string, int>)); // "Dictionary"

Why it’s better

  • Readable, stable, and matches the simple names you expect (without backtick arity)

4) Extension members: extension “properties” + “methods” in one block

Real-life scenario

User input is messy. Across your app you frequently:

  • detect blank strings
  • normalize emails (trim + lower)
  • count words for notes/descriptions

Before (classic extension methods only)

public static class StringExtensions
{
public static bool IsBlank(this string value) =>
string.IsNullOrWhiteSpace(value);

public static string NormalizedEmail(this string value) =>
value.Trim().ToLowerInvariant();

public static int WordCount(this string value) =>
value.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
}

With C# 14 (extension members)

public static class StringExtensionsCSharp14
{
extension(string value)
{
public bool IsBlank => string.IsNullOrWhiteSpace(value);
public int WordCount =>
value.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
public string NormalizedEmail() =>
value.Trim().ToLowerInvariant();
}
}

What it feels like to use

var email = "  alex@example.com  ";
if (!email.IsBlank)
{
var normalized = email.NormalizedEmail();
Console.WriteLine(normalized);
}

Why it’s better

  • You can model “computed information” naturally as properties (IsBlankWordCount)
  • Grouping in an extension(...) block makes it easy to scan: “these are all the members we’re adding to string

5) Simple lambda parameters with modifiers: ref / out without repeating types

Real-life scenario

  • You parse input from a form/querystring (out).
  • You perform an in-place update (ref) — e.g., points, counters, totals.

Before C# 14, if you needed ref/out in a lambda you often had to repeat parameter types even though the compiler already knew them from the delegate signature.

Before (pre-C# 14 style: types are repeated)

delegate bool TryParse<T>(string text, out T result);
delegate void Doubler(ref int number);

TryParse<int> tryParseInt = (string text, out int result) =>
int.TryParse(text, out result);

Doubler doubleIt = (ref int number) => number *= 2;

With C# 14 (simple parameters + modifiers)

TryParse<int> tryParseInt = (text, out result) =>
int.TryParse(text, out result);

Doubler doubleIt = (ref number) => number *= 2;

Why it’s better

  • Less noise: you don’t repeat types the compiler can infer
  • Lambdas read closer to intent: “parse this, output result” / “modify this by ref”

How C# 14 is better than previous versions (in practical terms)

Here’s the “why you should care” summary:

Less boilerplate, more intent

  • field removes the need for backing fields in many everyday validation scenarios
  • null-conditional assignment removes repetitive null-check blocks

More readable code in large codebases

  • nameof(List<>) is a small feature, but it makes logging/telemetry code cleaner and less error-prone
  • extension members let you write “domain language” that reads naturally (email.IsBlank) instead of “utility-function soup”

Better refactoring ergonomics

  • fewer manual backing fields and fewer guard ifs means fewer places to accidentally break behavior during refactors

Reference

Wednesday, 15 November 2023

Use Microsoft Graph API with Azure AD Authentication in .Net Core, C#

 Microsoft Graph is a RESTful web API that enables you to access Microsoft Cloud service resources. It allows you to access data across Microsoft 365 services i.e. emails, chats, calendars, user profiles, etc.

REST APIs are built on OData standards so can use the Odata syntax to query data through graph API with Postman or programmatically. Here in this article, we will see, how to call the Graph APIs in the .Net Core Web API project with C#.

For this example, we will create an ASP.NET Core web API project and follow the below steps:

Step 1: Configure Azure AD Authentication from the Project side.

To configure the Azure AD authentication, we need to add a few settings in appsettings.json and the below code in Program.cs as well as a middleware to validate the Azure AD Token.

appsettings.json

"AzureAd": {
"Domain": "domain name",
"TenantId": "AD Tenant ID",
"ClientId": "AD Client Id",
"Instance": "https://login.microsoftonline.com",
"ClientSecret": "Client secret"
},
"GraphApi": {
"BaseUrl": "https://graph.microsoft.com/v1.0",
"Scopes": "User.Read.All"
},

Above all values must be available in the appsettings.json otherwise Azure AD authentication and Graph API calls will fail.

Program.cs

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;


builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(options =>
{
options.TokenValidationParameters.ValidateAudience = false;
options.TokenValidationParameters.ValidateIssuer = true;
options.TokenValidationParameters.ValidIssuers = new string[] { $"https://login.microsoftonline.com/{builder.Configuration["AzureAD:TenantId"]}/v2.0" };
}, options =>
{
builder.Configuration.Bind("AzureAd", options);
})
.EnableTokenAcquisitionToCallDownstreamApi(options =>
{
builder.Configuration.Bind("AzureAd", options);
})
.AddMicrosoftGraph(builder.Configuration.GetSection("GraphApi"))
.AddDistributedTokenCaches();

In the above code here, we are enabling the Authentication service to validate Azure AD Jwt auth Token and adding the Graph API support to make a call to Graph APIs using the AD Token.
Nuget packages required are:

Microsoft.Graph
Microsoft.Identity.Web.GraphServiceClient
Microsoft.AspNetCore.Authentication.AzureAD.UI and
Microsoft.Identity.Web

a Middleware to validate the token, i.e. JwtTokenValidationMiddleware.cs

using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Net;
using System.Security.Claims;

public class JwtTokenValidationMiddleware
{
private readonly RequestDelegate _next;
public JwtTokenValidationMiddleware(RequestDelegate next)
{
_next = next;
}

public async Task Invoke(HttpContext context)
{
bool isAuthenticated = false;
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
var AzureAdInstance = "value from appsettings.json";
var AzureAdTenantId = "value from appsettings.json";
var AzureAdClientId = "value from appsettings.json";

if (token != null)
{
//set the value for AzureAdTenantId, AzureAdClientId and AzureAdInstance as per your appsettings.json
string issuer = $"{AzureAdInstance}/{AzureAdTenantId}/v2.0";
string stsDiscoveryEndpoint = $"{AzureAdInstance}/{AzureAdTenantId}/v2.0/.well-known/openid-configuration";

try
{
var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint, new OpenIdConnectConfigurationRetriever());
var config = configManager.GetConfigurationAsync().Result;

var tokenHandler = new JwtSecurityTokenHandler();

var validationParameters = new TokenValidationParameters
{
ValidAudience = {AzureAdClientId},
ValidIssuer = issuer,
IssuerSigningKeys = config.SigningKeys,
ValidateLifetime = true,
};

var claim = tokenHandler.ValidateToken(token, validationParameters, out _);

if(claim != null)
isAuthenticated = true;

}
catch (Exception ex)
{
isAuthenticated = false;
}
}
if(isAuthenticated)
await _next(context);
else
{
context.Response.Clear();
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
await context.Response.WriteAsync("Unauthorized");
return;
}
}

}

In the code in Program.cs, you can configure as many TokenValidationParameters to validate the token accordingly as per your need.
With this, we are done with Azure AD authentication from the code side.

Note: Don’t forget to use the JwtTokenValidationMiddleware in the Configure method of startup.cs.

 app.UseMiddleware<JwtTokenValidationMiddleware>();

Step 2: Add the required resource scope permission for Graph APIs

Your Enterprise Application, which is used to generate the Azure AD token must have Microsoft Graph API permission w.r.t to Microsoft 365 resources you need to access.

For example, if you need to access the user’s profile like name, email, profile pic, org hierarchy etc then you would need User.Read.All scope permission assigned for the application.

Go to Azure AD and search your application and navigate to API Permissions add the required permissions for Graph API. In this example, I’m adding User.Read.All delegated permission to read the user’s profile.

Note: You might need admin consent for your required scopes.

With this step, we are done with Settings.

Step 3: Adding a service class for User profiles data fetching using Graph API. .i.e. UserProfileService.cs

using Microsoft.Graph;

namespace MyApplication
{

public class UserInfo
{
public string Name { get; set; }
public string EmailId { get; set; }
public string ProfilePic { get; set; }
}

public class UserProfileService: IUserProfileService
{
private readonly GraphServiceClient _graphServiceClient;

public ADUserRepository(GraphServiceClient graphServiceClient)
{
_graphServiceClient = graphServiceClient;
}

public async Task<UserInfo> GetUserInformation(string userId)
{
var result = await _graphServiceClient.Users[userId].GetAsync();

var user = new UserInfo
{
EmailId = result.Mail,
Name = result.DisplayName,
ProfilePic =""
};

try
{
var photoresponse = await _graphServiceClient.Users[userId].Photos["48x48"].Content.GetAsync();
if (photoresponse != null)
{

MemoryStream ms = new MemoryStream();
photoresponse.CopyTo(ms);
ms.Position = 0;
var fileBytes = ms.ToArray();
user.ProfilePic= $"data:image/png;base64,{Convert.ToBase64String(fileBytes)}";
}

}
catch (Exception ex)
{
//log your exception
}

return user;
}
}
}

and add this service to the Service collection in Program.cs as

builder.Services.AddScoped<IUserProfileService, UserProfileService>();

Note: Graph Service client can’t be used with singleton instance hence you have to add your service to IServiceCollection as scoped service.

We need GraphServiceClient to call the Graph APIs which requires credentials and scopes but with the above approach, it is simple as the GraphServiceClient instance is getting created by Microsoft Dependency service collection and getting injected at run time.

Hope you enjoyed the content, follow me for more like this, and please don’t forget to LIKE it. Happy programming.