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:
- 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);
- 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:
- 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. - 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:
- Reinstall latest version of dotnet 5.x