zoukankan      html  css  js  c++  java
  • 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 分为 InputFormatterOutputFormatter

    • InputFormatter 用来解析请求 Body 的数据,将请求参数映射到强类型的 model,Request Body => Value
    • OutputFormatter 用来将强类型的数据序列化成响应输出,Value => Response Body

    Formatter 需要指定支持的 MediaType,可以理解为请求类型,体现在请求头上,对于 InputFormatter 对应的就是 Content-Type ,对于 OutputFormatter 对应的是 Accept,asp.net core 会根据请求信息来选择注册的 formatter。

    Sample

    先来看一下实现效果吧,实现效果如下:

    swagger

    swagger 的支持也算比较好了,在增加了新的 Content-Type 支持之后在 swagger 上可以看得到,而且可以切换请求的 Content-Type,上图中的 text/base64-json 就是我自定义的一个 Content-Type

    默认请求:

    json-request

    对原始请求进行 base64 编码,再请求:

    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 字符串就会像下面这样:

    error

    实际使用的时候,只需要在 Startup 里配置一下就可以了,如:

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

    More

    通过自定义 Content-Type 的支持我们可以无侵入的实现不同的请求内容,上面的示例代码可以在 Github 上获取 https://github.com/WeihanLi/SamplesInPractice/tree/master/AspNetCoreSample,可以根据自己的需要进行自定义

    References

    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    Redis——发布/订阅
    Redis——任务队列
    GOF设计模式——Builder模式
    GOF设计模式——Prototype模式
    GOF设计模式——Singleton模式
    shell 脚本中的数学计算表达
    shell $'somestring'
    shell if-elif-elif-fi
    vim 使用
    疑问:为什么要使用href=”javascript:void(0);”?
  • 原文地址:https://www.cnblogs.com/weihanli/p/14530672.html
Copyright © 2011-2022 走看看