MVC 3不绑定可空长

我创建了一个测试网站来调试我遇到的问题,看起来我要么传递JSON数据错误,要么MVC无法绑定可空的longs。 当然,我正在使用最新的MVC 3版本。

public class GetDataModel { public string TestString { get; set; } public long? TestLong { get; set; } public int? TestInt { get; set; } } [HttpPost] public ActionResult GetData(GetDataModel model) { // Do stuff } 

我发布了一个具有正确JSON内容类型的JSON字符串:

 { "TestString":"test", "TestLong":12345, "TestInt":123 } 

长期没有约束,它总是空的。 如果我把这个值放在引号中,它就可以工作,但我不应该这样做,我应该吗? 我需要为该值设置自定义模型绑定器吗?

我创建了一个testproject来测试它。 我把你的代码放到我的HomeController中并将其添加到index.cshtml:

  

我在GetData方法中放置了一个断点,并将值绑定到模型中,就像它们应该:

在此处输入图像描述

所以我认为你发送价值的方式有问题。 您确定“TestLong”值实际上是通过线路发送的吗? 您可以使用Fiddler进行检查。

如果你不想使用正则表达式而你只关心修复多long? ,以下也将解决问题:

 public class JsonModelBinder : DefaultModelBinder { public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder) { var propertyType = propertyDescriptor.PropertyType; if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) { var provider = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); if (provider != null && provider.RawValue != null && Type.GetTypeCode(provider.RawValue.GetType()) == TypeCode.Int32) { var value = new System.Web.Script.Serialization.JavaScriptSerializer().Deserialize(provider.AttemptedValue, bindingContext.ModelMetadata.ModelType); return value; } } return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder); } } 

我的同事为此提出了一个解决方法。 解决方案是获取输入流并使用正则表达式将所有数字变量包装在引号中,以欺骗JavaScriptSerializer正确地反序列化long。 这不是一个完美的解决方案,但它会解决这个问题。

这是在自定义模型绑定器中完成的。 我将Posting JSON Data用于ASP.NET MVC作为示例。 但是,如果在其他任何地方访问输入流,则必须小心。

 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 jsonStringData = new StreamReader(controllerContext.HttpContext.Request.InputStream).ReadToEnd(); // Wrap numerics jsonStringData = Regex.Replace(jsonStringData, @"(?<=:)\s{0,4}(?[\d\.]+)\s{0,4}(?=[,|\]|\}]+)", "\"${num}\""); // Use the built-in serializer to do the work for us 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"); } } 

然后把它放在全球:

 ModelBinders.Binders.DefaultBinder = new JsonModelBinder(); 

现在长期成功绑定。 我会称这是JavaScriptSerializer中的一个错误。 还要注意,long或nullable longs的数组在没有引号的情况下绑定得很好。

您可以使用此模型绑定器类

 public class LongModelBinder : IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { var valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); if (string.IsNullOrEmpty(valueResult.AttemptedValue)) { return (long?)null; } var modelState = new ModelState { Value = valueResult }; object actualValue = null; try { actualValue = Convert.ToInt64( valueResult.AttemptedValue, CultureInfo.InvariantCulture ); } catch (FormatException e) { modelState.Errors.Add(e); } bindingContext.ModelState.Add(bindingContext.ModelName, modelState); return actualValue; } } 

在Global.asax Application_Start中添加这些行

 ModelBinders.Binders.Add(typeof(long), new LongModelBinder()); ModelBinders.Binders.Add(typeof(long?), new LongModelBinder()); 

我想要整合Edgar提供的解决方案,但仍然具有DefaultModelBinder的function。 因此,我没有创建一个新的模型绑定器,而是采用了不同的方法,并将JsonValueProviderFactory替换为JsonValueProviderFactory定义方法。 原始MVC3源代码中的代码只有一个小的变化:

 public sealed class NumericJsonValueProviderFactory : ValueProviderFactory { private static void AddToBackingStore(Dictionary backingStore, string prefix, object value) { IDictionary d = value as IDictionary; if (d != null) { foreach (KeyValuePair entry in d) { AddToBackingStore(backingStore, MakePropertyKey(prefix, entry.Key), entry.Value); } return; } IList l = value as IList; if (l != null) { for (int i = 0; i < l.Count; i++) { AddToBackingStore(backingStore, MakeArrayKey(prefix, i), l[i]); } return; } // primitive backingStore[prefix] = value; } private static object GetDeserializedObject(ControllerContext controllerContext) { if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase)) { // not JSON request return null; } StreamReader reader = new StreamReader(controllerContext.HttpContext.Request.InputStream); string bodyText = reader.ReadToEnd(); if (String.IsNullOrEmpty(bodyText)) { // no JSON data return null; } JavaScriptSerializer serializer = new JavaScriptSerializer(); // below is the code that Edgar proposed and the only change to original source code bodyText = Regex.Replace(bodyText, @"(?<=:)\s{0,4}(?[\d\.]+)\s{0,4}(?=[,|\]|\}]+)", "\"${num}\""); object jsonData = serializer.DeserializeObject(bodyText); return jsonData; } public override IValueProvider GetValueProvider(ControllerContext controllerContext) { if (controllerContext == null) { throw new ArgumentNullException("controllerContext"); } object jsonData = GetDeserializedObject(controllerContext); if (jsonData == null) { return null; } Dictionary backingStore = new Dictionary(StringComparer.OrdinalIgnoreCase); AddToBackingStore(backingStore, String.Empty, jsonData); return new DictionaryValueProvider(backingStore, CultureInfo.CurrentCulture); } private static string MakeArrayKey(string prefix, int index) { return prefix + "[" + index.ToString(CultureInfo.InvariantCulture) + "]"; } private static string MakePropertyKey(string prefix, string propertyName) { return (String.IsNullOrEmpty(prefix)) ? propertyName : prefix + "." + propertyName; } } 

然后,要注册新的值提供程序,您需要将以下行添加到Global.asax:

 ValueProviderFactories.Factories.Remove(ValueProviderFactories.Factories.OfType().FirstOrDefault()); ValueProviderFactories.Factories.Add(new NumericJsonValueProviderFactory());