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 => ValueOutputFormatter
用來將強型別的資料序列化成響應輸出,Value => Response Body
Formatter 需要指定支援的 MediaType
,可以理解為請求型別,體現在請求頭上,對於 InputFormatter
對應的就是 Content-Type
,對於 OutputFormatter
對應的是 Accept
,asp.net core 會根據請求資訊來選擇註冊的 formatter。
Sample
先來看一下實現效果吧,實現效果如下:
swagger 的支援也算比較好了,在增加了新的 Content-Type
支援之後在 swagger 上可以看得到,而且可以切換請求的 Content-Type
,上圖中的 text/base64-json
就是我自定義的一個 Content-Type
預設請求:
對原始請求進行 base64 編碼,再請求:
Implement
實現程式碼如下:
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 字串就會像下面這樣:
實際使用的時候,只需要在 Startup
裡配置一下就可以了,如:
services.AddControllers(options =>
{
options.InputFormatters.Add(new Base64EncodedJsonInputFormatter());
});
More
通過自定義 Content-Type
的支援我們可以無侵入的實現不同的請求內容,上面的示例程式碼可以在 Github 上獲取 https://github.com/WeihanLi/SamplesInPractice/tree/master/AspNetCoreSample,可以根據自己的需要進行自定義