1

我有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 了吗?

4

1 回答 1

3

据我了解,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 的类似方法
于 2021-11-06T20:52:39.593 回答