咱們最近有一個本來是內網的服務要上公網,在公網上有一層 Cloudflare
做爲網站的公網流量提供者,CloudFlare 會有一層防火牆攔截掉一些非法的請求,咱們有一些 API 會提交一些 html 內容,通過 Cloudflare
的時候會被 Cloudflare
攔截,致使某些功能不可以正常使用,因而就想對提交的數據進行一個編碼以後再提交,服務器端針對須要解碼的請求進行解碼再解析,咱們新加了一個 Content-Type
的支持,編碼後的數據使用新的 Content-Type
,對於不編碼的數據依然能夠工做,目前咱們作了一個簡單的 base64 編碼,若是須要的話也能夠實現複雜一些的加密、壓縮等。html
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
git
InputFormatter
用來解析請求 Body
的數據,將請求參數映射到強類型的 model,Request Body => ValueOutputFormatter
用來將強類型的數據序列化成響應輸出,Value => Response BodyFormatter 須要指定支持的 MediaType
,能夠理解爲請求類型,體如今請求頭上,對於 InputFormatter
對應的就是 Content-Type
,對於 OutputFormatter
對應的是 Accept
,asp.net core 會根據請求信息來選擇註冊的 formatter。github
先來看一下實現效果吧,實現效果以下:web
swagger 的支持也算比較好了,在增長了新的 Content-Type
支持以後在 swagger 上能夠看獲得,並且能夠切換請求的 Content-Type
,上圖中的 text/base64-json
就是我自定義的一個 Content-Type
json
默認請求:c#
對原始請求進行 base64 編碼,再請求:api
實現代碼以下:服務器
public class Base64EncodedJsonInputFormatter : TextInputFormatter { public Base64EncodedJsonInputFormatter() { // 註冊支持的 Content-Type SupportedMediaTypes.Add("text/base64-json"); SupportedEncodings.Add(Encoding.UTF8); } public override async Task<InputFormatterResult> 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<object> GetModelValue(IServiceProvider serviceProvider, byte[] stringBytes) { var newtonJsonOption = serviceProvider.GetService<IOptions<MvcNewtonsoftJsonOptions>>()?.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<IOptions<JsonOptions>>().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 字符串就會像下面這樣:asp.net
實際使用的時候,只須要在 Startup
裏配置一下就能夠了,如:
services.AddControllers(options => { options.InputFormatters.Add(new Base64EncodedJsonInputFormatter()); });
經過自定義 Content-Type
的支持咱們能夠無侵入的實現不一樣的請求內容,上面的示例代碼能夠在 Github 上獲取 https://github.com/WeihanLi/SamplesInPractice/tree/master/AspNetCoreSample,能夠根據本身的須要進行自定義