如何让WebAPI使用JsonProperty验证我的JSON(必需= Required.Always)?

Mic*_*ltu 8 c# asp.net json.net asp.net-web-api asp.net-web-api2

public class MyModel
{
    [JsonProperty(PropertyName = "foo", Required = Required.Always)]
    public String Bar;
}

public class MyController : ApiController
{
    public String PostVersion1([FromBody] MyModel myModel)
    {
        if (ModelState.IsValid)
        {
            if (myModel.Bar == null)
                return "What?!";
            else
                return "Input is valid.";
        }
        else
        {
            return "Input is invalid.";
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

结果:

Input              |Output
-------------------|------
{ "bad" : "test" } | What?!
{ "Bar" : "test" } | What?!
{ "foo" : "test" } | Input is valid.
Run Code Online (Sandbox Code Playgroud)

显然支持JsonPropertyAttribute,因为我能够设置PropertyName并使其生效.但是,我希望ModelState.IsValid前两个示例输入为false,因为RequiredJsonProprty参数设置为Always.

如果我只是通过JsonConvert运行它:

JsonConvert.DeserializeObject<MyModel>(@"{'bad':'test'}");
Run Code Online (Sandbox Code Playgroud)

在反序列化期间抛出异常,如预期的那样:

Result Message: Newtonsoft.Json.JsonSerializationException : Required property 'foo' not found in JSON. Path '', line 1, position 14.
Run Code Online (Sandbox Code Playgroud)

Mar*_* N. 5

默认情况下JsonMediaTypeFormatter依赖heJsonProperty来决定模型字段是否是必需的。然而,它确实依赖于RequiredAttribute

如果您想这样做,请实现一个新的IRequiredMemberSelector并将其设置为MediaTypeFormatter.RequiredMemberSelector.

在您的实施中,IRequiredMemberSelector您将传递一个MemberInfo. 您可以使用它来评估模型成员是否具有该JsonProperty属性以及是否设置了必需的标志,最后返回 true 或 false。这传播到该ModelState.IsValid属性(但它不会使用 JSON.NET 错误消息,而是使用 DataAnnotations/WebApi 错误消息。

如果您这样做,那么我建议您也保留默认行为。


Mic*_*ltu 3

为了解决这个问题,我最终创建了自己的自定义 JSON.NET MediaTypeFormatter。我的格式化程序允许 JSON.NET 反序列化异常冒泡,从而将异常信息返回给调用者。

这是我构建的 MediaTypeFormatter:

public class JsonMediaFormatter : MediaTypeFormatter
{
    private readonly JsonSerializer _jsonSerializer = new JsonSerializer();

    public JsonMediaFormatter()
    {
        SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
    }

    public override Boolean CanReadType(Type type)
    {
        if (type == null)
            return false;

        return true;
    }

    public override Boolean CanWriteType(Type type)
    {
        if (type == null)
            return false;

        return true;
    }

    public override Task<Object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
    {
        return Task.FromResult(Deserialize(readStream, type));
    }

    public override Task WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext, CancellationToken cancellationToken)
    {
        Serialize(writeStream, value);
        return Task.FromResult(0);
    }

    private Object Deserialize(Stream readStream, Type type)
    {
        var streamReader = new StreamReader(readStream);
        return _jsonSerializer.Deserialize(streamReader, type);
    }

    private void Serialize(Stream writeStream, Object value)
    {
        var streamWriter = new StreamWriter(writeStream);
        _jsonSerializer.Serialize(streamWriter, value);
        streamWriter.Flush();
    }
}
Run Code Online (Sandbox Code Playgroud)

为了在内置格式化程序上使用此格式化程序,我将这一行添加到我的 WebApiConfig 中:

config.Formatters.Insert(0, new Formatters.JsonMediaFormatter());
Run Code Online (Sandbox Code Playgroud)

通过将其插入索引 0,它优先于内置格式化程序。如果您愿意,可以删除内置的 JSON 格式化程序。

在这种情况下,ModelState在操作中始终有效,因为如果反序列化失败,在触发操作之前会向用户抛出异常。为了仍然使用空FromBody参数执行操作,需要做更多的工作。