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 }
Длинные не связаны, он всегда равен нулю. Он работает, если я ставлю значение в кавычки, но мне не нужно это делать, должен ли я? Должен ли я иметь настраиваемое связующее устройство для этого значения?
Ответы
Ответ 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());