【问题标题】:Localize properties during serialization with JsonConvert.SerializeObject使用 JsonConvert.SerializeObject 在序列化期间本地化属性
【发布时间】:2021-11-15 14:05:25
【问题描述】:

据我了解,AsyncLocal 提供了跨任务保持其值的能力,因此我希望以下示例代码在对象被序列化时保持 Context.Culture 值,但事实并非如此。

在我看来,鉴于PropertyDisplay.Text 有一个getting,它的执行方式“不同”,因此Context.Culture 的值会丢失。

我的假设正确吗? 如果没有,有没有办法做到这一点?

预期输出

{
  "text": "prefix-zh-CHS"
}

实际输出

{
  "text": "prefix"
}

示例代码:

private static readonly Dictionary<string, string> LanguageMapping = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
    { "zh", "zh-CHS" }
};

static async Task Main(string[] args)
{
    PropertyDisplay propertyDisplay = null;
    string json = null;
    ExecutionContext.SuppressFlow();

    await Task.Run(() =>
    {
        Context.Culture = GetCultureInfo("zh");
    });

    await Task.Run(() =>
    {
        propertyDisplay = new PropertyDisplay()
        {
            Text = "prefix"
        };
    });

    await Task.Run(() =>
    {
        json = JsonConvert.SerializeObject(propertyDisplay, Formatting.Indented, new JsonSerializerSettings()
        {
            
        });
    });

    Console.WriteLine(json);
    Console.ReadKey();
}

public static CultureInfo GetCultureInfo(string language)
{
    CultureInfo retVal;

    if (language != null && LanguageMapping.ContainsKey(language))
    {
        language = LanguageMapping[language];
    }

    try
    {
        retVal = new CultureInfo(language);
    }
    catch
    {
        retVal = new CultureInfo("en-us");
    }

    return retVal;
}
public static class Context
{
    private static AsyncLocal<CultureInfo> culture = new AsyncLocal<CultureInfo>();
    
    public static CultureInfo Culture
    {
        get
        {
            return culture?.Value;
        }
    
        set
        {
            culture.Value = value;
        }
    }
}
public class PropertyDisplay
{
    private string displaySelectionText;
    
    [JsonProperty(PropertyName = "text")]
    public string Text
    {
        get
        {
            if (string.IsNullOrWhiteSpace(this.displaySelectionText))
            {
                return null;
            }
            
            var localized = GetLocalizedString(this.displaySelectionText);
            return localized;
        }
    
        set
        {
            this.displaySelectionText = value;
        }
    }
    
    public static string GetLocalizedString(string configText)
    {
        string retVal = configText;
        if (Context.Culture != null)
        {
            retVal = $"{configText}-{Context.Culture}";
        }
    
        return retVal;
    }
}

更新

看起来如果我打电话 ExecutionContext.SuppressFlow() 然后 它保持该值并按预期​​工作。

想知道是否有人可以解释它是如何工作的。

【问题讨论】:

  • AsyncLocal.Value 实际上存储在ExecutionContext (github.com/dotnet/runtime/blob/…) 中。我不确定Task.Run 是否克隆了ExecutionContext
  • 您能否与我们分享更新后的代码在哪里使用SuppressFlow()?请记住,还有一个RestoreFlow 方法,它只能从调用SuppressFlow 方法的同一线程中调用。
  • 但坦率地说,我完全不明白你为什么要使用AsyncLocalAsyncLocal 意味着(或多或少)每个异步上下文都将看到同一变量的自己的本地版本。但是您正试图将其用作它们之间的共享资源。
  • 如果您想将CultureInfo 设置在不同的任务上,而不是您想从中检索它,那么忘记AsyncLocal。将其用作“普通”共享资源。
  • @HoBa 您的真正问题是如何在序列化期间本地化文本属性,而不是如何序列化AsyncLocal 字段。此外,您的 PropertyValue 没有任何 AsyncLocal 字段。如果您使用async/await,当前的文化已经从一个任务流向另一个任务。 AsyncLocal 与您的要求相反

标签: c# multithreading serialization task


【解决方案1】:

您根本不需要AsyncLocalThreadStatic 或任何特别的东西。

您只需在适当的时候捕获Context 并将其传递给PropertyDisplay

这是简化的Context 类(不需要花哨的东西):

public class Context
{
    public CultureInfo Culture { get; set; }
}

您应该每次都创建一个新实例,而不是使其成为 static

static async Task Main(string[] args)
{
    var context = new Context();
    await Task.Run(() => context.Culture = GetCultureInfo("zh"));

    PropertyDisplay propertyDisplay = null;
    await Task.Run(() => propertyDisplay = new PropertyDisplay(context) { Text = "prefix" });

    string json = null;
    await Task.Run(() => json = JsonConvert.SerializeObject(propertyDisplay, Formatting.Indented));

    Console.WriteLine(json);
    Console.ReadKey();
}

您还需要对PropertyDisplay 类进行一些更改以接收Context 实例:

public class PropertyDisplay
{
    private string displaySelectionText;
    private readonly Context context;
    public PropertyDisplay(Context context)
    {
        this.context = context;
    }

    ...

    public string GetLocalizedString(string configText)
    {
        if (context.Culture == null) return configText;
        return $"{configText}-{context.Culture}";
    }
}

或者您可以进一步将GetLocalizedString 简化为单行:

public string GetLocalizedString(string configText)
    => configText + context.Culture != null ? $"-{context.Culture}" : "";

就是这样。输出将如预期:

{
  "text": "prefix-zh-CHS"
}

【讨论】:

    猜你喜欢
    • 2015-08-26
    • 2014-06-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-17
    • 1970-01-01
    • 1970-01-01
    • 2011-02-10
    相关资源
    最近更新 更多