开发者

MVC.net EF validation when using View Models

I'm working on an MVC application and i'm trying to implement some validation. I've strucuture the site to use EF for storage and a set of view models with automapper.

I want to add some validation which i'm sure would work if i added it to the View Models however i'm assuming it would be better to put validation in with the EF model so if in the future i create another interface the same validation would also apply.

First of is this the correct approach and second how do i get MVC to actually test the validation before saving the object. Currently it just skips my EF validation.

The address model is auto generated so i created this partial class to add the validation:

public partial class Address : IValidatableObject
{
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (!string.IsNullOrWhiteSpace(this.AddressLine1) &&
            !string.IsNullOrWhiteSpace(this.AddressLine2) &&
            !string.IsNullOrWhiteSpace(this.AddressLine3) &&
            !string.IsNullOrWhiteSpace(this.Town) &&
            !string.IsNullOrWhiteSpace(this.City) &&
            !string.IsNullOrWhiteSpace(this.County) &&
            !string.IsNullOrWhiteSpace(this.Postcode))
            yield return new ValidationResult("Address cannot be blank.");
    }
}

This is my view model class with the display names changed

public class AddressVM
{
开发者_开发问答    public int? ID { get; set; }

    [Display(Name = "Address line 1")]
    public string AddressLine1 { get; set; }

    [Display(Name = "Address line 2")]
    public string AddressLine2 { get; set; }

    [Display(Name = "Address line 3")]
    public string AddressLine3 { get; set; }

    [Display(Name = "Town")]
    public string Town { get; set; }

    [Display(Name = "City")]
    public string City { get; set; }

    [Display(Name = "County")]
    public string County { get; set; }

    [Display(Name = "Postcode")]
    public string PostCode { get; set; }
}

This is my controller

public ActionResult AddAddress(AddressVM vm)
{
    IncidentAddress theAddress = Mapper.Map<AddressVM, Address>(vm);
    if (ModelState.IsValid)
    {
        UOW.Addresses.Add(theAddress);
        UOW.Save();
    }
    return PartialView("AddressVM-edit", vm);
}


if (ModelState.IsValid)

This will always be true for your object, as it will look for validity of your model, which is AddressVM (you receive that from view so this is your model) and does not have any validators. ModelState does not know that you have mapped this object to some other which implements validation. You need to run validation on your other object manually and add validation errors to ModelState.

If you want to have this separated, you can implement IValidatableObject on AddressVM, and internally perform validation by creating a instance of Address, mapping it from AddressVM (this) and returning result of it's Validate method. You also can expose the same constructed Address object as a property and use it to perform entity operation.

Example of AddressVM:

public class AddressVM : IValidatableObject     
{ 
    public int? ID { get; set; } 

    [Display(Name = "Address line 1")] 
    public string AddressLine1 { get; set; } 

    [Display(Name = "Address line 2")] 
    public string AddressLine2 { get; set; } 

    [Display(Name = "Address line 3")] 
    public string AddressLine3 { get; set; } 

    [Display(Name = "Town")] 
    public string Town { get; set; } 

    [Display(Name = "City")] 
    public string City { get; set; } 

    [Display(Name = "County")] 
    public string County { get; set; } 

    [Display(Name = "Postcode")] 
    public string PostCode { get; set; }


    //// I added this and interface in class definition:

    public IncidentAddress GetIncidentAddress()
    { 
        return Mapper.Map<AddressVM, Address>(this);   
    }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)      
    {
        return this.GetIncidentAddress().Validate(validationContext);
    }
} 

This way your logic stays in your business object, and your viewmodel uses it without having copy of it or some other dependency.


The Address class and AddressVm are not bound to each other in your case - AutoMapper does not do validation stuff, it just copies values. So you do not get ModelState populated and validations performed.

There're two workarounds i'm thinking of

  1. Define the validations on AddressVm. If ModelState.IsValid, then map AddressVm to Address and save.
  2. You do not need AddressVm at all. Change Action signature to expect Address parameter. That way, ModelState.IsValid will be automatically populated by validation system (Not the best solution).

Ideally, ViewModels should be defined for specific scenarios. In your case, I would define AddAddressModel, use it only for adding addresses and define only the properties needed to create address. Then, define validations on AddAddressModel and use mapper to map ViewModel to Address instance (So, I prefer first solution, plus defining specific model).

If you need reusable validator classes, you could check out FluentValidation. It has good support of asp.net-mvc too.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜