Подтвердить что ты не робот

MVC 3 не связывает длину с нулевым значением

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

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

4b9b3361

Ответ 1

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

Это делается в специальном связующем устройстве. В качестве примера я использовал публикацию 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 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");
    }
}

Затем поместите это в Global:

ModelBinders.Binders.DefaultBinder = new JsonModelBinder();

Теперь длинный привязывается успешно. Я бы назвал это ошибкой в ​​JavaScriptSerializer. Также обратите внимание, что массивы длинных или нулевых длин привязываются просто отлично без кавычек.

Ответ 2

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

Если вы не хотите идти с 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);
  }
}

Ответ 4

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

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

Ответ 5

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