发布JSON数据到ASP.NET MVC
我尝试使用JSON获取行项目列表到一个网页,然后将被操纵,并通过ajax请求发送回到服务器使用相同的JSON结构(除了有一个字段值更改)。
从服务器接收数据很容易,操作更容易! 但发送该JSON数据回到服务器保存…自杀时间! 请有人可以帮忙!
使用Javascript
var lineitems; // get data from server $.ajax({ url: '/Controller/GetData/', success: function(data){ lineitems = data; } }); // post data to server $.ajax({ url: '/Controller/SaveData/', data: { incoming: lineitems } });
C# – 对象
public class LineItem{ public string reference; public int quantity; public decimal amount; }
C# – 控制器
public JsonResult GetData() { IEnumerable<LineItem> lineItems = ... ; // a whole bunch of line items return Json(lineItems); } public JsonResult SaveData(IEnumerable<LineItem> incoming){ foreach(LineItem item in incoming){ // save some stuff } return Json(new { success = true, message = "Some message" }); }
数据以序列化后的数据到达服务器。 自动化的模型联编程序试图绑定IEnumerable<LineItem> incoming
并令人惊讶地得到的IEnumerable
具有正确数量的LineItems
– 它只是没有用数据填充它们。
解
使用来自多个来源的答案,主要是在另一个stackoverflowpost和下面的BeRecursive
,我用两个主要的方法解决了我的问题。
服务器端
下面的反序列化器需要引用System.Runtime.Serialization
并using System.Runtime.Serialization.Json
private T Deserialise<T>(string json) { using (var ms = new MemoryStream(Encoding.Unicode.GetBytes(json))) { var serialiser = new DataContractJsonSerializer(typeof(T)); return (T)serialiser.ReadObject(ms); } } public void Action(int id, string items){ IEnumerable<LineItem> lineitems = Deserialise<IEnumerable<LineItem>>(items); // do whatever needs to be done - create, update, delete etc. }
客户端
它使用json.org的stringify方法,可以在这个依赖https://github.com/douglascrockford/JSON-js/blob/master/json2.js (缩小时是2.5kb)
$.ajax({ type: 'POST', url: '/Controller/Action', data: { 'items': JSON.stringify(lineItems), 'id': documentId } });
看看菲尔·哈克的模型绑定JSON数据的post。 问题是默认的模型联编程序不能正确序列化JSON。 您需要某种ValueProvider,或者您可以编写一个自定义模型绑定器:
using System.IO; using System.Web.Script.Serialization; public class JsonModelBinder : DefaultModelBinder { public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { if(!IsJSONRequest(controllerContext)) { return base.BindModel(controllerContext, bindingContext); } // Get the JSON data that's been posted var request = controllerContext.HttpContext.Request; //in some setups there is something that already reads the input stream if content type = 'application/json', so seek to the begining request.InputStream.Seek(0, SeekOrigin.Begin); var jsonStringData = new StreamReader(request.InputStream).ReadToEnd(); // Use the built-in serializer to do the work for us return new JavaScriptSerializer() .Deserialize(jsonStringData, bindingContext.ModelMetadata.ModelType); // -- REQUIRES .NET4 // If you want to use the .NET4 version of this, change the target framework and uncomment the line below // and comment out the above return statement //return new JavaScriptSerializer().Deserialize(jsonStringData, bindingContext.ModelMetadata.ModelType); } private static bool IsJSONRequest(ControllerContext controllerContext) { var contentType = controllerContext.HttpContext.Request.ContentType; return contentType.Contains("application/json"); } } public static class JavaScriptSerializerExt { public static object Deserialize(this JavaScriptSerializer serializer, string input, Type objType) { var deserializerMethod = serializer.GetType().GetMethod("Deserialize", BindingFlags.NonPublic | BindingFlags.Static); // internal static method to do the work for us //Deserialize(this, input, null, this.RecursionLimit); return deserializerMethod.Invoke(serializer, new object[] { serializer, input, objType, serializer.RecursionLimit }); } }
并告诉MVC在你的Global.asax文件中使用它:
ModelBinders.Binders.DefaultBinder = new JsonModelBinder();
此外,此代码使用的内容types='应用程序/ JSON',所以请确保你在jQuery中设置如下所示:
$.ajax({ dataType: "json", contentType: "application/json", type: 'POST', url: '/Controller/Action', data: { 'items': JSON.stringify(lineItems), 'id': documentId } });
这样做的最简单的方法
我敦促你阅读这个博客文章 ,直接解决你的问题。
菲尔·哈克(Phil Haack)指出,使用自定义模型粘合剂并不是真正的明智之举(他的博客文章也链接在上面的博客文章中)。
基本上你有三个select:
-
编写
JsonValueProviderFactory
并使用像json2.js
这样的客户端库直接与JSON进行通信。 -
编写一个
JQueryValueProviderFactory
,了解在$.ajax
中发生的jQuery JSON对象转换或 -
使用博客文章中概述的非常简单快捷的jQuery插件,可以准备任何JSON对象(甚至是将绑定到
IList<T>
数组以及将在服务器端正确parsing为DateTime
实例的DateTime
) Asp.net MVC默认模型联编程序。
最后一个是最简单的,不会干扰Asp.net MVC的内部工作,从而降低可能的bug表面。 使用博客文章中概述的技术将正确的数据绑定您的强types操作参数,并validation它们。 所以基本上是双赢的局面。
在MVC3中,他们已经添加了这个。
但更好的是,因为MVC源代码是开放的,你可以抓住ValueProvider并在你自己的代码中使用它(如果你还没有使用MVC3)。
你会最终得到这样的东西
ValueProviderFactories.Factories.Add(new JsonValueProviderFactory())
你可以试试这些。 1.在通过ajax调用服务器动作之前,对您的JSON对象进行string化。在动作中反序列化string,然后将数据用作字典。
下面的JavaScript示例(发送JSON对象
$.ajax( { type: 'POST', url: 'TheAction', data: { 'data': JSON.stringify(theJSONObject) } })
下面的动作(C#)示例
[HttpPost] public JsonResult TheAction(string data) { string _jsonObject = data.Replace(@"\", string.Empty); var serializer = new System.Web.Script.Serialization.JavaScriptSerializer(); Dictionary<string, string> jsonObject = serializer.Deserialize<Dictionary<string, string>>(_jsonObject); return Json(new object{status = true}); }
我解决了这个问题后vestigal的提示:
我可以在web.config中设置maxJsonLength的无限长度吗?
当我需要在控制器中发布一个大的json动作的时候,我会得到着名的“使用JSON JavaScriptSerializer进行反序列化时的错误,string的长度超过了maxJsonLength属性设置的值。\ r \ n参数名称:input价值提供者“。
我所做的是创build一个新的ValueProviderFactory,LargeJsonValueProviderFactory,并在GetDeserializedObject方法中设置MaxJsonLength = Int32.MaxValue
public sealed class LargeJsonValueProviderFactory : ValueProviderFactory { private static void AddToBackingStore(LargeJsonValueProviderFactory.EntryLimitedDictionary backingStore, string prefix, object value) { IDictionary<string, object> dictionary = value as IDictionary<string, object>; if (dictionary != null) { foreach (KeyValuePair<string, object> keyValuePair in (IEnumerable<KeyValuePair<string, object>>) dictionary) LargeJsonValueProviderFactory.AddToBackingStore(backingStore, LargeJsonValueProviderFactory.MakePropertyKey(prefix, keyValuePair.Key), keyValuePair.Value); } else { IList list = value as IList; if (list != null) { for (int index = 0; index < list.Count; ++index) LargeJsonValueProviderFactory.AddToBackingStore(backingStore, LargeJsonValueProviderFactory.MakeArrayKey(prefix, index), list[index]); } else backingStore.Add(prefix, value); } } private static object GetDeserializedObject(ControllerContext controllerContext) { if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase)) return (object) null; string end = new StreamReader(controllerContext.HttpContext.Request.InputStream).ReadToEnd(); if (string.IsNullOrEmpty(end)) return (object) null; var serializer = new JavaScriptSerializer {MaxJsonLength = Int32.MaxValue}; return serializer.DeserializeObject(end); } /// <summary>Returns a JSON value-provider object for the specified controller context.</summary> /// <returns>A JSON value-provider object for the specified controller context.</returns> /// <param name="controllerContext">The controller context.</param> public override IValueProvider GetValueProvider(ControllerContext controllerContext) { if (controllerContext == null) throw new ArgumentNullException("controllerContext"); object deserializedObject = LargeJsonValueProviderFactory.GetDeserializedObject(controllerContext); if (deserializedObject == null) return (IValueProvider) null; Dictionary<string, object> dictionary = new Dictionary<string, object>((IEqualityComparer<string>) StringComparer.OrdinalIgnoreCase); LargeJsonValueProviderFactory.AddToBackingStore(new LargeJsonValueProviderFactory.EntryLimitedDictionary((IDictionary<string, object>) dictionary), string.Empty, deserializedObject); return (IValueProvider) new DictionaryValueProvider<object>((IDictionary<string, object>) dictionary, CultureInfo.CurrentCulture); } private static string MakeArrayKey(string prefix, int index) { return prefix + "[" + index.ToString((IFormatProvider) CultureInfo.InvariantCulture) + "]"; } private static string MakePropertyKey(string prefix, string propertyName) { if (!string.IsNullOrEmpty(prefix)) return prefix + "." + propertyName; return propertyName; } private class EntryLimitedDictionary { private static int _maximumDepth = LargeJsonValueProviderFactory.EntryLimitedDictionary.GetMaximumDepth(); private readonly IDictionary<string, object> _innerDictionary; private int _itemCount; public EntryLimitedDictionary(IDictionary<string, object> innerDictionary) { this._innerDictionary = innerDictionary; } public void Add(string key, object value) { if (++this._itemCount > LargeJsonValueProviderFactory.EntryLimitedDictionary._maximumDepth) throw new InvalidOperationException("JsonValueProviderFactory_RequestTooLarge"); this._innerDictionary.Add(key, value); } private static int GetMaximumDepth() { NameValueCollection appSettings = ConfigurationManager.AppSettings; if (appSettings != null) { string[] values = appSettings.GetValues("aspnet:MaxJsonDeserializerMembers"); int result; if (values != null && values.Length > 0 && int.TryParse(values[0], out result)) return result; } return 1000; } } }
然后,在Global.asax.cs的Application_Start方法中,将ValueProviderFactoryreplace为新的:
protected void Application_Start() { ... //Add LargeJsonValueProviderFactory ValueProviderFactory jsonFactory = null; foreach (var factory in ValueProviderFactories.Factories) { if (factory.GetType().FullName == "System.Web.Mvc.JsonValueProviderFactory") { jsonFactory = factory; break; } } if (jsonFactory != null) { ValueProviderFactories.Factories.Remove(jsonFactory); } var largeJsonValueProviderFactory = new LargeJsonValueProviderFactory(); ValueProviderFactories.Factories.Add(largeJsonValueProviderFactory); }
如果你有JSON数据作为string进入(例如'[{“id”:1,“name”:“Charles”},{“id”:8,“name”:“John”}, “ID”:13, “姓名”: “萨利”}]')
然后,我会使用JSON.net并使用Linq到JSON来获取值…
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using Newtonsoft.Json; using Newtonsoft.Json.Linq; public partial class _Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { if (Request["items"] != null) { var items = Request["items"].ToString(); // Get the JSON string JArray o = JArray.Parse(items); // It is an array so parse into a JArray var a = o.SelectToken("[0].name").ToString(); // Get the name value of the 1st object in the array // a == "Charles" } } }
BeRecursive的答案是我使用的,所以我们可以标准化Json.Net(我们有MVC5和WebApi 5 – WebApi 5已经使用Json.Net),但我发现一个问题。 当你的路由中有你要发布的参数时,MVC会尝试调用URI值的模型联编程序,这段代码将尝试将发布的JSON绑定到这些值。
例:
[HttpPost] [Route("Customer/{customerId:int}/Vehicle/{vehicleId:int}/Policy/Create"] public async Task<JsonNetResult> Create(int customerId, int vehicleId, PolicyRequest policyRequest)
BindModel
函数被调用三次,第一次轰炸,因为它试图绑定JSON到customerId
的错误: Error reading integer. Unexpected token: StartObject. Path '', line 1, position 1.
Error reading integer. Unexpected token: StartObject. Path '', line 1, position 1.
我将这段代码添加到BindModel
的顶部:
if (bindingContext.ValueProvider.GetValue(bindingContext.ModelName) != null) { return base.BindModel(controllerContext, bindingContext); }
幸运的是,ValueProvider的路由值是通过这个方法得出的。