1. 程式人生 > >asp.net core 實現支援自定義 Content-Type

asp.net core 實現支援自定義 Content-Type

# asp.net core 實現支援自定義 Content-Type ## Intro 我們最近有一個原本是內網的服務要上公網,在公網上有一層 `Cloudflare` 作為網站的公網流量提供者,CloudFlare 會有一層防火牆攔截掉一些非法的請求,我們有一些 API 會提交一些 html 內容,經過 `Cloudflare` 的時候會被 `Cloudflare` 攔截,導致某些功能不能夠正常使用,於是就想對提交的資料進行一個編碼之後再提交,伺服器端針對需要解碼的請求進行解碼再解析,我們新加了一個 `Content-Type` 的支援,編碼後的資料使用新的 `Content-Type`,對於不編碼的資料依然可以工作,目前我們做了一個簡單的 base64 編碼,如果需要的話也可以實現複雜一些的加密、壓縮等。 ## Basis asp.net core 預設支援 JSON 請求,因為內建了針對 JSON 內容的 `Formatter`,.NET Core 2.x 使用的是 `Newtonsoft.Json` 作為預設 JSON formatter,從 .NET Core 3.0 開始引入了 `System.Text.Json` 作為預設的 JSON formatter,如果要支援 XML 需要引入針對 XML 的 formatter,相應的如果需要增加其他型別的請求實現自己的 formatter 就可以了 Formatter 分為 `InputFormatter` 和 `OutputFormatter` - `InputFormatter` 用來解析請求 `Body` 的資料,將請求引數對映到強型別的 model,Request Body => Value - `OutputFormatter` 用來將強型別的資料序列化成響應輸出,Value => Response Body Formatter 需要指定支援的 `MediaType`,可以理解為請求型別,體現在請求頭上,對於 `InputFormatter` 對應的就是 `Content-Type` ,對於 `OutputFormatter` 對應的是 `Accept`,asp.net core 會根據請求資訊來選擇註冊的 formatter。 ## Sample 先來看一下實現效果吧,實現效果如下: ![swagger](https://img2020.cnblogs.com/blog/489462/202103/489462-20210313221210331-129128092.png) swagger 的支援也算比較好了,在增加了新的 `Content-Type` 支援之後在 swagger 上可以看得到,而且可以切換請求的 `Content-Type`,上圖中的 `text/base64-json` 就是我自定義的一個 `Content-Type` 預設請求: ![json-request](https://img2020.cnblogs.com/blog/489462/202103/489462-20210313221210129-1671651422.png) 對原始請求進行 base64 編碼,再請求: ![base64-json-request](https://img2020.cnblogs.com/blog/489462/202103/489462-20210313221209725-262816060.png) ## Implement 實現程式碼如下: ``` c# public class Base64EncodedJsonInputFormatter : TextInputFormatter { public Base64EncodedJsonInputFormatter() { // 註冊支援的 Content-Type SupportedMediaTypes.Add("text/base64-json"); SupportedEncodings.Add(Encoding.UTF8); } public override async Task ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding) { try { using var reader = context.ReaderFactory(context.HttpContext.Request.Body, encoding); var rawContent = await reader.ReadToEndAsync(); if (string.IsNullOrEmpty(rawContent)) { return await InputFormatterResult.NoValueAsync(); } var bytes = Convert.FromBase64String(rawContent); var services = context.HttpContext.RequestServices; var modelValue = await GetModelValue(services, bytes); return await InputFormatterResult.SuccessAsync(modelValue); async ValueTask GetModelValue(IServiceProvider serviceProvider, byte[] stringBytes) { var newtonJsonOption = serviceProvider.GetService>()?.Value; if (newtonJsonOption is null) { await using var stream = new MemoryStream(stringBytes); var result = await System.Text.Json.JsonSerializer.DeserializeAsync(stream, context.ModelType, services.GetRequiredService>().Value.JsonSerializerOptions); return result; } var stringContent = encoding.GetString(bytes); return Newtonsoft.Json.JsonConvert.DeserializeObject(stringContent, context.ModelType, newtonJsonOption.SerializerSettings); } } catch (Exception e) { context.ModelState.TryAddModelError(string.Empty, e.Message); return await InputFormatterResult.FailureAsync(); } } } ``` 上述程式碼相容了使用 `System.Text.Json` 和 `Newtonsoft.Json`,在發生異常時將錯誤資訊新增一個 `ModelError` 以便在前端可以得到錯誤資訊的反饋,例如傳一個不合法的 base64 字串就會像下面這樣: ![error](https://img2020.cnblogs.com/blog/489462/202103/489462-20210313221209247-16112981.png) 實際使用的時候,只需要在 `Startup` 裡配置一下就可以了,如: ``` c# services.AddControllers(options => { options.InputFormatters.Add(new Base64EncodedJsonInputFormatter()); }); ``` ## More 通過自定義 `Content-Type` 的支援我們可以無侵入的實現不同的請求內容,上面的示例程式碼可以在 Github 上獲取
,可以根據自己的需要進行自定義 ## References - - -