Viewmodel Validation for a List

ViewModel validation for a List

If you are using Data Annotations to perform validation you might need a custom attribute:

public class EnsureOneElementAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
var list = value as IList;
if (list != null)
{
return list.Count > 0;
}
return false;
}
}

and then:

[EnsureOneElement(ErrorMessage = "At least a person is required")]
public List<Person> Persons { get; private set; }

or to make it more generic:

public class EnsureMinimumElementsAttribute : ValidationAttribute
{
private readonly int _minElements;
public EnsureMinimumElementsAttribute(int minElements)
{
_minElements = minElements;
}

public override bool IsValid(object value)
{
var list = value as IList;
if (list != null)
{
return list.Count >= _minElements;
}
return false;
}
}

and then:

[EnsureMinimumElements(1, ErrorMessage = "At least a person is required")]
public List<Person> Persons { get; private set; }

Personally I use FluentValidation.NET instead of Data Annotations to perform validation because I prefer the imperative validation logic instead of the declarative. I think it is more powerful. So my validation rule would simply look like this:

RuleFor(x => x.Persons)
.Must(x => x.Count > 0)
.WithMessage("At least a person is required");

Data validation for every item in a list of my ViewModel

There isn't really a way for Data Annotations to apply to the elements of a list. What you would have to do is create a wrapper class and apply the Data Annotation to the elements in the wrapper class, like so:

public IList<MyField> MyFields {get;set;}

public class MyField
{
[RegularExpression("MyRegex", ErrorMessageResourceName = "MyErrorMessage")]
public string Value
}

Usage:

@Html.TextBoxFor(model => model.MyFields[0].Value)

How to validate number of items in a list in mvc model

You can simply validate this in the controller:

public ActionResult TheAction(AssessorsViewModel model)
{
if (model.Assessors == null
|| model.Assessors.Count < 3
|| model.Assessors.Count > 7)
{
ModelState.AddModelError("Assessors", "Please enter note less than 3 and not more than 7 assessors.");
return View(model);
}
...
}

Another option would be to write a custom validation attribute. Here is an example of how to do this (the validator there is different, but the approach is clear).

validation on model or view model?

All validation needs to be done on the ViewModel.

In your case the RegisterViewModel is the data representation of the View associated public ActionResult Register(), which means, the view returns a RegisterViewModel object not a name object.

ModelState.IsValid checks validates the view's input [Register.cshtml] against the type it is bound to [RegisterViewModel not name]

So only the attributes applied to the RegisterViewModel will be Validated.

So you need to perform validation in the ViewModel.

This in-turn provides you additional flexibility of being able to use different validation rules for different ViewModels (though the ViewModels are bound to the same underlying Model)

EDIT: (with code suggestion)

No validation rules are applied to the Model properties

public class name {
public string first { get; set; }
public string middle { get; set; }
public string last { get; set; }
public string otherstuffnotneededontheview { get; set; }
}

They are applied in the ViewModel instead

public class RegisterViewModel {
[StringLength(50, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 3)]
public string fname { get; set; }
[StringLength(50, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 3)]
public string lname { get; set; }
}

This change should be pretty much enough (guessing you're properly binding RegisterViewModel to name)

Validation best practice for Model and ViewModel

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

ASP.NET MVC Validation in ViewModel or Model?

I highly recommend you use a view model. You may think it is redundant right now but I guarantee you that it is very useful and down the road you will thank me. I've been burned many times in the past trying to just use a model object everywhere and relying heavily on data annotations like yourself. Plus, you don't have to litter your model layer with view layer garbage such as [Display(Name="Module Name")]

In your case, I suggest this:

public class Module
{
[Key]
public int id { get; set; }

[Required]
[StringLength(100)]
[Column(TypeName = "varchar")]
public string ModuleName { get; set; }
}

public class ModuleViewModel
{
public int id { get; set; }

[Required]
[StringLength(30)]
[Display(Name="Module ID")]
public string ModuleID { get; set; }

[Required]
[StringLength(100)]
[Display(Name="Module Name")]
public string ModuleName { get; set; }

//To populate dropdownlist
public List<SelectListItem> ModuleLevelList { get; set; }

}

Best Practices ViewModel Validation in ASP.NET MVC

To answer your 3th question first: No there is no easier way then what you are doing. Two lines of code to get it working can hardly be easier. Although there is a plug-in you could use, like explained in the question unobtrusive validation not working with dynamic content

Your first question, how to centralize validation, I normally use a separate class file to store all my validation rules. This way I don't have to browse through every single class file to find the rules, but have them all in one place. If that's better, is matter of choice. The main reason I started to use it, is to be able to add validation to auto-generated classes, like classes from the Entity Framework.

So I have a file called ModelValidation.cs in my data layer, and have code for all my models like

/// <summary>
/// Validation rules for the <see cref="Test"/> object
/// </summary>
/// <remarks>
/// 2015-01-26: Created
/// </remarks>
[MetadataType(typeof(TestValidation))]
public partial class Test { }
public class TestValidation
{
/// <summary>Name is required</summary>
[Required]
[StringLength(100)]
public string Name { get; set; }

/// <summary>Text is multiline</summary>
[DataType(DataType.MultilineText)]
[AllowHtml]
public string Text { get; set; }
}

Now as you noticed I don't provide the actual error message. I use conventions by Haacked to add the messages. It makes it simple to add localized validation rules.

It basically comes down to a recource file containing something like:

Test_Name = "Provide name"
Test_Name_Required = "Name is required"

And these messages and naming will be used when you call regular MVC view code like

<div class="editor-container">
<div class="editor-label">
@Html.LabelFor(model => model.Name) <!--"Provide name"-->
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Name)
@Html.ValidationMessageFor(model => model.Name) <!--"Name is required"-->
</div>
</div>

Your second question, about different validation for add/edit can be handled in two ways. The best way, would be to use views as they are actually intended. That means you don't pass your actual models to the views, but you create a view model that contains only the data. So you have a view model for Create with the proper validation rules and a view model for Edit with the proper rules, and when they pass you insert the result in your actual model.
This however requires a lot more code and manual work, so I can imagine you're not really willing to do it like this.

Another option would be to use conditional validation like explained by viperguynaz. Now instead of a boolean, my classes that require a change between edit/add have a primary key Id int. So I check if Id>0 to determine if it is an edit or not.

UPDATE:

If you want to update validation on every ajax call, you could use jQuery ajaxComplete. This will revalidate all forms after every ajax request.

$( document ).ajaxComplete(function() {
$('form').each(function() {
var $el = $(this);
$el.data('validator', null);
$.validator.unobtrusive.parse($el);
})
});

If this is something you want, depends on how often you receive a form via AJAX. If you have a lot of AJAX request, like polling a status every 10seconds, than you don't want this. If you have an occasional AJAX request, that mostly contains a form, then you could use it.

If your AJAX returns a form you want to validate, then yes, it is good practise to update the validation. But I guess a better question would be "Do I really need to send the form by AJAX?"
AJAX is fun and useful, but it should be used with care and thought.



Related Topics



Leave a reply



Submit