14

I have separate model and viewmodel classes. Where viewmodel classes only do UI level validation (refer: Validation: Model or ViewModel).

I can verify on post action in a controller that model (vewmodel) is valid.

The ask: How do I validate the model (the main entity with data annotations).

I am not developing the viewmodel using model object. Just duplicating the properties and adding all properties possibly required in that particular view.

//Model Class
public class User
{
    [Required]
    public string Email {get; set;}

    [Required]
    public DateTime Created {get; set;}
}

//ViewModel Class
public class UserViewModel
{
    [Required]
    public string Email {get; set;}

    [Required]
    public string LivesIn {get; set;}
}

//Post action
public ActionResult(UserViewModel uvm)
{
    if( ModelState.IsValid)
        //means user entered data correctly and is validated

    User u = new User() {Email = uvm.Email, Created = DateTime.Now};
    //How do I validate "u"?

    return View();
}

Should do something like this:

var results = new List<ValidationResult>();
var context = new ValidationContext(u, null, null);
var r = Validator.TryValidateObject(u, context, results);

What I am thinking is adding this validation technique in the base class (of business entity), and verify it when I am mapping from viewmodel class to business entity.

Any suggestions?

Community
  • 1
  • 1
Yahya
  • 3,386
  • 3
  • 22
  • 40

2 Answers2

11

1) Use fluent validation on the model that the retrieves information from the user. it is more flexible then data annotation and easier to test.

2) You might want to look into automapper, by using automapper you don't have to write x.name = y.name.

3) For your database model I would stick to the data-annotations.

Everything below is based on the new information

First and all you should place validation on both location like you did now for the actual model validation this is how I would do it. Disclaimer: this is not the perfect way

First and all update the UserViewModel to

public class UserViewModel
    {
        [Required()]
        [RegularExpression(@"^(([A-Za-z0-9]+_+)|([A-Za-z0-9]+\-+)|([A-Za-z0-9]+\.+)|([A-Za-z0-9]+\++))*[A-Za-z0-9]+@((\w+\-+)|(\w+\.))*\w{1,63}\.[a-zA-Z]{2,6}$")]
        public String Email { get; set; }
    }

Then update the action method to

        // Post action
        [HttpPost]
        public ActionResult register (UserViewModel uvm)
        {
            // This validates the UserViewModel
            if (ModelState.IsValid)
            {

                try
                {
                    // You should delegate this task to a service but to keep it simple we do it here
                    User u = new User() { Email = uvm.Email, Created = DateTime.Now };
                    RedirectToAction("Index"); // On success you go to other page right?
                }
                catch (Exception x)
                {
                    ModelState.AddModelError("RegistrationError", x); // Replace x with your error message
                }

            }       

            // Return your UserViewModel to the view if something happened               
            return View(uvm);
        }

Now for the user model it gets tricky and you have many possible solutions. The solution I came up with (probably not the best) is the following:

public class User
    {
        private string email;
        private DateTime created;

        public string Email
        {
            get
            {
                return email;
            }
            set
            {
                email = ValidateEmail(value);
            }
        }

        private string ValidateEmail(string value)
        {
            if (!validEmail(value))
                throw new NotSupportedException("Not a valid email address");     

            return value;
        }

        private bool validEmail(string value)
        {
            return Regex.IsMatch(value, @"^(([A-Za-z0-9]+_+)|([A-Za-z0-9]+\-+)|([A-Za-z0-9]+\.+)|([A-Za-z0-9]+\++))*[A-Za-z0-9]+@((\w+\-+)|(\w+\.))*\w{1,63}\.[a-zA-Z]{2,6}$");
        }

Last some unit test to check my own code:

   [TestClass()]
    public class UserTest
    {

        /// <summary>
        /// If the email is valid it is stored in the private container
        /// </summary>
        [TestMethod()]
        public void UserEmailGetsValidated()
        {
            User x = new User();
            x.Email = "test@test.com";
            Assert.AreEqual("test@test.com", x.Email);
        }

        /// <summary>
        /// If the email is invalid it is not stored and an error is thrown in this application
        /// </summary>
        [TestMethod()]
        [ExpectedException(typeof(NotSupportedException))]
        public void UserEmailPropertyThrowsErrorWhenInvalidEmail()    
       {
           User x = new User();
           x.Email = "blah blah blah";
           Assert.AreNotEqual("blah blah blah", x.Email);
       }


        /// <summary>
        /// Clears an assumption that on object creation the email is validated when its set
        /// </summary>
        [TestMethod()]
        public void UserGetsValidatedOnConstructionOfObject()
        {
            User x = new User() { Email = "test@test.com" };
            x.Email = "test@test.com";
            Assert.AreEqual("test@test.com", x.Email);
        }
    }
SharpC
  • 6,974
  • 4
  • 45
  • 40
David
  • 4,185
  • 2
  • 27
  • 46
  • prd @Serghei I actually wanted to know how do I validate the model class (which is not bound to view). Keeping that my Views have properties from different model classes (in a ViewModel class) to fulfill all requirements on that particular View. – Yahya Jun 27 '11 at 13:11
  • @Yahya Can you post an example? it will be easier to point out where you should, and how, to do the validation. – David Jun 27 '11 at 13:59
  • prd I have added sample code in the original question for you. I hope it makes sense now. – Yahya Jun 27 '11 at 14:18
  • @Yahya i updated my post with a possible solution however its not the best one – David Jun 27 '11 at 17:17
  • prd Thanks for the solution. Unfortunately this doesn't look very tidy. For now, as my business entities are not mission critical, I am just *living* with no validation on them. Doing all the validation on viewmodels. For mapping, I took your advice and am using Automapper. – Yahya Jun 29 '11 at 15:42
3

I think is better to use Data Annotations look at this

ASP.NET MVC Valdation

and for server side validation you can use the fluent validation

and look at this question

Community
  • 1
  • 1
Sergey K
  • 4,071
  • 2
  • 23
  • 34