Asp.net web api Web API/JsonMediaTypeFormatter接受无效的JSON并将空参数传递给操作

Asp.net web api Web API/JsonMediaTypeFormatter接受无效的JSON并将空参数传递给操作,asp.net-web-api,json.net,Asp.net Web Api,Json.net,我有以下型号: public class Resource { [DataMember(IsRequired = true)] [Required] public bool IsPublic { get; set; } [DataMember(IsRequired = true)] [Required] public ResourceKey ResourceKey { get; set; } } public class ResourceKey

我有以下型号:

public class Resource
{
    [DataMember(IsRequired = true)]
    [Required]
    public bool IsPublic { get; set; }

    [DataMember(IsRequired = true)]
    [Required]
    public ResourceKey ResourceKey { get; set; }
}

public class ResourceKey
{
    [StringLength(50, MinimumLength = 1)]
    [Required]
    public string SystemId { get; set; }

    [StringLength(50, MinimumLength = 1)]
    [Required]
    public string SystemDataIdType { get; set; }

    [StringLength(50, MinimumLength = 1)]
    [Required]
    public string SystemEntityType { get; set; }

    [StringLength(50, MinimumLength = 1)]
    [Required]
    public string SystemDataId { get; set; }
}
我有以下行动方法签名:

public HttpResponseMessage PostResource(Resource resource)
我在正文中发送了以下带有JSON的请求(属性“IsPublic”的值故意无效):

这是无效的JSON-通过JSONLint运行它,它会告诉您:

第2行的分析错误:

{“IsPublic”:invalidvalue

应为“字符串”、“数字”、“空”、“真”、“假”、“{”、“[”

ModelState.IsValid属性为“true”-为什么?

此外,格式化程序似乎不再抛出验证错误,而是放弃反序列化,将'resource'参数作为null传递给action方法

请注意,如果我为其他属性输入了无效值,也会发生这种情况,例如,替换:

"SystemId": notAnObjectOrLiteralOrArray
但是,如果我发送以下JSON,并为“SystemId”属性指定一个特殊的未定义的值:

{
    "IsPublic": true,   
    ResourceKey:{       
        "SystemId": undefined,
        "SystemDataIdType": "int",
        "SystemDataId": "Lorem ipsum",
        "SystemEntityType":"EntityType"
    },    
}
然后我得到以下合理的异常抛出:

Exception Type: Newtonsoft.Json.JsonReaderException
Message: "Error reading string. Unexpected token: Undefined. Path 'ResourceKey.SystemId', line 4, position 24."
Stack Trace: " at Newtonsoft.Json.JsonReader.ReadAsStringInternal() 
at Newtonsoft.Json.JsonTextReader.ReadAsString() 
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.ReadForType(JsonReader reader, JsonContract contract, Boolean hasConverter) 
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)"
那么:Newtonsoft.Json库中发生了什么,导致了部分Json验证

PS:可以将JSON名称/值对发布到Web API,而无需将名称括在引号中

{
    IsPublic: true, 
    ResourceKey:{       
        SystemId: "123",
        SystemDataIdType: "int",
        SystemDataId: "Lorem ipsum",
        SystemEntityType:"EntityType"
    },    
}

这也是无效的JSON!

与其说是一个答案,不如说是一个解决方案,但我可以通过发布在的解决方案来实现这一点。基本上,不需要让Post方法的签名获取资源实例,而是让它不带参数,然后使用JSON.Net(或JsonMediaTypeFormatter的新实例)进行反序列化

public void Post()
{
    var json = Request.Content.ReadAsStringAsync().Result;
    var resource = Newtonsoft.Json.JsonConvert.DeserializeObject<Resource>(json);

    //Important world saving work going on here
}
public void Post()
{
var json=Request.Content.ReadAsStringAsync().Result;
var resource=Newtonsoft.Json.JsonConvert.DeserializeObject(Json);
//这里正在进行拯救世界的重要工作
}

好-看来部分问题是由我自己造成的

我在控制器上有两个过滤器:

  • 检查是否有任何空操作参数被传递给操作方法,如果有,则返回“400错误请求”响应,规定参数不能为空
  • 检查ModelState错误的ModelState检查筛选器,如果发现任何错误,则在“400错误请求”响应中返回它们
  • 我犯的错误是将空参数过滤器放在模型状态检查过滤器之前

    在模型绑定之后,对于第一个JSON示例,序列化将正确失败,并将相关的序列化异常置于ModelState中,而action参数将保持null,这是正确的

    但是,由于第一个筛选器检查空参数,然后返回“404错误请求”响应,因此ModelState筛选器从未启动

    因此,验证似乎没有发生,而事实上确实发生了,但结果却被忽略了

    重要信息:模型绑定期间发生的序列化异常被放置在ModelState键值对值的“Exception”属性中…而不是ErrorMessage属性中

    为了帮助其他人实现这一区别,以下是我的ModelValidationFilterAttribute:

    public class ModelValidationFilterAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            if (actionContext.ModelState.IsValid) return;
    
            // Return the validation errors in the response body.
            var errors = new Dictionary<string, IEnumerable<string>>();
            foreach (KeyValuePair<string, ModelState> keyValue in actionContext.ModelState)
            {
                var modelErrors = keyValue.Value.Errors.Where(e => e.ErrorMessage != string.Empty).Select(e => e.ErrorMessage).ToList();
                if (modelErrors.Count > 0)
                    errors[keyValue.Key] = modelErrors;
    
                // Add details of any Serialization exceptions as well
                var modelExceptions = keyValue.Value.Errors.Where(e => e.Exception != null).Select(e => e.Exception.Message).ToList();
                if (modelExceptions.Count > 0)
                    errors[keyValue.Key + "_exception"] = modelExceptions;
            }
            actionContext.Response =
                actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, errors);
        }
    }
    
    现在,下面的JSON结果是:

    {
        "IsPublic": invalidvalue,   
        "ResourceKey":{     
            "SystemId": "asdf",
            "SystemDataIdType": "int",
            "SystemDataId": "Lorem ipsum",
            "SystemEntityType":"EntityType"
        },    
    } 
    
    {
        "resource.IsPublic_exception": [(2)
        "Unexpected character encountered while parsing value: i. Path 'IsPublic', line 2, position 21.",
        "Unexpected character encountered while parsing value: i. Path 'IsPublic', line 2, position 21."
        ]-
    }
    

    然而,所有这些都不能解释为什么JsonMediaTypeFormatter仍然解析无效的JSON,例如,它不要求名称是字符串。

    我能够用一个非常简化的例子来复制这个问题——这个例子的输出是:200-OK-400-BadRequest-{“Message”:“模型数据为null,但它没有通过验证!”}我最近在另一个项目中遇到了这个问题,并将其视为我当时使用的自定义媒体类型格式化程序的一个奇怪之处而不予考虑,但这个示例仅使用标准格式化程序并展示了这个问题,所以我很好奇这一切意味着什么……我认为这个谜团的答案在默认模型绑定或p参数绑定配置,甚至可能在媒体类型格式化程序(
    JsonMediaTypeFormatter
    )中。作为参考,404“未找到”。您应该使用400表示“错误请求”-@Snixtor:correct。很抱歉,“404”是一个输入错误。我确实返回400。相同的异常添加在响应中出现两次。
        [ModelValidationFilter]
        [ActionArgNotNullFilter]
        public HttpResponseMessage PostResource(Resource resource)
    
    {
        "IsPublic": invalidvalue,   
        "ResourceKey":{     
            "SystemId": "asdf",
            "SystemDataIdType": "int",
            "SystemDataId": "Lorem ipsum",
            "SystemEntityType":"EntityType"
        },    
    } 
    
    {
        "resource.IsPublic_exception": [(2)
        "Unexpected character encountered while parsing value: i. Path 'IsPublic', line 2, position 21.",
        "Unexpected character encountered while parsing value: i. Path 'IsPublic', line 2, position 21."
        ]-
    }