MVC 3不绑定可空长

时间:2011-05-19 15:44:17

标签: c# asp.net-mvc-3 nullable model-binding

我创建了一个测试网站来调试我遇到的问题,看来我要么传入JSON数据错误,要么MVC无法绑定可空的长片。我当然正在使用最新的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 }

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

5 个答案:

答案 0 :(得分:4)

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

<script type="text/javascript">
    $(function () {
        $.post('Home/GetData', { "TestString": "test", "TestLong": 12345, "TestInt": 123 });
    });
</script>

我在GetData方法中放了一个断点,并且这些值与它们应该绑定到模型:

enter image description here

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

答案 1 :(得分:3)

如果您不想使用正则表达式并且只关心修复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);
  }
}

答案 2 :(得分:2)

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

这是在自定义模型绑定器中完成的。我以Posting JSON Data to 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}(?<num>[\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的数组在没有引号的情况下被绑定。

答案 3 :(得分:2)

您可以使用此模型活页夹类

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());

答案 4 :(得分:1)

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

public sealed class NumericJsonValueProviderFactory : ValueProviderFactory
{

    private static void AddToBackingStore(Dictionary<string, object> backingStore, string prefix, object value)
    {
        IDictionary<string, object> d = value as IDictionary<string, object>;
        if (d != null)
        {
            foreach (KeyValuePair<string, object> 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}(?<num>[\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<string, object> backingStore = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
        AddToBackingStore(backingStore, String.Empty, jsonData);
        return new DictionaryValueProvider<object>(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<JsonValueProviderFactory>().FirstOrDefault());
ValueProviderFactories.Factories.Add(new NumericJsonValueProviderFactory());