<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Vikhyat Khosla on Medium]]></title>
        <description><![CDATA[Stories by Vikhyat Khosla on Medium]]></description>
        <link>https://medium.com/@vikhyatkhosla?source=rss-c91393b5769c------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*QYMhKT5mR8vyqeIv.</url>
            <title>Stories by Vikhyat Khosla on Medium</title>
            <link>https://medium.com/@vikhyatkhosla?source=rss-c91393b5769c------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 07:43:34 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@vikhyatkhosla/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Managing Per-Invocation Context in Azure Functions with AsyncLocal<T>]]></title>
            <link>https://medium.com/@vikhyatkhosla/managing-per-invocation-context-in-azure-functions-with-asynclocal-t-5e55e93f9a0f?source=rss-c91393b5769c------2</link>
            <guid isPermaLink="false">https://medium.com/p/5e55e93f9a0f</guid>
            <category><![CDATA[depedency-injection]]></category>
            <category><![CDATA[azure]]></category>
            <category><![CDATA[azure-functions]]></category>
            <category><![CDATA[azure-function-trigger]]></category>
            <category><![CDATA[c-sharp-programming]]></category>
            <dc:creator><![CDATA[Vikhyat Khosla]]></dc:creator>
            <pubDate>Mon, 07 Jul 2025 14:36:18 GMT</pubDate>
            <atom:updated>2025-07-07T14:36:18.086Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rD-1rdvsWaRA5DOHy3n7yA.png" /></figure><p>In serverless applications like Azure Functions, managing execution-specific context (like Correlation ID, or a custom CV header) becomes tricky — especially when these values need to be accessed deep inside services that are registered as singletons.</p><p>This post walks through a clean, reusable solution for maintaining per-invocation state using AsyncLocal&lt;T&gt;, making it accessible from any service without breaking dependency injection (DI) patterns.</p><h3>💡 The Problem</h3><p>Azure Functions typically inject services using the built-in dependency injection container. But in scenarios like:</p><ul><li>Logging telemetry (CV, CorrelationId, TenantId, etc.)</li><li>Accessing user/request context from deep within services</li><li>Sharing state across the call chain (without polluting every method signature)</li></ul><p>…you run into problems when your services are singleton, but the context is <strong>per function invocation</strong>.</p><p>You can’t make the context scoped because Azure Functions don&#39;t have native support for request-scoped lifetimes.</p><h3>✅ The Solution: Use AsyncLocal&lt;T&gt; for Scoped-Per-Invocation Behavior</h3><p>.NET provides AsyncLocal&lt;T&gt;, which flows naturally with asynchronous calls. It works like a thread-local variable, but respects async/await flow — making it ideal for per-invocation context in Azure Functions.</p><h3>🧱 Step-by-Step Implementation</h3><h3>1️⃣ Define the Context Interface</h3><pre>public interface IExecutionContext<br>{<br>    string CV { get; set; }<br>    string CorrelationId { get; set; }<br>}</pre><h3>2️⃣ Create the Per-Invocation Context Model</h3><pre>public class DefaultExecutionContext : IExecutionContext<br>{<br>    public string CV { get; set; }<br>    public string CorrelationId { get; set; }<br>}</pre><h3>3️⃣ Create a Backing Store Using AsyncLocal&lt;T&gt;</h3><pre>public class AsyncLocalContextStore : IExecutionContextStore<br>{<br>    private static readonly AsyncLocal&lt;IExecutionContext&gt; store = new();</pre><pre>    public static void Set(IExecutionContext context)<br>    {<br>        if (store.Value != null)<br>        {<br>            throw new InvalidOperationException(&quot;Execution context is already set. Clear it before setting a new one.&quot;);<br>        }</pre><pre>        store.Value = context;<br>    }</pre><pre>    public static void Clear() =&gt; store.Value = null;</pre><pre>    private IExecutionContext Current =&gt; store.Value ?? throw new InvalidOperationException(&quot;Execution context not set.&quot;);</pre><pre>    public string CV { get =&gt; Current.CV; set =&gt; Current.CV = value; }<br>    public string CorrelationId { get =&gt; Current.CorrelationId; set =&gt; Current.CorrelationId = value; }<br>}</pre><h3>4️⃣ Create the DI-Injectable Context Wrapper</h3><pre>public class ExecutionContext : IExecutionContext<br>{<br>    private readonly IExecutionContextStore store;</pre><pre>    public ExecutionContext(IExecutionContextStore store)<br>    {<br>        this.store = store;<br>    }</pre><pre>    public string CV { get =&gt; store.CV; set =&gt; store.CV = value; }<br>    public string CorrelationId { get =&gt; store.CorrelationId; set =&gt;<br>}</pre><h3>5️⃣ Register the Services in Your DI Container</h3><pre>builder.Services.AddSingleton&lt;IExecutionContext, ExecutionContext&gt;();<br>builder.Services.AddSingleton&lt;IExecutionContextStore, AsyncLocalContextStore&gt;();</pre><h3>6️⃣ Set the Context at the Start of Each Function Invocation</h3><pre>public async Task RunAsync(<br>    [ServiceBusTrigger(&quot;queue-name&quot;, Connection = &quot;MyServiceBus&quot;)] ServiceBusReceivedMessage message)<br>{<br>    try<br>    {<br>        AsyncLocalContextStore.Set(new DefaultExecutionContext<br>        {<br>            CV = &quot;...&quot;,<br>            CorrelationId = &quot;...&quot;,<br>        });</pre><pre>        // do stuff;<br>    }<br>    finally<br>    {<br>        AsyncLocalContextStore.Clear();<br>    }<br>}</pre><h3>7️⃣ Access the Context Anywhere</h3><pre>public class TelemetryLogger<br>{<br>    private readonly IExecutionContext context;</pre><pre>    public TelemetryLogger(IExecutionContext context)<br>    {<br>        this.context = context;<br>    }</pre><pre>    public void Log()<br>    {<br>        Console.WriteLine($&quot;CV: {context.CV}, CorrelationId: {context.CorrelationId}&quot;);<br>    }<br>}</pre><p>Works seamlessly even in singleton services.</p><h3>🔐 Optional: Safety Check in .Set(...)</h3><p>The defensive check ensures you don’t accidentally overwrite context mid-execution:</p><pre>public static void Set(IExecutionContext context)<br>{<br>    if (store.Value != null)<br>    {<br>        throw new InvalidOperationException(&quot;Execution context is already set. Clear it before setting a new one.&quot;);<br>    }<br>    store.Value = context;<br>}</pre><h3>🚀 Conclusion</h3><p>Managing execution context in Azure Functions doesn’t need to be messy or inconsistent. By using AsyncLocal&lt;T&gt; as a backing store, you can safely track execution-specific data like CVs and correlation IDs and make it accessible across your entire service layer — even in singleton components.</p><p>This pattern is lightweight, testable, and production-safe. Give it a try the next time you need to thread context through your Azure Function logic!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5e55e93f9a0f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Migrating from WindowsAzure.ServiceBus to Microsoft.Azure.ServiceBus]]></title>
            <link>https://medium.com/@vikhyatkhosla/migrating-from-windowsazure-servicebus-to-microsoft-azure-servicebus-9ac49eb21413?source=rss-c91393b5769c------2</link>
            <guid isPermaLink="false">https://medium.com/p/9ac49eb21413</guid>
            <category><![CDATA[microsoft]]></category>
            <category><![CDATA[net-core]]></category>
            <category><![CDATA[azure-service-bus]]></category>
            <dc:creator><![CDATA[Vikhyat Khosla]]></dc:creator>
            <pubDate>Sat, 11 Jan 2020 14:05:48 GMT</pubDate>
            <atom:updated>2020-04-17T04:16:30.724Z</atom:updated>
            <content:encoded><![CDATA[<h4>Why to migrate ?</h4><p>A new SDK for ServiceBus was released in August 2017 and has resulted in many people to move from <a href="https://www.nuget.org/packages/WindowsAzure.ServiceBus/">WindowsAzure.ServiceBus</a> to <a href="https://www.nuget.org/packages/Microsoft.Azure.ServiceBus/">Microsoft.Azure.ServiceBus</a>. With the new SDK being .NET Standard 2.0 compatible making it usable cross platform in <strong>.NET core</strong> applications, <a href="https://docs.microsoft.com/en-us/dotnet/standard/choosing-core-framework-server">here</a> is why you’d want to use .NET core. The new SDK is <strong>open source, </strong><a href="https://github.com/Azure/azure-service-bus-dotnet">https://github.com/Azure/azure-service-bus-dotnet</a>, and in general provides cleaner API. I will list the set of changes I faced while migrating to the new SDK.</p><h4>Brokered Message changed to Message</h4><p>The object used to represent a message has been changed from <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.servicebus.messaging.brokeredmessage?view=azure-dotnet">BrokeredMessage</a> to <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.servicebus.message?view=azure-dotnet">Message</a>. There are 2 sets of propertiey collections on message object SystemProperties and UserProperties. Fields like DeadLetterSource, DeliveryCount, EnqueuedSequenceNumber, LockToken, SequenceNumber<em>, </em>are now part of <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.devices.messagesystempropertynames?view=azure-dotnet">SystemProperties</a>and not accessed by message object. Convenience methods like CompleteAsync, AbandonAsync, and DeadLetterAsync were called on BrokeredMessage directly are not available on Message object and functionality can be achieved with the help of ClientEntity.</p><h4>Client Entity and Message Receiver</h4><p>The above mentioned convenience methods are now exposed via <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.servicebus.iqueueclient?view=azure-dotnet">QueueClient</a>, <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.servicebus.itopicclient?view=azure-dotnet">TopicClient</a>, <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.servicebus.isubscriptionclient?view=azure-dotnet">SubscriptionClient</a>. RenewLockAsyncis not exposed via QueueClient anymore , we don’t have a way of renewing the lock for longer than the MaxAutoRenewDuration duration specified in MessageHandlerOptions Another change in QueueClient is, it does not expose Receive and ReceiveBatchapis. Functionality of recieving a batch of messages from a queue or a dead letter queue can be achieved by using <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.servicebus.core.messagereceiver?view=azure-dotnet">MessageReceiver</a> Which can be also used to call RenewLockAsync deprecated from QueueClient.</p><h4><strong>Namespace Manager changed to Management Client</strong></h4><p>The old <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.servicebus.namespacemanager?view=azure-dotnet">NamespaceManager</a>has been changed to <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.servicebus.management.managementclient?view=azure-dotnet">ManagementClient</a>. Most API’s are similar. One big change here was CreateQueueand DeleteQueuehave changed to async implementations of <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.servicebus.namespacemanager.createqueueasync?view=azure-dotnet#Microsoft_ServiceBus_NamespaceManager_CreateQueueAsync_System_String_">CreateQueueAsync</a>and <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.servicebus.namespacemanager.deletequeueasync?view=azure-dotnet#Microsoft_ServiceBus_NamespaceManager_DeleteQueueAsync_System_String_">DeleteQueueAsync</a>. DeleteQueueAsync now throws MessagingEntityNotFoundException if queue specified does not exist.</p><h4>Default transport type</h4><p>The default transport type is AMQP. This was blocked by firewall and I was getting connection exceptions. So I switched to the other option of AMQP over WebSockets which uses port 443. To configure this option we need to append ;TransportType=AmqpWebSockets to the end of the connection string.</p><h4><strong>Exceptions</strong></h4><p>While creating a queue if there is a synchronization issue we would get a <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.servicebus.messaging.messagingexception?view=azure-dotnet">MessagingException </a>with conflict code 409. In the new SDK this exception propogated by <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.servicebus.servicebusexception?view=azure-dotnet">ServiceBusException </a>with conflict sub code 40901. Inactivity is now indicated via an exception ServiceBusCommunicationException, <em>‘The connection was inactive for more than the allowed 60000 milliseconds and is closed by container’. </em>If there is no action, the connection is automatically closed. However, the next connection will reconnect automatically. This log just details the behavior.<br>In this SDK exceptions now derive from <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.azure.servicebus.servicebusexception?view=azure-dotnet">ServiceBusException</a> class, which has a property isTransient. If an operation throws a <em>transient </em>exception you could retry. If you are using using PUMP and exception is thrown before ProcessMessagesAsync is called, it comes down to handling of that exception in the exception handler. The pump already takes care of retries for you, so you don’t need to worry about that. Its up to you on how you want to log the exceptions. These transient exceptions that can definitely happen and is expected. But it is advised to log them since having a lot of transient exceptions continuously might have to be looked into. If the exceptions are scoped to a small period, then probably it would be related to some service restarts (load-balancing / service upgrades / OS updates etc).</p><p>Regarding non-transient exceptions, it is very much a case-by-case basis. You can find more details <a href="https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-messaging-exceptions">here</a></p><h4>Useful Links</h4><p>Here are some examples of functionalities which can be helpful when migrating.</p><ol><li><a href="https://docs.microsoft.com/en-us/dotnet/api/overview/azure/servicebus/client?view=azure-dotnet">Documentation</a></li><li><a href="https://github.com/Azure/azure-service-bus/tree/master/samples/DotNet/GettingStarted/Microsoft.Azure.ServiceBus/BasicSendReceiveUsingQueueClient">Receive Message Using QueueClient</a> (Register a listener when message is sent to queue)</li><li><a href="https://github.com/Azure/azure-service-bus/tree/master/samples/DotNet/GettingStarted/Microsoft.Azure.ServiceBus/SendReceiveUsingMessageSenderReceiver">Receive Single Message from queue</a> (Using Message Receiver)</li></ol><p>Use the above example to recieve a batch of messages or recieve messages from Dead Letter Queue</p><pre>var deadletterReceiver = new MessageReceiver(namespaceConnectionString, EntityNameHelper.FormatDeadLetterPath(queueName), ReceiveMode.PeekLock);</pre><pre>var messagesBatch = deadletterReciever.ReceiveAsync(int maxMessageCount, TimeSpan operationTimeout);</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9ac49eb21413" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>