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 => 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 上獲取 //github.com/WeihanLi/SamplesInPractice/tree/master/AspNetCoreSample,可以根據自己的需要進行自定義
References
- //docs.microsoft.com/en-us/aspnet/core/web-api/advanced/custom-formatters?view=aspnetcore-5.0
- //github.com/dotnet/AspNetCore.Docs/tree/main/aspnetcore/web-api/advanced/custom-formatters/samples
- //github.com/WeihanLi/SamplesInPractice/tree/master/AspNetCoreSample