Validating MVC and EF with ValidationContext Added

I have a scenario in which I would like to add an element to a ValidationContext and test it in validating an EF-initiated entity. I do this in the wizard, so I can only check certain things at certain steps. (If there is a good template for this, please share it).

The problem is that validation is triggered, actually twice, before the controllerโ€™s action even hits. I would like to understand why. I am not sure how to get the element in the ValidationContext before this happens, so I canโ€™t verify the correct stage at which I am.

In addition, if I perform only selective verification, when changes are saved, checking the element, as I have in my code below, then I do not get automatic model verification errors displayed when the page is refreshed.

In my user context:

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

The service that installs the object:

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

In my essence

 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" }); } } } } 

Controller:

 public ActionResult MyAction(HomeViewModel vm) { try { _incidentService.AddOrUpdate(vm.Enttiy); _incidentService.SaveChanges(WizardStep.Introduction); } catch (Exception ex) { return View(vm); } return RedirectToAction("Index"); } 
+8
c # validation asp.net-mvc entity-framework
source share
4 answers

The first check is performed on the created MVC model, which is passed to the controller. MVC uses the ModelBinder class to build, populate, and validate client http form data in the model. Any failed validation will be returned to the client. After that, the correct model can be changed by the controller, so the second check is performed by EF when saving. I believe that when saved, the EF check is only triggered if the property is new or has different data with the original value.

In theory, it should be possible to have a custom MVV ModelValidator and intercept the Validate method to set the ValidationContext elements. However, I could not figure out how to do this. However, I found a slightly different solution that works for me. Perhaps it can be adapted to your needs.

In my case, I wanted EF DbContext (in my code its CmsEntities) to be available for validation methods, so I can query the database (and do a thorough business logic check). The controller has a DbContext, but the model check is called by ModelBinder before passing it to the controllers.

My decision:

1) Add the DbContext property to your Entity (using a partial class or in a base object from which all inherited objects are)

2) Create a custom ModelBinder that will output the DbContext from the controller and put it into the model

3) Register your custom ModelBinder in Application_Start ()

Now, inside any validation method, the model will have a filled DbContext. ๏Š

Custom 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); } 

Global.asax.cs

  protected void Application_Start() { ... ModelBinders.Binders.DefaultBinder = new CmsModelBinder(); } 
+1
source share

First of all, you should consider whether the WizardStep belongs to a context or to an object that has been changed in the course of individual steps? Another thing, why not use that is. Strategy for handling validation logic in separate steps?

On checking, I see you are mixing 2 things.

One is validation in a context where you must handle the validation logic for each entity type that you have in context.

Another is the implementation of IValidatableObject.Validate, which should be called automatically for an object in SaveChanges.

I would decide and choose one of the methods, and from the information that you gave us, I think that having only IValidatableObject.Validate makes more sense, but then you have to either put a step in the entity that will be checked, or in some way enter this step in another way just for verification.

0
source share

You can do it as follows:

 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); } 
0
source share

Just submit my mvc verification solution:

 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" }); } } } } } 
0
source share

All Articles