Validating Model Example

You can validate a value using Validation Attributes from a class or from a property.

Context
We want to create a comment for a blog post so we have the Comment class.
public class Comment
{
public int Id { get; private set; }
public CommentText Content { get; private set; }

//DataAnnotations and CavemanTools Attributes		
[Required(ErrorMessage = "Email is required")]
[Email(ErrorMessage = "Invalid email format")]
public string Email { get; private set; }

//private constructor since we want to force object creation only from static method
private Comment()
{
			
}

private void Load(InputData data,IValidationDictionary errors)
{
	if (data == null) throw new ArgumentNullException("data");
	if (errors == null) throw new ArgumentNullException("errors");
	Content = CreateText(data.Comment, errors);
			
	//validates according to Validation Attributes of Email property. THe error key is the name of the property.
	if (data.Email.ValidateFor<Comment>(d=>d.Email,errors))
	{				
		Email = data.Email;
	}
}

		
CommentText CreateText(string value,IValidationDictionary errors)
{
	//we create a validation item which will hold all the error messages for the property Content
	// "content" is the name of  the key.
	var validation = new ValidationItem("content", errors);
	return CommentText.Create(value, validation);
}

public static Comment FromInput(InputData data,IValidationDictionary validation)
{
	var c = new Comment();
	c.Load(data,validation);
	if (validation.IsValid)
	{
		return c;
	}
	return null;
}
}
You can notice a few interesting things. You might think it's over engineered but in fact it prevents future bugs and thus saves time.
  • The Comment object can be created only with the static method because we don't want an object in invalid state. If we allow a constructor (even if we're doing the validation) you have to remember to check if IsValid every time you'll use the object. In this example you either have a valid object or your don't have any object.
  • The Content property is itself an object (more exactly, a value object ) which functions after the same principle: either is valid or it doesn't exist.
    • Using a value object has a few advantages
      • I can keep all the validation in the same place as the object
      • The name tells me what is this purpose
      • The validation attributes tell me the requirements of a comment text
      • I can add/change all the requirements easily , because they all are in one place

// we're using CavemanTools attributes here!
[Required(ErrorMessage = "*")]
[StringLength(500, ErrorMessage = "Your text is too long, only 500 characters are allowed")]
public class CommentText
{
private CommentText(string value)
{
	Value = value;
}

public static CommentText Create(string value,IAddError error)
{
	//we validate the string to check if it satisfies the conditions.			
	if (value.ValidateAs<CommentText>(error))
	{
		return 	new CommentText(value);
	}
	return null;
}
		
public string Value { get; private set; }
}

I'm using the CavemanTools extensions methods to validate a property and a value object. The IValidationDictionary object collects the errors for each property.
If you're using Asp.Net Mvc you can create a ModelStateWrapper like in this article http://asp-umb.neudesic.com/mvc/tutorials/validating-with-a-service-layer--cs (listing 7) and the errors are automatically available to the ModelState.

//the view model
public class InputData
{
public string Comment { get; set; }
public string Email { get; set; }
}


In CommentController
public ActionResult CreateComment(InputData data)
{
  var validation= new ModelStateWrapper(ModelState);

  //comment will be null, if the input data is invalid
  var comment = Comment.FromInput(input, validation);

    if(validation.IsValid)	//you can also check comment!=null
     {
      //all is good go on with it
     //redirect to post
	}
   //show the errors 
  return View(input);
}
This example is for Asp.Net Mvc but you can use the validation methods and approach anywhere. Just implement IValidationDicitonary(or use the supplied DefaultValidationeWrapper class) to collect and display the errors.
As you can see the validation itself is very easy and it takes place at the Business Layer (Model) level. Because an object can be ONLY in a valid state, we avoid future bugs and complications.

Last edited Jan 22, 2011 at 6:26 PM by mike_sapiens, version 1

Comments

No comments yet.