轉進 ASP.NET Core 世界,依循過去寫 MVC 經驗,加上參考網路技術文章(當然,還有流著奶與蜜的 Stackoverflow) 大致還算都順利,但不時發現新的眉角。

今天遇到的問題是 Razor Page 傳回 JsonResult 時,中文字元被轉成 UCN (Unicode Character Name,例如:"\u9ED1\u6697\u57F7\u884C\u7DD2") 編碼。Razor Page 程式範例如下,OnGetJsonData() 時使用 new JsonResult() 回傳簡單資料物件:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using PTSWeb.Models.Data;

namespace PTSWeb.Pages
{
    public class IndexModel : PageModel
    {
        private readonly ILogger<IndexModel> _logger;

        public IndexModel(ILogger<IndexModel> logger,)
        {
            _logger = logger;
        }

        public void OnGet()
        {

        }

        public ActionResult OnGetJsonData()
        {
            return new JsonResult(new
            {
                Blog = "darkthread",
                Name = "黑暗執行緒"
            });
        }
    }
}

如下圖所示,在預設情況下,中文字串將會被轉換成 UCN,另外屬性名稱的第一個字元被轉成小寫(我慣用大寫,程式碼產生器處理時可保持前後端一致):

這類問題對對我不算陌生,之前在 cshtml 就遇過:ASP.NET Core View 中文變 & # x4E2D; & # x6587;,大約知道與 Encoder 設定有關,差別在上回是 HtmlEncoder,ASP.NET Core 3.0 起預設改用 System.Text.Json 處理 JSON 序列化與反序列化,找到方法調整 System.Text.Json 的 Encoder 設定即可解決。

查到做法是在 Startup.ConfigureServices() 的 AddRazorPages()、AddControllers()、AddMvc() 後接上 AddJsonOptions() 修改預設設定:(我一併取消屬性名稱強制轉頭文字小寫(Camel Case)的功能)

public void ConfigureServices(IServiceCollection services)
{
    //... 略 ....
    services.AddRazorPages()
        .AddJsonOptions(options =>
        {
            //原本是 JsonNamingPolicy.CamelCase,強制頭文字轉小寫,我偏好維持原樣,設為null
            options.JsonSerializerOptions.PropertyNamingPolicy = null;
            //允許基本拉丁英文及中日韓文字維持原字元
            options.JsonSerializerOptions.Encoder = 
                JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.CjkUnifiedIdeographs);
        });
}

搞定!

不過,很有預感未來很有可能因為 System.Text.Json 的某個限制讓我想換回 Json.NET,所以提前部署,預圥查了切回去使用 Json.NET 的方法:

先用 NuGet 安裝 Microsoft.AspNetCore.Mvc.NewtonsoftJson:

AddRazorPages()、AddControllers()、AddMvc() 後方接 AddNewtonsoftJson():(Json.NET 在 ASP.NET Core 預設也會將屬性名稱轉成頭文字小寫,要保持原樣需呼叫 UseMemberCasing() )

services.AddRazorPages()
    .AddNewtonsoftJson(options =>
    {
        options.UseMemberCasing();
    });

Hint of how to change default JSON serialization and deserialization settings in ASP.NET Core.


Comments

# by Ike

「屬性名稱的第一個字元被轉成小寫」這很討厭,怎麼可以擅自改掉呢

# by Eason

請問一下 我用.net 6 開了一個 webapi 專案 在Promgram.cs 的service 跟您一樣 都在AddRazorPages()、AddControllers()、AddMvc() 接上 AddJsonOptions() 但還是無法將json 轉成,請問是哪裡有漏了嗎 程式如下 Service 已加入 builder.Services.AddControllers() .ConfigureApiBehaviorOptions(options => { options.SuppressModelStateInvalidFilter = true; options.SuppressMapClientErrors = true; }).AddJsonOptions(options => { options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase; options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.CjkUnifiedIdeographs); options.JsonSerializerOptions.WriteIndented = true; }); builder.Services.AddMvcCore().AddJsonOptions(options => { options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase; options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.CjkUnifiedIdeographs); options.JsonSerializerOptions.WriteIndented = true; }); builder.Services.AddMvc().AddJsonOptions(options => { options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase; options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.CjkUnifiedIdeographs); options.JsonSerializerOptions.WriteIndented = true; }); builder.Services.AddRazorPages().AddJsonOptions(options => { options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase; options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.CjkUnifiedIdeographs); options.JsonSerializerOptions.WriteIndented = true; }); json程式部分 var result = new { ErrorMessage = "你好!", ErrorMessage2 = "YO" }; var js = JsonSerializer.Serialize(result); 結果 {"ErrorMessage":"\u4F60\u597D!","ErrorMessage2":"YO"}

# by Jeffrey

to Eason,要不要將可重現問題的專案丟上 Github,讓大家可以下載測試,應該能馬上找到答案。

# by Eason

謝謝 Jeffrey 提醒 我寫了一個我目前遇到問題的專案在github 位置在 https://github.com/HsunsProjects/Net6WebAPI 專案裡面有兩隻api 分別是api/Question/Why (這支是我目前有問題的API,不知道為什麼在program.cs 裡的 service .AddJsonOptions 無法編碼中文) 和api/Question/Solved(這知是我知道怎麼處理的方法,但想像版主一樣寫在service裡處理) 想請問有沒有大大可以幫忙解答一下

# by Jeffrey

to Eason, 我修改了一個版本,修改對照在這裡 https://github.com/darkthread/Net6WebAPI/commit/4d61a88fe1f7113e3987b7fbfb30d4b146132e6a 發現幾個問題: 1. 只需 AddMvc() 就好,它涵蓋 AddControllers()、AddMvcCore()... 參考:https://blog.darkthread.net/blog/add-mvc-to-razor-project/ 2. 記得 dotnet new gitignore 加上 .gitignore,bin、obj 一般是不需要放進版控的 3. .AddJsonOptions() 影響的是 Controller.Json() 的序列化行為,如果要自己呼叫 JsonSerializer.Serialize() 要另外設定,建議做法是 QuestionController 改繼承 Controller,回傳結果改成 return this.Json(result)。

# by Eason

To Jeffrey 大大第1、2點的提醒 第一點主要是要測試到底加在哪會有差所以通通給他加下去了啦,哈哈 :) 然後也謝謝第3點解除了我心中的問題,原來AddJsonOptions()只影響了Controller.Json() 只是因為我開啟的是WEBAPI專案,他預設是繼承ControllerBase,我以為是一樣的意思 雖然工作都是C# MVC開發,但對於很多很基礎的問題仍然不太了解為什麼,也因此常常來拜訪黑暗執行緒 真的很感謝 Jeffrey 和這個平台 :)

# by Jeffrey

to Eason, 補充一點,改繼承 Controller 會導致無法使用原本 WebAPI 的標準寫法(等於是用 MVC 去實作 WebAPI,這篇有更多討論 https://blog.darkthread.net/blog/build-webapi-with-aspnetmvc-tutorial/),若要維持 ControllerBase 並設定 JSON 序列化方式,可以參考這篇 https://makolyte.com/aspdotnet-how-to-change-the-json-serialization-settings/ (手續較繁瑣)

Post a comment