Validate Model Programmatically in ASP.NET MVC
ASP.NET model binding framework takes care of validating a model based
on data annotation validations. This works well when a model is being bound with
request data. However, at times you may need to create and fill a model
programmatically. In such cases, although the model properties are decorated
with data annotation validators, they won't validate the data because they are
not invoked at all. Consider a situation wherein users are uploading CSV files
from the client machine on to the server. Your application is supposed to read
those files and assign the values from the file to the model properties. The
model objects are then stored to some database. In this case data validations
won't fire and your model will be held in an invalid state. Since model
validations are not being invoked ModelState.IsValid will always return true and
there won't be any easy way to detect and display these validation failures.
Luckily, ASP.NET MVC allows you to validate a model object via code. This
article shows how.
Let's assume that you have a UserInfo model class as shown below:
public class UserInfo
{
[Required]
[StringLength(100, MinimumLength = 10)]
public string FirstName { get; set; }
[Required]
[StringLength(100, MinimumLength = 10)]
public string LastName { get; set; }
[Required]
public DateTime BirthDate { get; set; }
}
The UserInfo class consists of three properties - FirstName, LastName and
BirthDate. All the model properties are decorated with [Required] attribute.
Additionally, FirstName and LastName properties are decorated with [StringLength]
attribute. Both of these properties also have MinimumLength set to 10 just for
the sake of testing.
Now consider a view (Index.cshtml) as shown below:

The Index view shown above consists of a file upload control and a button.
You are supposed to select a CSV file that contains data suitable for UserInfo
model. Upon selecting a CSV file and clicking the Upload button success or error
message will be displayed in another view (UploadResult.cshtml). A sample CSV
file would look like this:
Nancy,Davolio,1/2/1960
The HTML markup that makes Index.cshtml is shown below:
<h1>Select file to be uploaded:</h1>
<form action="/home/savedata" method="post"
enctype="multipart/form-data">
<input type="file" name="file1" />
<input type="submit" value="Upload" />
</form>
The <form> posts the file to SaveData() action method. The SaveData() action
does the job of reading the uploaded file, instantiating a model object, setting
model properties and then displaying UploadResult view with a success or error
message. For example, the following figure shows UploadResult view when the
UserInfo model is in invalid state:

Notice that the UploadResult view displays validation errors through
ValidationSummary() helper.
Now let's see the most important part of the application - SaveData() action
method.
public ActionResult SaveData()
{
if(Request.Files.Count>0)
{
HttpFileCollectionBase files = Request.Files;
foreach(string key in Request.Files)
{
HttpPostedFileBase item = Request.Files[key];
byte[] data = new byte[item.InputStream.Length];
int byteCount = item.InputStream.Read(data, 0, data.Length);
string strData = ASCIIEncoding.UTF8.GetString(data).Trim();
string[] values = strData.Split(',');
UserInfo info = new UserInfo();
info.FirstName = values[0];
info.LastName = values[1];
info.BirthDate = DateTime.Parse(values[2]);
if(TryValidateModel(info))
{
//save in database here
ViewBag.Message = "User information is
validated successfully!";
}
else
{
ViewBag.Message = "Error while validating user info.
Please review the errors below
and upload the file again!";
}
return View("UploadResult", info);
}
}
return View("Index");
}
The SaveData() action iterates through the Request.Files collection and gets
hold of the file being uploaded. In our example it is assumed that only one will
be uploaded. You don't need to save this file on the server because your
interest is in the CSV data in the file. That's why the code reads the content
of InputStream of the file using Read() method. The Read() method reads the
content in a byte array. This byte array is converted into a string using
GetString() method of the ASCIIEncoding class. The string data is further split
into a string array using Split() method.
Notice the code marked in bold letters. The code creates a UserInfo object
and assigns FirstName, LastName and BirthDate properties from the string array
created earlier. If you see the sample data shown earlier you will realize that
FirstName and LastName doesn't meet the requirement of [StringLength] attribute.
However, at this stage ModelState.IsValid will be true because data validations
are not invoked yet.

Next, the code uses TryValidateModel() method that invokes the data
validations. There are two variations of this method - ValidateModel()
and TryValidateModel(). Both of them come from the
Controller base class. The former method throws an exception if there are any
validation errors whereas the later method silently returns false in case
validations fail. If you check ModelState.IsValid after the call to
TryValidateModel() you will see that it returns false because there are
validation errors.

The SaveData() action sends UserInfo model object to the UploadResult view so
that validation errors can be displayed. The UploadResult view contains this
HTML markup:
@model StProcInCodeFirstDemo.Models.UserInfo
...
<body>
@ViewBag.Message
<br /><br />
@Html.ValidationSummary()
<br /><br />
@Html.ActionLink("Upload again!","Index")
</body>
...
Notice that UploadResult view uses ValidateSummary() helper to display the
validation error messages. As you might be aware ValidationSummary() or
ValidationMessage() helpers work only if ModelState dictionary contains some
entries. In our example these entries are added by TryValidateModel() method.
Since ModelState dictionary contains some entries the ValidationSummary()
iterates through them and displays the validation errors.
That's it! Try running the application a couple of times with invalid as well
as valid data and see if everything works as expected.