Проверка MVC и EF с добавленным элементом ValidationContext

у меня есть сценарий, где я хотел бы добавить элемент в ValidationContext и проверить его в проверке сущности, инициированной EF. Я делаю это в Мастере, поэтому я могу проверять только определенные вещи на определенных шагах. (Если есть хороший шаблон для этого, пожалуйста, поделитесь им).

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

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

в моем пользовательском контексте:

public WizardStep Step { get; set; }

protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
    items.Add("ValidationStep", Step);
    return base.ValidateEntity(entityEntry, items);
}

сервис, который устанавливает сущность:

public void SaveChanges(WizardStep step)
{
    _context.Step = step;
    _context.SaveChanges();
}

в своей сущности

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
    // Step will only be present when called from save changes.  Calls from model state validation won't have it
    if (validationContext.Items.ContainsKey("ValidationStep"))
    {
        var validationStep = (WizardStep)validationContext.Items["ValidationStep"];
        if (validationStep == WizardStep.Introduction)
        {
            if (criteria)
            {
                yield return new ValidationResult($"Error message  ", new[] { "field" });
            }
        }
    }
}

4 ответов


первая проверка выполняется на созданной модели MVC, которая передается контроллеру. MVC использует класс ModelBinder для создания, заполнения и проверки данных клиентской формы http в модели. Любая неудачная проверка будет возвращена клиенту. Допустимая модель может быть изменена контроллером, поэтому вторая проверка выполняется EF при сохранении. Я считаю, что при сохранении проверка EF запускается только в том случае, если свойство является новым или имеет другие данные значение.

теоретически должно быть возможно иметь пользовательский MVC ModelValidator и перехватить метод Validate для установки элементов ValidationContext. Однако я не мог понять, как это сделать. Однако я нашел немного другое решение, которое работает для меня. Возможно, его можно приспособить под ваши нужды.

в моем случае я хотел, чтобы EF DbContext (в моем коде его именованные CmsEntities) были доступны для методов проверки, чтобы я мог запросить базу данных (и сделать rich комплексная проверка бизнес-логики). Контроллер имеет DbContext, но проверка модели вызывается ModelBinder перед передачей его действию контроллера.

мое решение:

1) добавьте свойство DbContext в мой объект (используя частичный класс или базовый объект, от которого наследуют все объекты)

2) Создайте пользовательский ModelBinder, который получит DbContext от контроллера и заполнит его в model

3) зарегистрировать Пользовательский ModelBinder в Application_Start ()

теперь, внутри любого метода проверки, модель будет иметь заполненный DbContext. 

Пользовательский ModelBinder

public class CmsModelBinder : DefaultModelBinder
{
    protected override bool OnModelUpdating(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        // Copy CmsEntities from Controller to the Model (Before we update and validate the model)
        var modelPropertyInfo = bindingContext.Model.GetType().GetProperty("CmsEntities");
        if (modelPropertyInfo != null)
        {
            var controllerPropertyInfo = controllerContext.Controller.GetType().GetProperty("CmsEntities");
            if (controllerPropertyInfo != null)
            {
                CmsEntities cmsEntities = controllerPropertyInfo.GetValue(controllerContext.Controller) as CmsEntities;
                modelPropertyInfo.SetValue(bindingContext.Model, cmsEntities);
            }
        }            
        return base.OnModelUpdating(controllerContext, bindingContext);
    }

глобальные.асакс.cs

    protected void Application_Start()
    {
        ...
        ModelBinders.Binders.DefaultBinder = new CmsModelBinder();
    }

прежде всего, вы должны рассмотреть, принадлежит ли WizardStep контексту или объекту, который изменяется во время отдельных шагов? Другое дело, почему бы не использовать IE. Стратегия обработки логики проверки на отдельных шагах?

о проверке, я вижу, вы смешиваете 2 вещи.

один-проверка в контексте, где вы должны обрабатывать логику проверки для каждого типа сущности, который у вас есть в контексте.

другой-это реализация IValidatableObject.Проверьте, что должно вызываться автоматически для сущности в SaveChanges.

Я бы решил и выбрал один из способов пойти, и из информации, которую вы дали нам, я думаю, имея только IValidatableObject.Проверка имеет больше смысла, но тогда вам придется либо поместить шаг в объект, который проверяется, либо каким-то образом ввести этот шаг другим способом только для проверки.


вы можете сделать это таким образом:

try
{
  //write code

} 
catch (System.Data.Entity.Validation.DbEntityValidationException ex)
            {
                var outputLines = new List<string>();
                foreach (var eve in ex.EntityValidationErrors)
                {
                    outputLines.Add(string.Format(
                        "{0}: Entity of type \"{1}\" in state \"{2}\" has the following validation errors:",
                        DateTime.Now, eve.Entry.Entity.GetType().Name, eve.Entry.State));
                    foreach (var ve in eve.ValidationErrors)
                    {
                        outputLines.Add(string.Format(
                            "- Property: \"{0}\", Error: \"{1}\"",
                            ve.PropertyName, ve.ErrorMessage));
                    }
                }
                System.IO.File.AppendAllLines(@"c:\temp\errors.txt", outputLines);
            }

просто поделитесь моим решением для MVC validate:

public class TestController:Controller
{
    public ActionResult Action1(MyModel data)
    {
        try
        {
            if (!ModelState.IsValid)
            {
                var errors = ModelState.Values.Where(c => c.Errors.Count > 0).SelectMany(c => c.Errors.Select(o => o.ErrorMessage));
                var errorMsg = String.Join("<br/>", errors);
                return Json(new
                {
                    IsSuccess = false,
                    Message = errorMsg
                });
            }
            //deal business
            return Json(new { IsSuccess = true, Message = "success" });
        }
        catch (Exception ex)
        {
            return Json(new { IsSuccess = false, Message = "fail" });
        }
    }
}
public class MyModel : IValidatableObject
{
    [Required(ErrorMessage = "{0} is required")]
    public decimal TotalPrice { get; set; }
    [Required(ErrorMessage = "{0} is required")]
    public decimal TotalPriceWithoutCoupon { get; set; }
    public ContactStruct Contact { get; set; }
    public bool Condition{ get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        var instance = validationContext.ObjectInstance as MyModel;
        if (instance == null)
        {
            yield break;
        }
        if (instance.Condition)
        {
            if (instance.Contact == null)
            {
                yield return new ValidationResult("contact is required", new string[] { "Contact" });
            }
            else
            {
                if (string.IsNullOrEmpty(instance.Contact.phone))
                {
                    yield return new ValidationResult("the phone of contact is required", new string[] { "Contact.phone" });
                }
            }
        }
    }
}