Пользовательская модель 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, он работает. Надеюсь, что смогу помочь кому-то.