5

私は次のモデルを持っています:

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」の意図的に無効な値)。

Request Method:POST
Host: localhost:63307
Connection: keep-alive
Content-Length: 477
User-Agent: Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.97 Safari/537.22
Origin: chrome-extension://hgmloofddffdnphfgcellkdfbfbjeloo
Content-Type: application/json
Accept: */*
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3

{
    "IsPublic": invalidvalue,   
    "ResourceKey":{     
        "SystemId": "asdf",
        "SystemDataIdType": "int",
        "SystemDataId": "Lorem ipsum",
        "SystemEntityType":"EntityType"
    },    
}

これは無効な JSON です。JSONLint で実行すると、次のように表示されます。

2 行目の解析エラー:

{ "IsPublic": 無効な値,

...................^ 'STRING'、'NUMBER'、'NULL'、'TRUE'、'FALSE'、'{'、'[' が必要です

ModelState.IsValid プロパティは「true」です - なぜですか???

また、検証エラーをスローする代わりに、フォーマッタは逆シリアル化をあきらめて、'resource' 引数を単に null としてアクション メソッドに渡しているようです!

これは、他のプロパティに無効な値を入力した場合にも発生することに注意してください。たとえば、次のように置き換えます。

"SystemId": notAnObjectOrLiteralOrArray

ただし、 「SystemId」プロパティに特別な未定義の値を指定して次の JSON を送信すると、次のようになります。

{
    "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)"

SO: Newtonsoft.Json ライブラリで何が起こっており、部分的な JSON 検証のように見えるのですか?

PS: 名前を引用符で囲まずに、JSON の名前と値のペアを Web API に投稿することは可能です...

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

これも無効な JSON です。

4

2 に答える 2

3

OK-それで、問題の一部は私自身の行動によって引き起こされたようです。

私はコントローラーに2つのフィルターを持っていました:

  1. アクションメソッドに渡されているnullアクションパラメータがあるかどうかを確認し、渡されている場合は、パラメータをnullにできないことを規定する「400BadRequest」応答を返します。
  2. ModelStateのエラーをチェックし、エラーが見つかった場合は「400BadRequest」応答で返すModelState検査フィルター。

私が犯した間違いは、モデル状態チェックフィルターの前にnull引数フィルターを配置することでした。

モデルバインディング後、最初のJSONの例ではシリアル化が正しく失敗し、関連するシリアル化例外がModelStateに配置され、action引数はnullのままになります。

ただし、最初のフィルターはnull引数をチェックしてから、「404 Bad Request」応答を返していたため、ModelStateフィルターが開始されることはありませんでした...

したがって、実際には検証が行われていないように見えましたが、結果は無視されていました。

重要:モデルバインディング中に発生するシリアル化例外は、ModelStateKeyValueペアの値の「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);
    }
}

そして、これが正しい順序のフィルターを使用したアクションメソッドです。

    [ModelValidationFilter]
    [ActionArgNotNullFilter]
    public HttpResponseMessage PostResource(Resource resource)

したがって、次の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."
    ]-
}

ただし、これはすべて、無効なJSONがJsonMediaTypeFormatterによってまだ解析される理由を説明していません。たとえば、名前が文字列である必要はありません。

于 2013-02-27T10:58:19.643 に答える
1

答えというよりも回避策ですが、http://aspnetwebstack.codeplex.com/workitem/609に投稿された回避策を使用してこれを機能させることができました。基本的に、Post メソッドの署名に Resource インスタンスを使用させる代わりに、パラメーターを使用させずに、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
}
于 2013-02-26T21:58:23.350 に答える