Multithreaded code

HttpClient - how to instantiate it properly (hint: it’s a singleton)

HttpClient is meant to be used as a singleton, ideally using a single instance for your entire application. This has a few implications:

  1. You shouldn’t use DefaultRequestHeaders for things like authorization, as those change from one request to another. Instead, build an HttpRequestMessage and send it using HttpClient’s SendAsync method
var message = new HttpRequestMessage(HttpMethod.Post, requestUri)
{
    Content = content
};

message.Headers.Add("Accept", "application/json");
message.Headers.Add("Authorization", $"Bearer {token}");

client.SendAsync(message);
  1. You can have multiple instances of HttpClient, but ideally they should share a single request handler in order to pool connections (but really, just set the headers on the message and not directly on the client)
using HttpClientHandler handler = new HttpClientHandler();

async Task WorkWithApiA()
{
    using var client = new HttpClient(handler, false);
    client.DefaultRequestHeaders.Add("X-Secret-Auth-Token", "foo");

    await client.GetAsync("/");
    // ...
}

async Task WorkWithApiB()
{
    using var client = new HttpClient(handler, false);
    client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", "foo");

    await client.GetAsync("/");
    // ...
}

– via this GitHub issue

ConfidentialClientApplication (MSAL) - is it thread safe?

It..depends 😱.

Basically, you’re supposed to have a confidential client application per token cache. But how many token caches you have depends on the authentication flow you’re using:

  1. When you’re using something like on-behalf-of, where the application is using an identity other than its own (i.e. the user’s identity and permissions) to call another web API, then you’re supposed to have a token cache per session, as documented here. This means using an instance of ConfidentialClientApplication for each session.
  2. When you’re using client credentials authentication, in which your app has its own identity and uses that to authenticate, then it’s ok to use ConfidentialClientApplication as a singleton since there will only be one access token in the in-memory cache, no matter how many users and sessions there are. This is documented here.

– via this GitHub issue and, of course, StackOverflow

DataLakeServiceClient and friends - can I use them as singletons?

All the client classes are thread-safe, which is pretty cool - you can use them as singletons for a given set of constructor parameters. This includes stuff like DataLakeServiceClient, DataLakeFileSystemClient, DataLakeDirectoryClient, and DataLakeFileClient.

From the docs:

We guarantee that all client instance methods are thread-safe and independent of each other.

See also Thread safety and client lifetime management for Azure SDK objects (emphasis mine):

Because Azure SDK clients are thread-safe, there’s no reason to construct multiple SDK client objects for a given set of constructor parameters. Treat Azure SDK client objects as singletons once constructed.

Thread.Sleep in async code

Use Task.Delay.

– via Stack Overflow

Locking async code

Use SemaphoreSlim instead of lock/Monitor.TryEnter. See the docs and the example below. Don’t forget to use the SemaphoreSlim(Int32, Int32)) constructor. Always Release.

private static readonly SemaphoreSlim Semaphore = new(1, 1);
//...

await Semaphore.WaitAsync();
try
{
    // do stuff
}
finally
{
    Semaphore.Release();
}

There’s an interesting discussion about locking async code over on StackOverflow too, including a very thoughtful answer by Eric Lippert.

General

Injecting configuration objects in your services/controllers

Let’s say you want to abstract reading configuration values from the appsettings.json file. You can do this by creating a class that represents the configuration section you want to read, and then injecting that into your services/controllers.

For example:

// appsettings.json
{
    "Fancy": {
        "FancyIs": "FancyDoes"
    }
}
// in FancyConfiguration.cs
public class FancyConfiguration
{
    public string FancyIs { get; set; }
}

// in Program.cs
builder.Services.Configure<FancyConfiguration>(builder.Configuration.GetSection("Fancy"));

// in your controller
public class MyController : ControllerBase
{
    private readonly FancyConfiguration _fancyConfig;

    public MyController(IOptions<FancyConfiguration> fancyConfig)
    {
        _fancyConfig = fancyConfig.Value;
    }    
}

Get rid of warnings such as “[CS8618] Non-nullable property ‘Topic’ must contain a non-null value when exiting constructor. Consider declaring the property as nullable.”

Nullable reference types are enabled.

You can disable them by changing Nullable to disable in your .csproj file:

<PropertyGroup>
     <Nullable>disable</Nullable>
</PropertyGroup>

Or, you can make the strings optional by adding ? to the end of the type (but that’s madness 🤷🏻‍♂️):

public string? Topic { get; set; }

Or, you can initialize it with default! (which doesn’t look verbose at all, no sirree):

public string Topic { get; set; } = default!;

– via StackOverflow

Log “Information” messages in Application Insights

First, make sure Application Insights is set up properly.

Then, make sure to add an “Application Insights” section to the “Logging” section of your appsettings.json file, as seen below. This is important, since by default the SDK adds a default logging filter that instructs Application Insights to capture only Warning logs and higher.

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    },
    "ApplicationInsights": {
      "LogLevel": {
        "Default": "Information"
      }
    }
  }
}

Just so you know, you can also achieve this by setting Logging__LogLevel__Default and Logging__ApplicationInsights__LogLevel__Default in the Configuration section of your web app.

– via Microsoft Learn

Tooling (i.e. dotnet)

How to install the ASP.NET development certificate

dotnet dev-certs https --trust

dotnet watch - avoid being continually asked if I want to restart my app (Yes (y) / No (n) / Always (a) / Never (v)?)

export DOTNET_WATCH_RESTART_ON_RUDE_EDIT=1

– via GitHub

dotnet - stop sending telemetry to Microsoft

export DOTNET_CLI_TELEMETRY_OPTOUT=1

dotnet - avoid launching a browser when running dotnet watch run

SET DOTNET_WATCH_SUPPRESS_LAUNCH_BROWSER=1

– via ASP.NET Hacker

Fix Azure Functions build issues with Microsoft.Azure.WebJobs.Script.ExtensionsMetadataGenerator

Every time I update dotnet to the latest 7.x version, I start getting .nuget/packages/microsoft.azure.webjobs.script.extensionsmetadatagenerator/4.0.1/build/Microsoft.Azure.WebJobs.Script.ExtensionsMetadataGenerator.targets(37,5): error : Metadata generation failed. Exit code: '137' Error: 'Failed to initialize CoreCLR, HRESULT: 0x80004005' whenever building my net6.x, v4 functions project. I’ll just leave this here for next time 😉.

Here’s what I shouldn’t try ‘cause it won’t work:

  • Updated all NuGet packages
  • Downloaded and installed dotnet-core-uninstall
  • Ran dotnet sdk check for a list of the SDK versions I had installed
  • sudo ./dotnet-core-uninstall remove <each_and_every_version_below_6x> –sdk
  • Reinstalled latest version of dotnet 6.x as well
  • Nothing worked

Here’s what I should do:

– some tips here, and here