Teams Bot 如何使用新的 System.Text.Json 庫

我最近把 LuckyDraw的代碼升級到了 .net core 3.1,當然我也很想使用最新的微軟json庫,System.Text.Json這個庫的性能比之前Newtonsoft.Json速度更快,而且就我本人愛好來說,更加喜歡System.Text.Json的命名,之前一直覺得 JObject, JArray, JToken 這些名字不夠符合 c# 的 naming guideline。

微軟 這篇文章 很好的告訴大家如何將 Newtonsoft.Json 遷移到 System.Text.Json,但是如果你是用Bot SDK來開發teams bot,事情比你想象的複雜很多。

我們先來看一下bot sdk的sample code是怎麼做的,打開EchoBot的代碼,找到Startup.cs文件,你可以看到這麼一行:

public class Startup
{
    ...
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers().AddNewtonsoftJson();
        ...
    }
}

現在大家明白了把,bot samples雖然都已經升級到了.net core 3.1,但是,它還是把mvc設置成使用Newtonsoft.Json。

那問題到底在哪裏,爲什麼一定要使用Newtonsoft? 我們來看一下bot sdk源碼,看一下bot framework裏最核心的Activity的代碼。

public partial class Activity
{
    ...
    [JsonProperty(PropertyName = "type")]
    public string Type { get; set; }

    [JsonProperty(PropertyName = "id")]
    public string Id { get; set; }

    [JsonProperty(PropertyName = "timestamp")]
    public System.DateTimeOffset? Timestamp { get; set; }

    [JsonProperty(PropertyName = "localTimestamp")]
    public System.DateTimeOffset? LocalTimestamp { get; set; }

    [JsonProperty(PropertyName = "localTimezone")]
    public string LocalTimezone { get; set; }

    [JsonProperty(PropertyName = "serviceUrl")]
    public string ServiceUrl { get; set; }

    [JsonProperty(PropertyName = "channelId")]
    public string ChannelId { get; set; }

    [JsonProperty(PropertyName = "from")]
    public ChannelAccount From { get; set; }

    ...
}

可以看到每個property都有一個JsonProperty的attribute,這個attribute是在Newtonsoft.Json裏定義的,當序列化的時候,會使用指定的name作爲json裏的屬性名字。當然在新的System.Text.Json裏也有一個對應的attribute,叫JsonPropertyName,所以問題就來了,如果我們使用新的System.Text.Json來對一個activity對象進行serialize和deserialize,由於屬性 Type 上只有JsonProperty並沒有新的JsonPropertyName,serialize後,json就用了首字母大寫的{"Type":"blablabla"},如果是使用老的Newtonsoft.Json,那就是{"type":"blablabla"}

當然不單單是JsonProperty這麼一個問題,還有其他json序列化和反序列化的一些attribute也有類似問題,比如下面這兩個:

namespace Microsoft.Bot.Builder.Dialogs.Adaptive.Templates
{
    [JsonConverter(typeof(ActivityTemplateConverter))]
    public class ActivityTemplate : ITemplate<Activity>
    {
        ...
    }
}
namespace Microsoft.Bot.Builder.Dialogs.Adaptive.Actions
{
    public class BeginDialog : BaseInvokeDialog
    {
        [JsonConstructor]
        public BeginDialog(...)
            : base(dialogIdToCall, options)
        {
            ...
        }
        ...
    }
}

正式由於上面這些問題,所以如果要繼續擁護在mvc裏使用新的System.Text.Json,同時又要使用bot sdk來做開發,那就必須在和bot sdk裏一些對象打交道的時候使用老的Newtonsoft.Json。

比如以前可以這麼寫:

public class MessagesController : ControllerBase
{
    [HttpPost("messages")]
    public async Task<IActionResult> GetMessage([FromBody]Activity activity)
    {
        ...
    }
}

現在就要:

public class MessagesController : ControllerBase
{
    [HttpPost("messages")]
    public async Task<IActionResult> GetMessage()
    {
        Activity activity;
        using (var streamReader = new StreamReader(Request.Body))
        {
            var bodyString = await streamReader.ReadToEndAsync();
            activity = JsonConvert.DeserializeObject<Activity>(bodyString);
        }
        ...
    }
}

因爲你不能再依賴於mvc來幫你deserialize出Activity對象,因爲我們的mvc是使用新的System.Text.Json。

當我們要返回一個activity對象的時候,以前可以這樣:

[HttpPost("messages")]
public async Task<IActionResult> GetMessage([FromBody]Activity activity)
{
    Activity repliedActivity;
    ...
    return Ok(repliedActivity);
}

現在就要:

[HttpPost("messages")]
public async Task<IActionResult> GetMessage()
{
    Activity repliedActivity;
    ...
    return OkFromNewtonsoftJson(repliedActivity);
}

private IActionResult OkFromNewtonsoftJson(object value)
{
    if (value == null)
    {
        return NoContent();
    }

    var json = JsonConvert.SerializeObject(value);
    return Content(json, "application/json", Encoding.UTF8);
}

因爲我們不能再靠mvc來幫你serialize一個Activity對象,必須手動使用Newtonsoft.Json來序列化。

希望bot sdk和其他sdk,能夠儘快的兼容新的Json庫,這樣才能使廣大開發者擁抱 System.Text.Json。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章