假設我們有以下簡單的Ajax調用:ASP.NET的WebAPI VS MVC微妙的行爲偏差,當涉及到json-的參數
$.ajax({
url: "/somecontroller/someaction",
data: JSON.stringify({
someString1: "",
someString2: null,
someArray1: [],
someArray2: null
}),
method: "POST",
dataType: "json",
contentType: "application/json; charset=utf-8"
})
.done(function (response) {
console.log(response);
});
Ajax調用目標的行動asp.net控制器。 asp.net網站在處理json序列化時有默認(「工廠」)設置,唯一的調整是通過nuget安裝Newtonsoft.Json.dll,因此web.config包含以下部分:
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" />
</dependentAssembly>
global.asax.cs中webapi和mvc的配置部分保持不變。說了這麼多,我注意到,如果控制器「somecontroller」是的WebAPI控制器:
public class FooController : ApiController
{
public class Some
{
public string SomeString1 { get; set; }
public string SomeString2 { get; set; }
public long[] SomeArray1 { get; set; }
public long[] SomeArray2 { get; set; }
}
[HttpPost]
public IHttpActionResult Bar([FromBody] Some entity)
{
return Ok(new {ping1 = (string) null, ping2 = "", ping3 = new long[0]});
}
}
然後在C#世界獲得「someaction」方法裏面的數據是,像這樣:
entity.someString1: "",
entity.someString2: null,
entity.someArray1: [],
entity.someArray2: null
然而,如果控制器是一個MVC控制器(mvc4要準確):
public class FooController : System.Web.Mvc.Controller
{
public class Some
{
public string SomeString1 { get; set; }
public string SomeString2 { get; set; }
public long[] SomeArray1 { get; set; }
public long[] SomeArray2 { get; set; }
}
[HttpPost]
public System.Web.Mvc.JsonResult Bar([FromBody] Some entity)
{
return Json(new { ping1 = (string)null, ping2 = "", ping3 = new long[0] });
}
}
然後在CSHARP世界接收的方法中的數據看起來像這樣:
entity.someString1: null,
entity.someString2: null,
entity.someArray1: null,
entity.someArray2: null
很明顯,webapi和mvc控制器之間在參數的反序列化過程中如何處理空數組和空字符串時存在偏差。我設法解決了MVC控制器的怪癖,以便對空字符串和空數組執行「webapi」行爲(爲了完整性,我將在末尾發佈我的解決方案)。
我的問題是這樣的:
爲什麼會出現這種偏差在關於反序列化擺在首位存在?
我不能說這只是爲了「方便」而完成的,因爲默認mvc設置留下了多少空間,這些缺陷只是讓人費解並能夠清楚並一致地修復行動/ dto級別。
附錄:任何有興趣在這裏就是我強迫MVC控制器的行爲「的WebAPI」的方式,當談到他們送入動作的方法之前,反序列化參數:
//inside Application_Start
ModelBinders.Binders.DefaultBinder = new CustomModelBinder_Mvc();
ValueProviderFactories.Factories.Remove(
ValueProviderFactories.Factories.OfType<JsonValueProviderFactory>().FirstOrDefault()
);
ValueProviderFactories.Factories.Add(new JsonNetValueProviderFactory_Mvc());
實用工具類:
using System.Web.Mvc;
namespace Project.Utilities
{
public sealed class CustomModelBinder_Mvc : DefaultModelBinder //0
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
bindingContext.ModelMetadata.ConvertEmptyStringToNull = false;
Binders = new ModelBinderDictionary { DefaultBinder = this };
return base.BindModel(controllerContext, bindingContext);
}
}
//0 respect empty ajaxstrings aka "{ foo: '' }" gets converted to foo="" instead of null http://stackoverflow.com/a/12734370/863651
}
而且
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Dynamic;
using System.Globalization;
using System.IO;
using System.Web.Mvc;
using IValueProvider = System.Web.Mvc.IValueProvider;
// ReSharper disable RedundantCast
namespace Project.Utilities
{
public sealed class JsonNetValueProviderFactory_Mvc : ValueProviderFactory //parameter deserializer
{
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
if (controllerContext == null)
throw new ArgumentNullException(nameof(controllerContext));
if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
return null;
var jsonReader = new JsonTextReader(new StreamReader(controllerContext.HttpContext.Request.InputStream));
if (!jsonReader.Read())
return null;
var jsonObject = jsonReader.TokenType == JsonToken.StartArray //0
? (object)JsonSerializer.Deserialize<List<ExpandoObject>>(jsonReader)
: (object)JsonSerializer.Deserialize<ExpandoObject>(jsonReader);
return new DictionaryValueProvider<object>(AddToBackingStore(jsonObject), InvariantCulture); //1
}
private static readonly CultureInfo InvariantCulture = CultureInfo.InvariantCulture;
private static readonly JsonSerializer JsonSerializer = new JsonSerializer //newtonsoft
{
Converters =
{
new ExpandoObjectConverter(),
new IsoDateTimeConverter {Culture = InvariantCulture}
}
};
//0 use jsonnet to deserialize object to a dynamic expando object if we start with a [ treat this as an array
//1 return the object in a dictionary value provider which mvc can understand
private static IDictionary<string, object> AddToBackingStore(object value, string prefix = "", IDictionary<string, object> backingStore = null)
{
backingStore = backingStore ?? new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
var d = value as IDictionary<string, object>;
if (d != null)
{
foreach (var entry in d)
{
AddToBackingStore(entry.Value, MakePropertyKey(prefix, entry.Key), backingStore);
}
return backingStore;
}
var l = value as IList;
if (l != null)
{
if (l.Count == 0) //0 here be dragons
{
backingStore[prefix] = new object[0]; //0 here be dragons
}
else
{
for (var i = 0; i < l.Count; i++)
{
AddToBackingStore(l[i], MakeArrayKey(prefix, i), backingStore);
}
}
return backingStore;
}
backingStore[prefix] = value;
return backingStore;
}
private static string MakeArrayKey(string prefix, int index) => $"{prefix}[{index.ToString(CultureInfo.InvariantCulture)}]";
private static string MakePropertyKey(string prefix, string propertyName) => string.IsNullOrEmpty(prefix) ? propertyName : $"{prefix}.{propertyName}";
}
//0 here be dragons its vital to deserialize empty jsarrays "{ foo: [] }" to empty csharp array aka new object[0]
//0 here be dragons without this tweak we would get null which is completely wrong
}
相關問題:http://stackoverflow.com/questions/3641723/why-do-i-get-null-instead-of-empty-string-when-receiving-post-request-in-from-ra。沒有找到任何詳細的基本原理,關於視圖模型的新實例中引用類型的默認值在模型綁定期間爲null的情況只有幾點。對我來說,選擇它的原因並不真實。 –
確實。我正在研究這樣的帖子,人們會通過解決核心問題(也就是數據腐敗)而繞過叢林而變通解決方案。我覺得這裏的房間裏有一頭大象。 – xDisruptor