如何在.NET 6.0的MinimalApi中配置NewtonsoftJson

14

我有一个使用net6.0的最小API项目,我想使用NetwtonsoftJson代替内置的System.Text.Json库进行序列化和反序列化。

目前我有这些JsonOptions配置,它按预期工作。

builder.Services.Configure<JsonOptions>(options =>
{
    options.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
    options.SerializerOptions.WriteIndented = true;    
    options.SerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
    options.SerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
});

如果我尝试更改为使用类似下面的Newtonsoft.Json.JsonSerializerSettings等价物,我将无法获得相同的行为。 相反,它似乎使用默认的System.Text.Json配置。
builder.Services.Configure<JsonSerializerSettings>(options =>
{
    options.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
    options.Converters.Add(
        new StringEnumConverter
        {
            NamingStrategy = new Newtonsoft.Json.Serialization.CamelCaseNamingStrategy()
        });
});

net5.0 中,我知道可以使用以下内容:

services.AddControllers().AddNewtonsoftJson((options) => //options); // OR
services.AddMvc().AddNewtonsoftJson((options) => //options);

然而,如果我在我的 net6.0 项目中像上面那样使用它,那么我就不再使用 MinimalApi 了吗?

1个回答

18
如在文档中提到的:

Body 绑定源使用 System.Text.Json 进行反序列化。无法更改此默认设置

但是有解决方法。
根据我的理解,Minimal API 依赖于一些关于类型绑定的约定。从我所看到的,它们会搜索具有以下签名的方法:ValueTask<TModel?> BindAsync(HttpContext context, ParameterInfo parameter),否则将尝试使用 httpContext.Request.ReadFromJsonAsync,该方法内部使用 System.Text.Json,而且无法更改,因此 services.Add...().AddNewtonsoftJson((options) => //options); 的方法不起作用。

要使用 Newtonsoft.Json,你可以尝试以下方法(除了直接通过 app.MapPost("/pst", (HttpContext c) => c.Request...) 处理请求):

如果你可以控制所有需要使用它进行反序列化的类,可以将它们都继承自某个通用基类,该基类将具有所需签名的方法(你也可以使用带有 实现静态方法 的接口):

public class BaseModel<TModel>
{
    public static async ValueTask<TModel?> BindAsync(HttpContext context, ParameterInfo parameter)
    {
        if (!context.Request.HasJsonContentType())
        {
            throw new BadHttpRequestException(
                "Request content type was not a recognized JSON content type.",
                StatusCodes.Status415UnsupportedMediaType);
        }

        using var sr = new StreamReader(context.Request.Body);
        var str = await sr.ReadToEndAsync();
        
        return JsonConvert.DeserializeObject<TModel>(str);
    }
}

使用方法:

class PostParams : BaseModel<PostParams>
{
    [JsonProperty("prop")]
    public int MyProperty { get; set; }
}

// accepts json body {"prop": 2}
app.MapPost("/pst", (PostParams po) => po.MyProperty);

请注意,在这个例子中,BaseModel<TModel>的实现相当简单,可能还有改进的空间(至少可以查看HttpRequestJsonExtensions.ReadFromJsonAsync)。
如果您无法控制模型或不想从某个基类继承它们,您可以考虑创建包装器。
public class Wrapper<TModel>
{
    public Wrapper(TModel? value)
    {
        Value = value;
    }

    public TModel? Value { get; }

    public static async ValueTask<Wrapper<TModel>?> BindAsync(HttpContext context, ParameterInfo parameter)
    {
        if (!context.Request.HasJsonContentType())
        {
            throw new BadHttpRequestException(
                "Request content type was not a recognized JSON content type.",
                StatusCodes.Status415UnsupportedMediaType);
        }

        using var sr = new StreamReader(context.Request.Body);
        var str = await sr.ReadToEndAsync();

        return new Wrapper<TModel>(JsonConvert.DeserializeObject<TModel>(str));
    }
}

使用方式变为:
class PostParams
{
    [JsonProperty("prop")]
    public int MyProperty { get; set; }
}

// accepts json body {"prop": 2}
app.MapPost("/pst", (Wrapper<PostParams> po) => po.Value.MyProperty);

一些额外有用的链接:

  • MVC模型绑定器 - 由David Fowler提供。尽管我无法使其适用于services.AddControllers().AddNewtonsoftJson((options) => //options);
  • ParameterBinder - Damian Edwards提供的类似方法

Guru Stron:这是否意味着当 MVC(Web Api)控制器与 Minimal APIs 一起使用时,services.AddControllers().AddNewtonsoftJson(..) 仍然可以工作?也就是说,我们是否可以将控制器类型用作逃生口,以使用 Newtonsoft Json 作为替代方法,而不是向类型添加方法或使用包装器? - GrumpyRodriguez
1
@GrumpyRodriguez 是的,就控制器和MVC而言,据我所知没有任何改变。 - Guru Stron
非常感谢。我原以为我只能坚持使用以前的主机模型(GenericHost)并且要避免失去使用Newtonsoft json的选项,后来我意识到新的主机模型并不等同于仅有的最小API。 - GrumpyRodriguez
1
@GrumpyRodriguez 是的,没错。现在的最小托管模型已经成为了 Minimal APIs 和普通 ASP.NET Core 的默认设置,并且它应该可以涵盖大多数通用托管模型允许的场景。很高兴能够帮到你! - Guru Stron
谢谢@GuruStron,这是我在使用NewtonsoftJson和.net core 6的MinimalApi时遇到的最佳解决方案。 - Tejasvi Hegde

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接