如何根据路由配置两个 JSON 序列化器和 select 正确的序列化器
How to configure two JSON serializers and select the correct one based on the route
我有一个使用 .Net Framework 4.7 的 ASP.NET Core Web API 项目,我正在升级到 .Net Core 3.1。我升级的原因之一是使用新的 System.Text.Json 序列化程序。
目前,我有一些基于路线的 API 版本,例如:
/api/v1/controller
/api/v2/controller
我将创建一个新的 (v3) 来使用新的序列化程序。但问题是:我想在旧路线上继续使用 JSON.Net,以避免集成客户端出现任何可能的格式问题。
有没有一种简单的方法可以配置 Asp.Net Core 以根据路由自动 select 正确的 JSON 序列化程序?
您可以创建自己的超级 InputFormatter
/OutputFormatter
以便它在运行时检查条件,然后决定使用 System.Text.Json
或动态使用 Newtonsoft.Json
.
比如我们可以查看当前的action方法(或者controllerclass):
- 如果它有自定义属性
[UseSystemTextJsonAttribute]
,则使用System.Text.Json
- 如果它有自定义属性
[UseNewtonsoftJsonAttribute]
,则使用Newtonsoft.Json
。
我创建了一个自定义的 InputFormatter 供您参考:
// the custom attribute
internal abstract class UseJsonAttribute : Attribute, IAsyncActionFilter
{
public Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) => next();
}
internal class UseSystemTextJsonAttribute : UseJsonAttribute { }
internal class UseNewtonsoftJsonAttribute : UseJsonAttribute { }
// Our Super Input Formatter
internal class MySuperJsonInputFormatter : TextInputFormatter
{
public MySuperJsonInputFormatter()
{
SupportedEncodings.Add(UTF8EncodingWithoutBOM);
SupportedEncodings.Add(UTF16EncodingLittleEndian);
SupportedMediaTypes.Add("application/json");
}
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
{
var mvcOpt= context.HttpContext.RequestServices.GetRequiredService<IOptions<MvcOptions>>().Value;
var formatters = mvcOpt.InputFormatters;
TextInputFormatter formatter =null; // the real formatter : SystemTextJsonInput or Newtonsoft
Endpoint endpoint = context.HttpContext.GetEndpoint();
if(endpoint.Metadata.GetMetadata<UseSystemTextJsonAttribute>()!= null)
{
formatter= formatters.OfType<SystemTextJsonInputFormatter>().FirstOrDefault();
//formatter = formatter ?? SystemTextJsonInputFormatter
}
else if( endpoint.Metadata.GetMetadata<UseNewtonsoftJsonAttribute>() != null){
// don't use `Of<NewtonsoftJsonInputFormatter>` here because there's a NewtonsoftJsonPatchInputFormatter
formatter= (NewtonsoftJsonInputFormatter)(formatters
.Where(f =>typeof(NewtonsoftJsonInputFormatter) == f.GetType())
.FirstOrDefault());
}
else{
throw new Exception("This formatter is only used for System.Text.Json InputFormatter or NewtonsoftJson InputFormatter");
}
var result = await formatter.ReadRequestBodyAsync(context,encoding);
return result;
}
}
internal class MySuperJsonOutputFormatter : TextOutputFormatter
{
... // similar to MySuperJsonInputFormatter, omitted for brevity
}
然后在启动时配置Jsonsettings/options:
services.AddControllers(opts =>{ })
.AddNewtonsoftJson(opts =>{ /**/ })
.AddJsonOptions(opts =>{ /**/ });
注意 AddNewtonsoftJson()
将删除内置 SystemTextJsonInputFormatters
。所以我们需要手动配置 MvcOptions
:
services.AddOptions<MvcOptions>()
.PostConfigure<IOptions<JsonOptions>, IOptions<MvcNewtonsoftJsonOptions>,ArrayPool<char>, ObjectPoolProvider,ILoggerFactory>((opts, jsonOpts, newtonJsonOpts, charPool, objectPoolProvider, loggerFactory )=>{
// configure System.Text.Json formatters
if(opts.InputFormatters.OfType<SystemTextJsonInputFormatter>().Count() ==0){
var systemInputlogger = loggerFactory.CreateLogger<SystemTextJsonInputFormatter>();
opts.InputFormatters.Add(new SystemTextJsonInputFormatter(jsonOpts.Value, systemInputlogger));
}
if(opts.OutputFormatters.OfType<SystemTextJsonOutputFormatter>().Count() ==0){
opts.OutputFormatters.Add(new SystemTextJsonOutputFormatter(jsonOpts.Value.JsonSerializerOptions));
}
// configure Newtonjson formatters
if(opts.InputFormatters.OfType<NewtonsoftJsonInputFormatter>().Count() ==0){
var inputLogger= loggerFactory.CreateLogger<NewtonsoftJsonInputFormatter>();
opts.InputFormatters.Add(new NewtonsoftJsonInputFormatter(
inputLogger, newtonJsonOpts.Value.SerializerSettings, charPool, objectPoolProvider, opts, newtonJsonOpts.Value
));
}
if(opts.OutputFormatters.OfType<NewtonsoftJsonOutputFormatter>().Count()==0){
opts.OutputFormatters.Add(new NewtonsoftJsonOutputFormatter(newtonJsonOpts.Value.SerializerSettings, charPool, opts));
}
opts.InputFormatters.Insert(0, new MySuperJsonInputFormatter());
opts.OutputFormatters.Insert(0, new MySuperJsonOutputFormatter());
});
现在应该可以正常工作了。
我使用这种方法(创建一个“超级”格式化程序,然后决定使用哪个真正的格式化程序)允许不同的 路由 具有不同的 JSON 格式(在我们的场景中是否使用驼峰命名法)。这很好用,并允许我们使用 third-party add-in,它需要一组不同于我们标准的特定格式规则...
由于 third-party add-in 已完全编译,我们无法在其控制器上使用自定义属性;没有人给我们每个人加一个(!);所以我们改为检查路由并根据它选择格式化程序。
在这种情况下真正的救星:)
我有一个使用 .Net Framework 4.7 的 ASP.NET Core Web API 项目,我正在升级到 .Net Core 3.1。我升级的原因之一是使用新的 System.Text.Json 序列化程序。
目前,我有一些基于路线的 API 版本,例如:
/api/v1/controller
/api/v2/controller
我将创建一个新的 (v3) 来使用新的序列化程序。但问题是:我想在旧路线上继续使用 JSON.Net,以避免集成客户端出现任何可能的格式问题。
有没有一种简单的方法可以配置 Asp.Net Core 以根据路由自动 select 正确的 JSON 序列化程序?
您可以创建自己的超级 InputFormatter
/OutputFormatter
以便它在运行时检查条件,然后决定使用 System.Text.Json
或动态使用 Newtonsoft.Json
.
比如我们可以查看当前的action方法(或者controllerclass):
- 如果它有自定义属性
[UseSystemTextJsonAttribute]
,则使用System.Text.Json
- 如果它有自定义属性
[UseNewtonsoftJsonAttribute]
,则使用Newtonsoft.Json
。
我创建了一个自定义的 InputFormatter 供您参考:
// the custom attribute
internal abstract class UseJsonAttribute : Attribute, IAsyncActionFilter
{
public Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) => next();
}
internal class UseSystemTextJsonAttribute : UseJsonAttribute { }
internal class UseNewtonsoftJsonAttribute : UseJsonAttribute { }
// Our Super Input Formatter
internal class MySuperJsonInputFormatter : TextInputFormatter
{
public MySuperJsonInputFormatter()
{
SupportedEncodings.Add(UTF8EncodingWithoutBOM);
SupportedEncodings.Add(UTF16EncodingLittleEndian);
SupportedMediaTypes.Add("application/json");
}
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding)
{
var mvcOpt= context.HttpContext.RequestServices.GetRequiredService<IOptions<MvcOptions>>().Value;
var formatters = mvcOpt.InputFormatters;
TextInputFormatter formatter =null; // the real formatter : SystemTextJsonInput or Newtonsoft
Endpoint endpoint = context.HttpContext.GetEndpoint();
if(endpoint.Metadata.GetMetadata<UseSystemTextJsonAttribute>()!= null)
{
formatter= formatters.OfType<SystemTextJsonInputFormatter>().FirstOrDefault();
//formatter = formatter ?? SystemTextJsonInputFormatter
}
else if( endpoint.Metadata.GetMetadata<UseNewtonsoftJsonAttribute>() != null){
// don't use `Of<NewtonsoftJsonInputFormatter>` here because there's a NewtonsoftJsonPatchInputFormatter
formatter= (NewtonsoftJsonInputFormatter)(formatters
.Where(f =>typeof(NewtonsoftJsonInputFormatter) == f.GetType())
.FirstOrDefault());
}
else{
throw new Exception("This formatter is only used for System.Text.Json InputFormatter or NewtonsoftJson InputFormatter");
}
var result = await formatter.ReadRequestBodyAsync(context,encoding);
return result;
}
}
internal class MySuperJsonOutputFormatter : TextOutputFormatter
{
... // similar to MySuperJsonInputFormatter, omitted for brevity
}
然后在启动时配置Jsonsettings/options:
services.AddControllers(opts =>{ })
.AddNewtonsoftJson(opts =>{ /**/ })
.AddJsonOptions(opts =>{ /**/ });
注意 AddNewtonsoftJson()
将删除内置 SystemTextJsonInputFormatters
。所以我们需要手动配置 MvcOptions
:
services.AddOptions<MvcOptions>()
.PostConfigure<IOptions<JsonOptions>, IOptions<MvcNewtonsoftJsonOptions>,ArrayPool<char>, ObjectPoolProvider,ILoggerFactory>((opts, jsonOpts, newtonJsonOpts, charPool, objectPoolProvider, loggerFactory )=>{
// configure System.Text.Json formatters
if(opts.InputFormatters.OfType<SystemTextJsonInputFormatter>().Count() ==0){
var systemInputlogger = loggerFactory.CreateLogger<SystemTextJsonInputFormatter>();
opts.InputFormatters.Add(new SystemTextJsonInputFormatter(jsonOpts.Value, systemInputlogger));
}
if(opts.OutputFormatters.OfType<SystemTextJsonOutputFormatter>().Count() ==0){
opts.OutputFormatters.Add(new SystemTextJsonOutputFormatter(jsonOpts.Value.JsonSerializerOptions));
}
// configure Newtonjson formatters
if(opts.InputFormatters.OfType<NewtonsoftJsonInputFormatter>().Count() ==0){
var inputLogger= loggerFactory.CreateLogger<NewtonsoftJsonInputFormatter>();
opts.InputFormatters.Add(new NewtonsoftJsonInputFormatter(
inputLogger, newtonJsonOpts.Value.SerializerSettings, charPool, objectPoolProvider, opts, newtonJsonOpts.Value
));
}
if(opts.OutputFormatters.OfType<NewtonsoftJsonOutputFormatter>().Count()==0){
opts.OutputFormatters.Add(new NewtonsoftJsonOutputFormatter(newtonJsonOpts.Value.SerializerSettings, charPool, opts));
}
opts.InputFormatters.Insert(0, new MySuperJsonInputFormatter());
opts.OutputFormatters.Insert(0, new MySuperJsonOutputFormatter());
});
现在应该可以正常工作了。
我使用这种方法(创建一个“超级”格式化程序,然后决定使用哪个真正的格式化程序)允许不同的 路由 具有不同的 JSON 格式(在我们的场景中是否使用驼峰命名法)。这很好用,并允许我们使用 third-party add-in,它需要一组不同于我们标准的特定格式规则...
由于 third-party add-in 已完全编译,我们无法在其控制器上使用自定义属性;没有人给我们每个人加一个(!);所以我们改为检查路由并根据它选择格式化程序。
在这种情况下真正的救星:)