Пользовательская модель Binder, не проверяющая модель
Я начал играть с knockout.js, и при этом я использовал FromJsonAttribute (созданный Стивом Сандерсоном). Я столкнулся с проблемой, когда пользовательский атрибут не выполнил проверку модели. Я собрал простой пример - я знаю, что это похоже на много кода, но основная проблема заключается в том, как заставить валидацию модели в рамках настраиваемого связующего объекта.
using System.ComponentModel.DataAnnotations;
namespace BindingExamples.Models
{
public class Widget
{
[Required]
public string Name { get; set; }
}
}
и вот мой контроллер:
using System;
using System.Web.Mvc;
using BindingExamples.Models;
namespace BindingExamples.Controllers
{
public class WidgetController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(Widget w)
{
if(this.ModelState.IsValid)
{
TempData["message"] = String.Format("Thanks for inserting {0}", w.Name);
return RedirectToAction("Confirmation");
}
return View(w);
}
[HttpPost]
public ActionResult PostJson([koListEditor.FromJson] Widget w)
{
//the ModelState.IsValid even though the widget has an empty Name
if (this.ModelState.IsValid)
{
TempData["message"] = String.Format("Thanks for inserting {0}", w.Name);
return RedirectToAction("Confirmation");
}
return View(w);
}
public ActionResult Confirmation()
{
return View();
}
}
}
Моя проблема в том, что модель всегда действительна в моем методе PostJson. Для полноты здесь приведен код Sanderson для атрибута FromJson:
using System.Web.Mvc;
using System.Web.Script.Serialization;
namespace koListEditor
{
public class FromJsonAttribute : CustomModelBinderAttribute
{
private readonly static JavaScriptSerializer serializer = new JavaScriptSerializer();
public override IModelBinder GetBinder()
{
return new JsonModelBinder();
}
private class JsonModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var stringified = controllerContext.HttpContext.Request[bindingContext.ModelName];
if (string.IsNullOrEmpty(stringified))
return null;
var model = serializer.Deserialize(stringified, bindingContext.ModelType);
return model;
}
}
}
}
Ответы
Ответ 1
Описание
FromJsonAttribute
привязывается только к модели и, как вы сказали, не имеет подтверждения.
Вы можете добавить подтверждение к FromJsonAttribute
, чтобы проверить модель на соответствие его атрибутам DataAnnotations.
Это можно сделать с помощью класса TypeDescriptor
.
TypeDescriptor Предоставляет информацию о характеристиках компонента, таких как его атрибуты, свойства и события.
Проверьте мое решение. Я протестировал его.
Решение
private class JsonModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var stringified = controllerContext.HttpContext.Request[bindingContext.ModelName];
if (string.IsNullOrEmpty(stringified))
return null;
var model = serializer.Deserialize(stringified, bindingContext.ModelType);
// DataAnnotation Validation
var validationResult = from prop in TypeDescriptor.GetProperties(model).Cast<PropertyDescriptor>()
from attribute in prop.Attributes.OfType<ValidationAttribute>()
where !attribute.IsValid(prop.GetValue(model))
select new { Propertie = prop.Name, ErrorMessage = attribute.FormatErrorMessage(string.Empty) };
// Add the ValidationResult to the ModelState
foreach (var validationResultItem in validationResult)
bindingContext.ModelState.AddModelError(validationResultItem.Propertie, validationResultItem.ErrorMessage);
return model;
}
}
Дополнительная информация
Ответ 2
Спасибо, спасибо, dknaack!! Ваш ответ был именно тем, что я искал, за исключением того, что я хочу проверить после привязки каждого свойства. B/c У меня есть свойства, зависящие от других свойств, и я не хочу продолжать связывать, если зависимое свойство недействительно.
Здесь моя новая перегрузка BindProperty:
protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor){
// if this is a simple property, bind it and return
if(_simplePropertyKeys.ContainsKey(propertyDescriptor.Name)){
this.BindSimpleProperty(bindingContext, propertyDescriptor);
// if this is complex property, only bind it if we don't have an error already
} else if (bindingContext.ModelState.IsValid){
this.BindComplexProperty(bindingContext, propertyDescriptor);
}
// add errors from the data annotations
propertyDescriptor.Attributes.OfType<ValidationAttribute>()
.Where(a => a.IsValid(propertyDescriptor.GetValue(bindingContext.Model)) == false)
.ForEach(r => bindingContext.ModelState.AddModelError(propertyDescriptor.Name, r.ErrorMessage));
}
Ответ 3
Прежде всего, я только начинаю изучать ASP.NET, поэтому не принимаю мое решение всерьез. Я нашел эту статью и, как вы, попытался сделать собственное связующее устройство. Не было подтверждения. Затем я только что заменил интерфейс IModelBinder с помощью DefaultModelBinder и voula, он работает. Надеюсь, что смогу помочь кому-то.