Feeds:
Posts
Comments

Posts Tagged ‘mvc’

In a recent project, one of my models had the following validation requirements:
If “CompletedDate” has a value (a date value), “Status” must be set to “Completed”. If “Status” is set to “Completed”, the “CompletedDate” must be set.

In other words, validation wasn’t simply that a value must be a string, or a number between x and y, etc; the validation of one field is dependant on the value in the other and vice versa.

I first went down this route, running the validation in the data layer (in the Entity Framework stuff):
MSDN: How to: Customize Data Field Validation in the Data Model

This was pretty good, but the problem is *when* the validation occurs when using this approach: when the model is being saved. I want validation to run *before* saving the data, so that MVC can know that there’s a problem, and send some friendly message to the user. A typical “edit” command in a controller looks like this:

[sourcecode language=”csharp”][HttpPost]
public ActionResult Edit(ProjectTask projecttask)
{
if (ModelState.IsValid)
{
db.ProjectTasks.Attach(projecttask);
db.ObjectStateManager.ChangeObjectState(projecttask, EntityState.Modified);
db.SaveChanges();
return RedirectToAction("Details", "Project", new { id = projecttask.Workorder.CallID });
}
return View(projecttask);
}[/sourcecode]

So I *do* have an option, I could put a try around db.SaveChanges, and catch my validation error there. However, I would then have to grab the error message and figure out some way to send that back to the client (via ViewBag, most likely). I didn’t like this.

I then inspected this ModelState.IsValid method, and had the thought to create an extension method along the lines of ModelState.IsValidProjectTask(). With this, I can add error messages into the ModelState, specific to the fields. This means Razor script like:

@Html.ValidationMessageFor(model => model.CompletionDate)

will work, returning a proper error message where appropriate.

So here’s what I did:

[sourcecode language=”csharp”][HttpPost]
public ActionResult Edit(ProjectTask projecttask)
{
if (ModelState.IsValidProjectTask())
{
db.ProjectTasks.Attach(projecttask);
db.ObjectStateManager.ChangeObjectState(projecttask, EntityState.Modified);
db.SaveChanges();
return RedirectToAction("Details", "Project", new { id = projecttask.Workorder.CallID });
}
return View(projecttask);
}[/sourcecode]

and then in my Models namespace:

[sourcecode language=”csharp”]using System.Web.Mvc;

namespace MyProject.Models
{
public static class ModelStateDictionaryExtensions
{
public static bool IsValidProjectTask(this ModelStateDictionary modelStateDictionary)
{
return modelStateDictionary.IsValid & ValidateDates(modelStateDictionary);
}

private static bool ValidateDates(ModelStateDictionary modelStateDictionary)
{
ModelState outState;
var submittedDate = string.Empty;
var submittedStatus = string.Empty;

if (modelStateDictionary.ContainsKey("CompletionDate"))
{
modelStateDictionary.TryGetValue("CompletionDate", out outState);
submittedDate = outState.Value.AttemptedValue;
}
if (modelStateDictionary.ContainsKey("Status"))
{
modelStateDictionary.TryGetValue("Status", out outState);
submittedStatus = outState.Value.AttemptedValue;
}

if (submittedStatus.ToUpper() == "COMPLETED" & string.IsNullOrEmpty(submittedDate))
{
modelStateDictionary.AddModelError("CompletionDate", "Task completed, but no completion date provided.");
return false;
}
if (submittedStatus.ToUpper() != "COMPLETED" & !string.IsNullOrEmpty(submittedDate))
{
modelStateDictionary.AddModelError("Status", "Completion date provided, but task not completed.");
return false;
}

return true;
}
}
}[/sourcecode]

Advertisements

Read Full Post »