How to save validation rules inside an object when using a view model inside a controller?

So, I have an entity in my Models directory:

 public class Event { public int Id { get; set; } [Required, MaxLength(50), MinLength(3)] public string Name { get; set; } [Required, MaxLength(2000)] public string Description { get; set; } } 

and I want to open it for viewing using viewModel:

 public class BaseEventViewModel { public string Name { get; set; } [DataType(DataType.MultilineText)] public string Description { get; set; } } public class EventCreateViewModel : BaseEventViewModel { } 

My reasoning is that I want all data validation to be performed on entities, and all presentation materials (e.g., rendering of a text area) should be performed in a presentation model. Then I can use, however, many representation models that I want to represent as my entity, while preserving data integrity.

So, I changed my controller to use the new view model:

  [HttpPost] [ValidateAntiForgeryToken] public ActionResult Create(EventCreateViewModel viewModel) { if (ModelState.IsValid) { db.Events.Add(new Event { Name = viewModel.Name, Description = viewModel.Description }); db.SaveChanges(); return RedirectToAction("Index"); } return View(viewModel); } 

However, none of the entity checks are performed, and I can submit a blank form that throws a DbEntityValidationException .

Presumably this is because ModelState.IsValid working on a view model, not an object that represents a view model. How can I catch these validation errors?

+5
source share
4 answers

I really found the answer after it was pushed in the right direction. If I add this annotation to my view model, it inherits all the annotations applied to the properties of my object:

 [MetadataType(typeof(Event))] public class BaseEventViewModel { public int Id { get; set; } public string Name { get; set; } [DataType(DataType.MultilineText)] public string Description { get; set; } } 

Now when I submit an empty form, they show me validation errors as usual.

This is due to a caution about having to redefine each property again in my view model, which leads to the point that the view model only supports the required properties, however it works for my case.

+4
source

A model is all that is passed through the parameters of the action method. Sorry - the only way to achieve this is to add data annotations also in ViewModels .

EDIT: it can be added at runtime with reflection (therefore, changes to the Entity check can be automatically β€œvisible” on ViewModels ) - however this works a lot. You would need to inherit from DataAnnotationsModelValidatorProvider , get all the attributes from the corresponding properties of the Entity class and add them to viewmodels. I think the best way is to write unit tests for Entity and ViewModels validation rules (the same attributes that are added to the fields in Entity and ViewModel ) to avoid errors in the validators.

The second good way (and the fastest) to solve this problem is to use the AOP framework, such as PostSharp . Create an aspect like: EntityNameValidatorAspect (to add data annotations for the property with the corresponding attribute values). Then you add this aspect ( [EntityNameValidatorAspect] ) before the name property in Entity and ViewModel and so on. This is an analogy to refactoring repeated code for a method - you simply "reorganize" several common attributes into one.

+1
source

In this case, your validation rules should be in the view model. The work you do is input user validation data, and that is what you use to get input.

You may have a separate verification step that is performed for an object before it is written to the database. In this case, you should use a separate validation mechanism to process the rules for the object.

0
source

I use this awesome nuget that makes ExpressiveAnnotations dynamic annotations using a similar AOP template

You can check out any logic you can dream of:

 public string Email { get; set; } public string Phone { get; set; } [RequiredIf("Email != null")] [RequiredIf("Phone != null")] [AssertThat("AgreeToContact == true")] public bool? AgreeToContact { get; set; } 
0
source

Source: https://habr.com/ru/post/1211951/


All Articles