Gradinariu Cezar
Gradinariu Cezar

Reputation: 573

.NET Core equivalent of CallContext.LogicalGet/SetData

I am trying to move into .net core an existing .net application that is using CallContext.LogicalGet/SetData.

When a web request hits the application I save a CorrelationId in the CallContext and whenever I need to log something later down the track I can easily collect it from the CallContext, without the need to transfer it everywhere.

As CallContext is no longer supported in .net core since it is part of System.Messaging.Remoting what options are there?

One version I have seen is that the AsyncLocal could be used (How do the semantics of AsyncLocal differ from the logical call context?) but it looks as if I would have to transmit this variable all over which beats the purpose, it is not as convenient.

Upvotes: 38

Views: 14948

Answers (3)

VanasisB
VanasisB

Reputation: 141

This is what I use for CallContext in ASP.NET Core.

The concept is adapted from Cazzulino's blog post on CallContext.

It provides a way to set and retrieve contextual data that is scoped either to a specific physical thread or flows with the logical call context across asynchronous method calls.

/// <summary>
/// Provides a way to set and retrieve contextual data that can be scoped either 
/// to a specific physical thread or to the logical call context, which flows 
/// across asynchronous calls.
/// 
/// - `SetData` and `GetData` use thread-local storage (`ThreadLocal<T>`) to associate 
///   data with a specific physical thread. The data will not be available if the 
///   execution moves to another thread.
/// 
/// - `LogicalSetData` and `LogicalGetData` use logical context storage (`AsyncLocal<T>`), 
///   which ensures that the data persists across asynchronous calls and is available 
///   even when the execution moves to different threads within the same logical flow.
/// </summary>
public static class CallContext
{
    static ConcurrentDictionary<string, AsyncLocal<object>> asyncLocalStates = new ConcurrentDictionary<string, AsyncLocal<object>>();
    static ConcurrentDictionary<string, ThreadLocal<object>> threadLocalStates = new ConcurrentDictionary<string, ThreadLocal<object>>();

    /// <summary>
    /// Stores a given object and associates it with the specified name in the thread-local context.
    /// This data is tied to the specific physical thread.
    /// </summary>
    /// <param name="name">The name with which to associate the new item in the call context.</param>
    /// <param name="data">The object to store in the call context.</param>
    public static void SetData(string name, object data) =>
        threadLocalStates.GetOrAdd(name, _ => new ThreadLocal<object>()).Value = data;

    /// <summary>
    /// Retrieves an object with the specified name from the thread-local context.
    /// The data is only available on the thread where it was stored.
    /// </summary>
    /// <param name="name">The name of the item in the call context.</param>
    /// <returns>The object in the call context associated with the specified name, or <see langword="null"/> if not found.</returns>
    public static object GetData(string name) =>
        threadLocalStates.TryGetValue(name, out ThreadLocal<object> data) ? data.Value : null;
        
    /// <summary>
    /// Stores a given object and associates it with the specified name in the logical context.
    /// This data flows across asynchronous calls and thread switches within the same logical flow.
    /// </summary>
    /// <param name="name">The name with which to associate the new item in the call context.</param>
    /// <param name="data">The object to store in the call context.</param>
    public static void LogicalSetData(string name, object data) =>
        asyncLocalStates.GetOrAdd(name, _ => new AsyncLocal<object>()).Value = data;

    /// <summary>
    /// Retrieves an object with the specified name from the logical context.
    /// The data is available across asynchronous calls and thread switches within the same logical flow.
    /// </summary>
    /// <param name="name">The name of the item in the call context.</param>
    /// <returns>The object in the call context associated with the specified name, or <see langword="null"/> if not found.</returns>
    public static object LogicalGetData(string name) =>
        asyncLocalStates.TryGetValue(name, out AsyncLocal<object> data) ? data.Value : null;
}

Upvotes: 0

Ogglas
Ogglas

Reputation: 69928

Had this problem when we switched a library from .Net Framework to .Net Standard and had to replace System.Runtime.Remoting.Messaging CallContext.LogicalGetData and CallContext.LogicalSetData.

I followed this guide to replace the methods:

http://www.cazzulino.com/callcontext-netstandard-netcore.html

/// <summary>
/// Provides a way to set contextual data that flows with the call and 
/// async context of a test or invocation.
/// </summary>
public static class CallContext
{
    static ConcurrentDictionary<string, AsyncLocal<object>> state = new ConcurrentDictionary<string, AsyncLocal<object>>();

    /// <summary>
    /// Stores a given object and associates it with the specified name.
    /// </summary>
    /// <param name="name">The name with which to associate the new item in the call context.</param>
    /// <param name="data">The object to store in the call context.</param>
    public static void SetData(string name, object data) =>
    state.GetOrAdd(name, _ => new AsyncLocal<object>()).Value = data;

    /// <summary>
    /// Retrieves an object with the specified name from the <see cref="CallContext"/>.
    /// </summary>
    /// <param name="name">The name of the item in the call context.</param>
    /// <returns>The object in the call context associated with the specified name, or <see langword="null"/> if not found.</returns>
    public static object GetData(string name) =>
        state.TryGetValue(name, out AsyncLocal<object> data) ? data.Value : null;
}

Upvotes: 30

kzu
kzu

Reputation: 1871

You can use a dictionary of AsyncLocal to simulate exactly the API and behavior of the original CallContext. See http://www.cazzulino.com/callcontext-netstandard-netcore.html for a complete implementation example.

Upvotes: 22

Related Questions