MVC 3 долго не связывается - PullRequest
12 голосов
/ 19 мая 2011

Я создал тестовый веб-сайт для отладки возникшей у меня проблемы, и кажется, что либо я неверно передаю данные JSON, либо MVC просто не может связывать обнуляемые значения long.Конечно, я использую последнюю версию 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 }

Длинное несвязано, это всегда ноль.Это работает, если я помещаю значение в кавычки, но я не должен был этого делать, не так ли?Нужно ли иметь привязку пользовательской модели для этого значения?

Ответы [ 5 ]

4 голосов
/ 19 мая 2011

Я создал тестовый проект, чтобы проверить это.Я поместил ваш код в мой 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 .

3 голосов
/ 02 февраля 2012

Если вы не хотите использовать Regex и вам нужно только исправить 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 голосов
/ 07 февраля 2014

Вы можете использовать эту модель связующего класса

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());
2 голосов
/ 23 мая 2011

Мой коллега придумал обходной путь для этого. Решение состоит в том, чтобы взять входной поток и использовать Regex, чтобы обернуть все числовые переменные в кавычки, чтобы заставить JavaScriptSerializer правильно десериализовать длинные. Это не идеальное решение, но оно решает проблему.

Это делается в пользовательском связывателе модели. Я использовал Отправка данных JSON в 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 long легко связываются без кавычек.

1 голос
/ 03 ноября 2011

Я хотел включить решение, представленное Эдгаром, но все еще обладать функциями 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());
...