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

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

Intro

咱們最近有一個本來是內網的服務要上公網,在公網上有一層 Cloudflare 做爲網站的公網流量提供者,CloudFlare 會有一層防火牆攔截掉一些非法的請求,咱們有一些 API 會提交一些 html 內容,通過 Cloudflare 的時候會被 Cloudflare 攔截,致使某些功能不可以正常使用,因而就想對提交的數據進行一個編碼以後再提交,服務器端針對須要解碼的請求進行解碼再解析,咱們新加了一個 Content-Type 的支持,編碼後的數據使用新的 Content-Type,對於不編碼的數據依然能夠工做,目前咱們作了一個簡單的 base64 編碼,若是須要的話也能夠實現複雜一些的加密、壓縮等。html

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 分爲 InputFormatterOutputFormattergit

  • InputFormatter 用來解析請求 Body 的數據,將請求參數映射到強類型的 model,Request Body => Value
  • OutputFormatter 用來將強類型的數據序列化成響應輸出,Value => Response Body

Formatter 須要指定支持的 MediaType,能夠理解爲請求類型,體如今請求頭上,對於 InputFormatter 對應的就是 Content-Type ,對於 OutputFormatter 對應的是 Accept,asp.net core 會根據請求信息來選擇註冊的 formatter。github

Sample

先來看一下實現效果吧,實現效果以下:web

swagger

swagger 的支持也算比較好了,在增長了新的 Content-Type 支持以後在 swagger 上能夠看獲得,並且能夠切換請求的 Content-Type,上圖中的 text/base64-json 就是我自定義的一個 Content-Typejson

默認請求:c#

json-request

對原始請求進行 base64 編碼,再請求:api

base64-json-request

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.JsonNewtonsoft.Json,在發生異常時將錯誤信息添加一個 ModelError 以便在前端能夠獲得錯誤信息的反饋,例如傳一個不合法的 base64 字符串就會像下面這樣:asp.net

error

實際使用的時候,只須要在 Startup 裏配置一下就能夠了,如:

services.AddControllers(options =>
    {
        options.InputFormatters.Add(new Base64EncodedJsonInputFormatter());
    });

More

經過自定義 Content-Type 的支持咱們能夠無侵入的實現不一樣的請求內容,上面的示例代碼能夠在 Github 上獲取 https://github.com/WeihanLi/SamplesInPractice/tree/master/AspNetCoreSample,能夠根據本身的須要進行自定義

References

相關文章
相關標籤/搜索