When working with HTTP requests in Zig, retrieving and printing response headers can be straightforward with the standard library. Here’s a concise example demonstrating how to perform a GET request and list the response headers from https://ziglang.org/.
This guide covers how to configure Entra authentication for a React Single Page Application (SPA) and a .NET API backend, enabling access to Microsoft Dataverse using the On-Behalf-Of (OBO) flow.
1. Register the Backend API Application
Name:myapp-api
Navigate to Azure Portal → Azure Active Directory → App registrations → New registration.
Name: myapp-api
Supported account types: Single tenant
Redirect URI: leave blank.
After registration:
Expose an API:
Set the Application ID URI to: api://<myapp-api-app-id>
Add a Scope:
Name: user_impersonation
Display Name/Description: “Access Dataverse data”
State: Enabled
Grant permissions:
Go to API permissions → Add a permission.
Choose APIs my organization uses, search for Dataverse or Dynamics CRM.
4. Register the Backend App as a Dataverse App User
Copy the myapp-api Application ID.
Go to Power Platform Admin Center → App Users → New App User
Paste the Application ID.
Assign the appropriate security role for Dataverse access.
5. Backend Code Setup
Program.cs
// Choose credential source based on environment (development vs production)TokenCredential credential =builder.Environment.IsDevelopment()switch{true=>new DefaultAzureCredential(),false=>new DefaultAzureCredential()};// Configure authentication using Microsoft Identity Webbuilder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd")).EnableTokenAcquisitionToCallDownstreamApi().AddDownstreamApi("DownstreamDataverseApi",builder.Configuration.GetSection("DownstreamDataverseApi")).AddInMemoryTokenCaches();// Configure HTTP client for Dataverse API callsbuilder.Services.AddHttpClient("DataverseClient", client =>{client.BaseAddress=new Uri(dataverseUrl);// Dataverse environment URLclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));client.DefaultRequestHeaders.Add("OData-MaxVersion","4.0");client.DefaultRequestHeaders.Add("OData-Version","4.0");client.DefaultRequestHeaders.Add("Prefer","odata.include-annotations=\"*\"");// Request annotations})// Add authentication handler for injecting tokens.AddHttpMessageHandler(_ =>new DataverseManagedIdentityAuthHandler())// Optional: Add resilience features (retry policies, etc.).AddStandardResilienceHandler();// Register custom Dataverse client wrapperbuilder.Services.AddScoped<DataverseHttpClient>();var app =builder.Build();// Enable authentication and authorization middlewareapp.UseAuthentication();app.UseAuthorization();
appsettings.json
{"AzureAd":{"Audience":"api://4ac715bd-612a-4830-89e0-b3ad0efe5a6e",// Backend API Application ID URI"Authority":"https://login.microsoftonline.com/<your-tenant-id>",// Tenant Authority URL"Instance":"https://login.microsoftonline.com/",// Common Microsoft login endpoint"TenantId":"<your-tenant-id>",// Azure AD Tenant ID"Domain":"your-tenant-domain.com",// Tenant domain name"ClientId":"4ac715bd-612a-4830-89e0-b3ad0efe5a6e"// Backend API App Registration Client ID},"DownstreamDataverseApi":{"BaseUrl":"https://{your-environment}.api.crm.dynamics.com/",// Dataverse API base URL"Scopes":"https://{your-environment}.api.crm.dynamics.com/.default"// Dataverse API default scope}}
DataverseManagedIdentityAuthHandler.cs
// Custom HTTP message handler for authenticating Dataverse API requestspublicsealedclassDataverseManagedIdentityAuthHandler( ITokenAcquisition tokenAcquisition, IHttpContextAccessor httpContextAccessor, IConfiguration configuration): DelegatingHandler{// This method is called for every outgoing HTTP requestprotectedoverrideasync Task<HttpResponseMessage>SendAsync(HttpRequestMessage request, CancellationToken cancellationToken){// Get the current authenticated user from HTTP contextvar user =httpContextAccessor.HttpContext?.User;// Reject if no authenticated user is foundif(user?.Identityisnot{IsAuthenticated:true}){thrownew UnauthorizedAccessException("User is not authenticated");}// Load required scopes from configurationvar scopesToAccessDataverseApi =configuration["DownstreamDataverseApi:Scopes"]??thrownew Exception("Configuration 'DownstreamDataverseApi:Scopes' is missing.");// Acquire access token on behalf of the authenticated uservar token =awaittokenAcquisition.GetAccessTokenForUserAsync([scopesToAccessDataverseApi], user:user);// Attach the token to the outgoing HTTP requestrequest.Headers.Authorization=new AuthenticationHeaderValue("Bearer",token);// Proceed with sending the HTTP requestreturnawaitbase.SendAsync(request,cancellationToken);}}
When building serverless solutions in Azure, securely connecting to services like Microsoft Dataverse (formerly Common Data Service) is a common task. Azure Functions combined with Managed Identity offers a clean and secure way to authenticate without managing secrets. In this post, I’ll show you how to access the Dataverse OData API using a managed identity, and share a few gotchas—like needing the Application (client) ID, not the Object ID, when registering the app in Power Platform.
Why Managed Identity?
Managed Identity lets your Azure Function authenticate against Azure AD and access resources like Dataverse without needing any client secrets or certificates.
Step 1: Configure Azure Function for Managed Identity
In your Azure Function:
Go to the Identity blade in the portal.
Enable the System-assigned managed identity.
Copy the Object ID (you’ll need this in the next step).
Step 2: Register the Managed Identity as an App User in Power Platform
Here’s the tricky part: Power Platform (Dataverse) requires the Application (client) ID of an Entra ID Enterprise Application, not the Object ID.
Here’s how to get it:
Go to Microsoft Entra ID → Enterprise Applications.
Search for the Object ID from your Azure Function’s Identity blade.
Open that Enterprise App and copy the Application ID (this is what Dataverse wants).
In Power Platform Admin Center, create a new App User, paste this Application ID, and assign the correct security role.
Step 3: Implement a Custom Auth Handler in Your Azure Function
Now, let’s get to code.
Create a delegating handler that uses DefaultAzureCredential to fetch a token for Dataverse:
Following up on my previous post, I’ve put together a new set of Semgrep rules focused specifically on string-related performance issues in C#.
These are the kinds of things that rarely show up in code reviews, and not everything is covered by Resharper or IDEs. The issues adds up tho, especially if they are in a hot path. Things related to strings can also cause a lot of allocations, leading to plenty of work for the garbage collector. These rules are designed to be lightweight, easy to integrate into your workflow, and catch the kind of subtle inefficiencies that can quietly degrade performance over time. Some of the have auto fixes, meaning you can apply the rules to your code base, and it will sort it out. This is still work in progress, but let’s go through the rules.
1. String Comparison
Calling ToLower() or ToUpper() just to compare strings is wasteful, it allocates a new string, converts every character, and then compares. Use string.Equals(str1, str2, StringComparison.OrdinalIgnoreCase) to compare the strings without creating any temporary strings. Resharper does not flag this.
publicboolToLower_Different(){// Here ToLower allocates a new string.returnTestString1.ToLower().Equals(TestString2);}publicboolStringEquals_OrdinalIgnoreCase_SameIgnoreCase(){// Here we compare without allocating new stringsreturnstring.Equals(TestString1,TestString2,StringComparison.OrdinalIgnoreCase);}
csharp-inefficient-string-comparison.yaml
rules:-id:csharp-inefficient-string-comparisonpatterns:-pattern-either:-pattern:$STR.ToLower().Equals($OTHER)-pattern:$STR.ToLowerInvariant().Equals($OTHER)-pattern:$STR.ToUpper().Equals($OTHER)-pattern:$STR.ToUpperInvariant().Equals($OTHER)-pattern-not:String.Equals($STR, $OTHER, StringComparison.OrdinalIgnoreCase)message:> Inefficient string comparison. Use String.Equals(s1, s2, StringComparison.OrdinalIgnoreCase) instead of ToLower()/ToUpper().Equals() for better performance and clarity.fix:String.Equals($STR, $OTHER, StringComparison.OrdinalIgnoreCase)languages:[csharp]severity:WARNINGmetadata:category:performancesubcategory:-easyfix-stringsreferences:-"https://blog.smistad.me/semgrep-rules-for-c-performance/"
2. Avoid string.Format for cases where interpolation is enough
string.Format adds overhead and is harder to read. Interpolation ($"...") is faster and alloc-free in simple cases. For more complex formatting you should continue to use string.Format, but where it is used for basic string concatenation you should switch to string interpolation. Resdharper suggests fixing this if you use string.Format, but not in cases where you use string.Concat.
publicstringFormat(){// Resharper suggests switching to interpolationreturnstring.Format("{0} {1} {2}",Left,Right,Middle);}publicstringInterpolation(){return$"{Left}{Right}{Middle}";}publicstringConcat(){// No suggestion to fix this from Resharperreturnstring.Concat(Left,"",Right,"",Middle);}
Method
Mean
Ration
Allocated
Interpolation
0.4472 ns
1.00
–
Concat
19.2632 ns
43.16
56 B
Format
44.2375 ns
99.12
56 B
Interpolation is much faster than the alternatives. The benchmark here is the code shown above, so I guess the interpolation just gets optimized away in the end. This will also apply to your actual code in situations where you use it for simple string concatenations. It also causes less allocations than the alternatives.
The reason is that we have to avoid parsing the format parameters, in the cases where you just refer to a variable. So this improvement only really works in simple use cases.
csharp-string-format-to-interpolation.yaml
The regex for detecting more complex format parameters is not quite working. So this rule currently picks up some false-positives.
rules:-id:csharp-string-format-to-interpolationlanguages:[csharp]severity:WARNINGmessage:"Use string interpolation ($\"...\") instead of string.Format for simple cases"metadata:description:"Detects simple string.Format calls that could be replaced with string interpolation"category:"performance"references:-"https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/interpolated"-"https://blog.smistad.me/semgrep-rules-for-c-performance/"technology:-csharpsubcategory:-easyfix-stringspattern-either:-pattern:string.Format("$FMT", $A1)-pattern:string.Format("$FMT", $A1, $A2)-pattern:string.Format("$FMT", $A1, $A2, $A3)-pattern:string.Format("$FMT", $A1, $A2, $A3, $A4)pattern-not-regex:\{\d+:[^}]+\}
3. Use AsSpan() Instead of Substring()
In some cases we can avoid allocating a new string with string.Substring() and instead use .AsSpan().
Some typical cases we can avoid is inputs to int/double/Guid.Parse() methods, or comparing a substring to a string literal.
// allocates new stringint.Parse(tName.Substring("VariantArray".Length),;if(s.Substring(i)=="INF")// Using AsSpan()int.Parse(tName.AsSpan("VariantArray".Length));if(s.AsSpan(i).SequenceEqual("INF"))
Here Resharp will suggest to use a range index instead of substring, but this is actually slower than using the substring method. You do not avoid any allocations either, as you do if you use .AsSpan().
[Benchmark(Baseline =true)]publicstringSubstring(){return"this is my wonderful string".Substring("this".Length);}[Benchmark]public ReadOnlySpan<char>AsSpan(){return"this is my wonderful string".AsSpan("this".Length);}[Benchmark]publicstringRangeIndex(){// Resharper will sugest changing your code to thisreturn"this is my wonderful string"["this".Length..];}
Method
Mean
Ratio
Allocated
AsSpan
0.2085 ns
0.05
–
Substring
4.5797 ns
1.00
72 B
RangeIndex
6.5919 ns
1.44
72 B
csharp-substring.yaml
rules:-id:csharp-avoid-substring-for-span-accepting-methodslanguages:[csharp]message:Use AsSpan instead of Substring to avoid string allocations when passing to methods accepting ReadOnlySpan<char>.severity:WARNINGmetadata:category:performancesubcategory:-easyfix-stringslikelihood:LOWimpact:LOWpatterns:-pattern:$METHOD($STR.Substring($IDX))-metavariable-regex:metavariable:$METHODregex:> (int|float|double|decimal|uint|long|bool|Guid|DateTime|DateTimeOffset)\.(Parse(Exact)?|TryParse(Exact)?)fix:$METHOD($STR.AsSpan($IDX))-id:csharp-avoid-substring-for-suffixpattern:$STR.Substring($IDX)message:Use AsSpan instead of Substring to avoid string allocations.languages:[csharp]severity:INFOmetadata:category:performancesubcategory:-easyfix-stringslikelihood:LOWimpact:LOWfix:$STR.AsSpan($IDX)-id:csharp-avoid-substring-equalspattern:$STR.Substring($IDX) == "$SUFFIX"message:Use AsSpan(...).SequenceEqual("...") instead of Substring == "..." for performance.languages:[csharp]severity:WARNINGmetadata:category:performancesubcategory:-easyfix-stringslikelihood:LOWimpact:LOWfix:$STR.AsSpan($IDX).SequenceEqual("$SUFFIX")
4. Optimize UTF-8 Transcoding
Not avoiding any allocations with this one, but you save some CPU cycles. This is also caught by Resharper
returnEncoding.UTF8.GetBytes("ThIs A StRiNG");// Can be shortend to this:return"ThIs A StRiNG"u8.ToArray();
csharp-avoid-transcoding.yaml
rules:-id:csharp-avoid-transcodingpatterns:-pattern-either:-pattern:Encoding.UTF8.GetBytes("$STR")message:Use u8 to avoid csharp-avoid-transcodingfix:\"$STR\"u8.ToArray()languages:[csharp]severity:WARNINGmetadata:category:performancesubcategory:-easyfix-stringslikelihood:LOWimpact:LOW
Performance isn’t just about making users happy, though. It makes our lives as developers way better too. Think about your typical day, you’re constantly running your code, debugging, testing, and then doing it all over again. When your code runs faster, you spend less time waiting and more time actually coding. Nobody enjoys staring at a spinning wheel while your tests run or the debugger loads up. Those small delays mess with your flow and make development less fun.
I was thinking of using semgrep to catch a lot of small easy to fix performance improvements. So I want to just share the rules I make, so maybe somebody else can use them to.
These rules will be covering the small cases, but sometimes the performance issues can be a death of a thousand cuts. Garbage Collection can be a real killer for performance, so a lot of the rules will try to cover things where there is some alternative that requires less or no allocations.
Stop Converting Strings Just to Compare Them
Strings are everywhere in our code, and the way we compare them can make a surprising difference in performance. Here’s our first rule that catches a really common mistake.
We’ve all done this at some point:
// The slow way
if (someString.ToLower().Equals(otherString))
{
// Do something
}
Or maybe this version:
// Also slow
if (someString.ToUpper().Equals(otherString))
{
// Do something
}
What’s wrong with this? A few things:
It creates a whole new string just for the comparison
It wastes memory for this temporary string
It has to convert every character before it even starts comparing
The Better Way
There’s a much faster way to do the same thing in C#:
if (String.Equals(someString, otherString, StringComparison.OrdinalIgnoreCase))
{
// Do something
}
This skips creating new strings completely and just does the comparison directly.
The Semgrep Rule
Here’s the rule I made to catch this in your code:
“But it’s just nanoseconds,” you might say. True, but:
In a busy app, you might do these comparisons millions of times
Every little memory allocation makes the garbage collector work harder
These tiny slowdowns add up across your whole codebase
This is just the first of several performance-boosting rules I’m working on. If you add these to your workflow, you’ll catch these speed bumps before they slow down your code.
If you’ve ever found yourself waiting for your local .NET application to authenticate with Azure services, you’re not alone. That delay is often caused by DefaultAzureCredential trying authentication methods that simply don’t exist on your development machine.
Why Your Local Development Is Slow
When you use DefaultAzureCredential without any customization, it tries a whole series of authentication methods one after another until something works. For local development, this is painfully inefficient.
Think about it: your development machine doesn’t have managed identities or workload identities – those only exist in Azure environments. Yet DefaultAzureCredential will still stubbornly try to use them, waiting for timeouts before giving up and moving to the next method. Those wasted seconds add up, especially when you’re frequently restarting your application during development.
As shown in Microsoft’s documentation on credential chains, DefaultAzureCredential tries these credential types in order:
Environment
Workload Identity
Managed Identity
Visual Studio
Azure CLI
Azure PowerShell
Azure Developer CLI
Interactive browser (when explicitly enabled)
The first three are the biggest culprits for slowing down local development.
The Simple Fix: Tell It What to Skip
The good news is that fixing this is easy. You don’t need to abandon DefaultAzureCredential entirely, you just need to tell it which methods to skip when you’re developing locally:
var isDevelopment =builder.Environment.IsDevelopment();TokenCredential credential =new DefaultAzureCredential(new DefaultAzureCredentialOptions{ExcludeEnvironmentCredential=isDevelopment,ExcludeManagedIdentityCredential=isDevelopment,ExcludeWorkloadIdentityCredential=isDevelopment,});
This skips the 3 first parts of the flow, directly trying a development credential.
Integration with HttpClient
Here’s how you can integrate this optimized credential with an HttpClient used to call an API:
Only skip these credentials in development. Your production code should still use the full chain.
The beauty of this approach is that it’s environment-aware – your app will automatically use the right authentication strategy based on where it’s running.
If your development process involves environment variables for authentication, you might want to keep EnvironmentCredential enabled.
Wrap Up
I have been experimenting with keeping app start times fast. Especially when you need to access a service like Azure Key Vault, or Azure App Config during application startup, then you want the authentication to be fast.