Category Archives: ASP.NET MVC

ASP.NET MVC QuickStart 8: partial updates using jquery

…continued from part 7.

Objectives

In this Hands-On Lab, you will learn how to do partial updates in JSON format using jquery. In particular, you will:
– Task 1: add new controller action and view
– Task 2: add controller action method to get list of members
– Task 3: add jquery to populate members combo box
– Task 4: show member details

System requirements

You must have the following items to complete this lab:
– Microsoft Visual Studio 2008 SP1 (professional edition)
– Microsoft ASP.NET MVC 1.0

Prequisites

You must have the following skills to understand this lab:
– Fundamental knowledge of software development in .NET 3.5
– Some experience in ASP.NET web development
This lab builds further on the QuickStart 7 code.

Task 1: add new controller action and view

We are going to create a new page that has a combo box containing all member last names, and the details of the selected member. First add a new controller action to the MembersController:

public ActionResult Index2()
{
// return this list to the default view
return View();
}

Also add a view for this action method:

Add View

In this case we are not going to create a strongly typed view because the needed data will be retrieved asynchronously using jquery.

Modify the generated view. First, place a combo box in the view:

 

<%@ Page Title="" Language="C#"
MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent"
runat="server">
Index2
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent"
runat="server">
<h2>Index2</h2>

<% <select id="MembersDropDownList" />
</asp:Content>

To fill this combo box with members using AJAX, we have to:

  • Add a controller action method that returns a list of members
  • Write some jquery that uses the controller action method to populate the combo box when the page has been rendered

We will do this in the next tasks.

Task 2: add controller action method to get list of members

Add a new action method called GetCustomers to the MembersController:

public JsonResult GetMembers()
{
// Get a list of members using the model
List<Member> members = MemberService.GetMembers();

// Return list of members as JSON
return this.Json(members);
}

As you see this method returns a different type of action result: a JsonResult. It’s in JSON format, which is a lightweight data-interchange format (see http://json.org).

Task 3: add jquery to populate members combo box

Now we have to consume the JSON data and populate the combo box with members.

First we need references to the jquery javascript libraries, so open up the Site.Master and add the needed references:

<head runat="server">
<title>
<asp:ContentPlaceHolder ID="TitleContent" runat="server" />
</title>
<link href="../../Content/Site.css" rel="stylesheet" type="text/css" />
<script src="/Scripts/jQuery-1.3.2.js" type="text/javascript"></script>
<script src="/Scripts/jquery-1.3.2.min.js" type="text/javascript"></script>
</head>

Open the Index2 view. To retrieve the list of members when the DOM of the page is loaded and to populate the combo box with these members, we can write the following  jquery:

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent"
runat="server">
<script type="text/javascript">
$(document).ready(function()
{
// if the DOM is loaded, start retrieving the members
$.getJSON("/Members/GetMembers", null, function(data)
{
addMembers(data);
})
});
function addMembers(data)
{
// Get the DOM reference to the drop-down list.
var membersDropDownList = $("#MembersDropDownList");
// Loop through each member and populate the combo box.
$.each(data, function(index, optionData) {
membersDropDownList.append("<option value='"
+ optionData.ID
+ "'>" + optionData. LastName
+ "</option>");
});
};
</script>
<h2>Index2</h2>
<select id="MembersDropDownList" />
</asp:Content>

Take your time to study and understand the jquery syntax.

Now start the application and go to the page “/members/index2”. If everything works, the combo box should be populated:

Index2

Task 4: show member details

Finally, we want to show the details of the selected member. The first step to do that is to provide a new action member for the MemberController that retrieves the details of a member. So add the following action method to the MemberController:

public JsonResult GetMemberDetails(int memberID)
{
// Get member details using model
Member m = MemberService.GetMember(memberID);
// Return list of members as JSON
return this.Json(m);
}

The next thing is to make sure that when the selected combo box item changes, the member details are updated. Again, we will do that with jquery:

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent"
runat="server">
<script type="text/javascript">
$(document).ready(function()
{
$.getJSON("/Members/GetMembers", null, function(data)
{
addMembers(data);
})
var membersDropDownList = $("#MembersDropDownList");
membersDropDownList.change(function() {
populateDetails(this.value);
})
});
function populateDetails(id)
{
$.getJSON("/Members/GetMemberDetails",
{memberID : id }, function(data) {
$("#FirstName").html(data.FirstName);
$("#LastName").html(data.LastName);
});
}
function addMembers(data)
{
// Get the DOM reference to the drop-down list.
var membersDropDownList = $("#MembersDropDownList");
// Loop through each member and populate the combo box.
$.each(data, function(index, optionData) {
membersDropDownList.append("<option value='"
+ optionData.ID
+ "'>" + optionData.LastName
+ "</option>");
});
};
</script>
<h2>Index2</h2>
<select id="MembersDropDownList"></select>
<p />
<div>First name: <span id="FirstName"></span></div>
<div>Last name: <span id="LastName"></span></div>
</asp:Content>

Start the application again, go to the page “/members/index2” and select another member. The details should become visible:
 

Index2

One thing left: when we initially load this page, the member details are not filled in. To solve that, just add the following line of code:

<script type="text/javascript">
$(document).ready(function()
{
$.getJSON("/Members/GetMembers", null, function(data)
{
addMembers(data);
populateDetails(membersDropDownList.val());
})
var membersDropDownList = $("#MembersDropDownList");
membersDropDownList.change(function() {
populateDetails(this.value);
})
});
</script>

If you try it now, the member details will be visible when the page is first loaded.

ASP.NET MVC QuickStart 7: action filters

…continued from part 6.

Objectives

In this Hands-On Lab, you will be introduced to the ASP.NET MVC framework. In particular, you will:

  • Task 1: understand action filters
  • Task 2: built-in action filters
  • Task 3: write a custom action filter

System requirements

You must have the following items to complete this lab:

  • Microsoft Visual Studio 2008 SP1 (professional edition)
  • Microsoft ASP.NET MVC 1.0

Prequisites

You must have the following skills to understand this lab:

  • Fundamental knowledge of software development in .NET 3.5
  • Some experience in ASP.NET web development

This lab builds further on the QuickStart 6 code.

Task 1: understand action filters

An action filter is behavior that can be attached to controllers and action methods. They inject extra logic in the request processing pipeline:

  • before and after an action method runs
  • before and after action results are executed
  • during exception handling

Action filters are very powerful if you want to inject general-purpose code that has to be reused all over the place, but implemented once, such as logging, authorization, caching and others.

The MVC framework knows about following filter types:

Filter type           Interface             When run                         
Authorization filter IAuthorizationFilter Before running any other
filter or action method
Action Filter IActionFilter Before and after an action
method is run
Result Filter IResultFilter Before and after an action
result is executed
Exception filter IExceptionFilter Handles exception thrown
by action filter, action
result or action method

The default implementations of these filter types are:

Filter type               Default implementation
Authorization filter AuthorizeAttribute
Action Filter ActionFilterAttribute
Result Filter ActionFilterAttribute
Exception filter HandleErrorAttribute

Task 2: built-in action filters

ASP.NET MVC has a number of built in action filters. To use them, you just need to decorate the controller or action filter with a specific action filter attribute, for example:

[Authorize()]
[OutputCache(Duration=60)]
public ActionResult Index()
{

}

In this particular example, we use the AuthorizeAttribute to specify that the Index action method can only be executed if the user is logged in. We have also decorated the action method Index with the OutputCacheAttribute, which will cache the output of the action method for a specific duration (60 seconds). These two action filters are filters that are already built-in into the ASP.NET MVC framework, but of course you can build your own action filters. Other built in action filters are: AcceptVerbs, Bind, HandleError, ModelBinder, NonAction and others.

Task 3: write a custom action filter

In this task we will create a custom action filter that logs the stages of a request to a controller action. To keep things simple, we will just write to the Visual Studio output window.

To start, add a new folder to the MvcApplication1 web application and call it ActionFilters. Right click this folder and select Add -> Class, and name it LogAttribute, and implement it with the following code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Diagnostics;
namespace MvcApplication1.ActionFilters
{
public class LogAttribute : ActionFilterAttribute,
IActionFilter, IResultFilter, IExceptionFilter
{
#region IActionFilter Members
void IActionFilter.OnActionExecuted(ActionExecutedContext filterContext)
{
Log("OnActionExecuted", filterContext.RouteData);
}
void IActionFilter.OnActionExecuting(ActionExecutingContext filterContext)
{
Log("OnActionExecuting", filterContext.RouteData);
}
#endregion
#region IResultFilter Members
void IResultFilter.OnResultExecuted(ResultExecutedContext filterContext)
{
Log("OnResultExecuted", filterContext.RouteData);
}
void IResultFilter.OnResultExecuting(ResultExecutingContext filterContext)
{
Log("OnResultExecuting", filterContext.RouteData);
}
#endregion
#region IExceptionFilter Members
public void OnException(ExceptionContext filterContext)
{
Log("OnException", filterContext.RouteData);
}
#endregion
#region Log
public static void Log(string message, RouteData routeData)
{
Trace.WriteLine(
string.Format("{0}, controller = {1}, action = {2}",
message,
routeData.Values["controller"],
routeData.Values["action"]));
}
#endregion
}
}

This code is very straightforward, notice the following:

  • the LogAttribute class inherits from ActionFilterAttribute, which provides you with the default implementation.
  • the LogAttribute class implements three interfaces: IActionFilter, IResultFilter and IExceptionFilter.
  • In each interface method the filter context is passed, that can be used to determine information about the context of where the method was triggered (controller, action method…).

Then add the following using statement on top of the MembersController:

using MvcApplication1.ActionFilters;

Now you can apply the Log action filter, for example, decorate the action method Index of the MembersController with it:

[Log]
public ActionResult Index()
{
// Get a list of members using the model
List<Member> members = MemberService.GetMembers();

// return this list to the default view
return View(members);
}

Hit F5 to start the web application, go to http://localhost:3382/members and have a look at the output window in visual studio:

Output window

As you see, the various steps in the pipeline have been written to the output window.

Note: if you would throw an exception in the Index action method, the OnException step would be executed too.

Go to next part.

ASP.NET MVC QuickStart 6: automated testing

…continued from part 5.

Objectives

In this Hands-On Lab, you will be introduced to the ASP.NET MVC framework. In particular, you will:

  • Task 1: add a unit test project
  • Task 2: Test model
  • Task 3: Test controller
  • Task 4: Test routing

System requirements

You must have the following items to complete this lab:

  • Microsoft Visual Studio 2008 SP1 (professional edition)
  • Microsoft ASP.NET MVC 1.0

Prequisites

You must have the following skills to understand this lab:

  • Fundamental knowledge of software development in .NET 3.5
  • Some experience in ASP.NET web development

This lab builds further on the QuickStart 5 code.

Task 1: add a unit test project

In previous QuickStarts we didn’t focus on testing, because the focus was on other aspects of ASP.NET MVC. But one of the biggest advantages of the MVC framework is that you can unit test everything. If you take the test-driven approach, you would first create your tests and then write the code to make those tests pass.

Let’s start off with adding a unit test project: right click the MvcApplication1 solution and select Add -> New project. Select Test Project in Test project types and fill in name and location:

Add new project

Click OK. A test project is now added to your solution:

TestProject1 project

Delete the generated UnitTest1.cs file. Also add a reference to the MvcApplication1 project, and to:

  • System.Web.Mvc
  • System.Web.Abstractions
  • System.Data
  • System.Data.DataSetExtensions
  • System.Web
  • System.Web.Routing

You should test every aspect of you project, in this case this means that you should test:

  • Model (MemberService)
  • Controller action methods (MemberController)
  • Routing

Task 2: Test model

Right click the TestProject1 project and select Add -> New test:

Add new test

Select Unit Test and name it ModelTest.cs. Click OK. Add the following using statement on top of the ModelTest class:

using MvcApplication1.Models;

Then add some unit tests, like:

[TestMethod]
public void Get_Members_Returns_List()
{
var members = MemberService.GetMembers();
Assert.IsNotNull(members, "No members retrieved");
Assert.AreEqual(5, members.Count, "Got wrong number of members");
Assert.AreEqual("Geoffrey", members[0].FirstName);
Assert.AreEqual("Denver", members[0].LastName);
// ...
}
[TestMethod]
public void Delete_Member_Workd()
{
var members_before = MemberService.GetMembers();
MemberService.DeleteMember(members_before[0].ID);
var members_after = MemberService.GetMembers();
Assert.AreEqual(4, members_after.Count, "Member was not deleted");
// ...
}

Run these tests and make sure they pass.

Task 3: Test controller

Add a new unit test to the test project and call it MembersControllerTest. Add the following using statements on top:

using MvcApplication1.Controllers;
using MvcApplication1.Models;
using System.Web.Mvc;

Add the following tests:

[TestMethod]
public void Index_Presents_Correct_Page_Of_Members()
{
MembersController membersController = new MembersController();
ViewResult result = membersController.Index() as ViewResult;

Assert.IsNotNull(result, "Didn't render view");
var members = (IList<Member>)result.ViewData.Model;
Assert.AreEqual(5, members.Count, "Got wrong number of members");
Assert.AreEqual("Geoffrey", members[0].FirstName);
Assert.AreEqual("Denver", members[0].LastName);
}
[TestMethod]
public void Details_Presents_Correct_Page_Of_Member()
{
MembersController membersController = new MembersController();
ViewResult result = membersController.Details(0) as ViewResult;
Assert.IsNotNull(result, "Didn't render view");
var member = ((Member)result.ViewData.Model);
Assert.AreEqual("Geoffrey", member.FirstName);
Assert.AreEqual("Denver", member.LastName);
}

Run these tests and make sure they pass.

Task 4: Test routing

To test routing, we have to mock the request context. To do so, the easiest way is to use a mocking framework, and in this case we will be using moq, which can be downloaded from http://code.google.com/p/moq. Then, from the unit test project, add a reference to Moq.dll.

Add a new unit test and call it InboundRoutingTests.cs. On top of this class, add the following using statements:

using System.Web.Routing;
using MvcApplication1;
using System.Web;

Testing a specific route comes always down to the same:

  • registering the web application’s route collection
  • mocking the request context
  • get the mapped route based in the URL
  • test the mapped route to see if it matches expected values

So we’ll first add a method that accepts a URL and expected routing data values and tests whether the URL routing matches these expected values:

private void TestRoute(string url, object expectedValues)
{
// Arrange: Prepare the route collection and a mock request context
RouteCollection routes = new RouteCollection();
MvcApplication.RegisterRoutes(routes);
var mockHttpContext = new Moq.Mock<HttpContextBase>();
var mockRequest = new Moq.Mock<HttpRequestBase>();
mockHttpContext.Setup(x => x.Request).Returns(mockRequest.Object);
mockRequest.Setup(x =>
x.AppRelativeCurrentExecutionFilePath).Returns(url);
// Act: Get the mapped route
RouteData routeData = routes.GetRouteData(mockHttpContext.Object);
// Assert: Test the route values against expectations
Assert.IsNotNull(routeData);
var expectedDict = new RouteValueDictionary(expectedValues);
foreach (var expectedVal in expectedDict)
{
if (expectedVal.Value == null)
Assert.IsNull(routeData.Values[expectedVal.Key]);
else
Assert.AreEqual(expectedVal.Value.ToString(),

routeData.Values[expectedVal.Key].ToString());
}
}

And finally we can write unit tests to test different routes, for example:

[TestMethod]
public void Slash_Goes_To_All_Members_Page()
{
TestRoute("~/", new
{
controller = "Home",
action = "Index",
id = (string)null,
});
}
[TestMethod]
public void Member2_Goes_To_Member2_Details_Page()
{
TestRoute("~/member2", new
{
controller = "Members",
action = "Details",
id = 2,
});
}

Run these tests and make sure they pass.

Go to next part.

ASP.NET MVC QuickStart 5: routing

…continued from part 4.

Objectives

In this Hands-On Lab, you will be introduced to the ASP.NET MVC framework. In particular, you will:

  • Task 1: Understand routing
  • Task 2: Add constraint
  • Task 3: Add new route
  • Task 4: Generate outgoing URL’s

System requirements

You must have the following items to complete this lab:

  • Microsoft Visual Studio 2008 SP1 (professional edition)
  • Microsoft ASP.NET MVC 1.0

Prequisites

You must have the following skills to understand this lab:

  • Fundamental knowledge of software development in .NET 3.5
  • Some experience in ASP.NET web development

This lab builds further on the QuickStart 4 code.

Task 1: understand routing

In ASP.NET MVC, a URL does not map to a file, but on an action method of a controller. This routing from URL to controller/action can be configured in the routing table, that is configured in the RegisterRoutes method of the Global.asax.cs file in the web application project, which defaults to:

public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home",
action = "Index",
id = "" } // Parameter defaults
);
}

Note: the routing system not only maps incoming URL’S to the appropriate controller/action, but also constructs outgoing URLS!

Each route defines an URL pattern and how to handle requests for such URL’s. For the default route, this means:

URL                     Maps to
/ controller = Home, action = Index, id = “”
/members controller = Members, action = Index, id = “”
/members/create controller = Members, action = Create, id = “”
/members/details/2 controller = Members, action = Details, id = “2”

If you omit controller in the URL, it defaults to Home because we specified a parameter default for controller. If you omit action in the URL, it defaults to Index because we specified a parameter default for action.

When you define a route by using the MapRoute method, you can pass a number of parameters:

  • a route name, which is optional
  • the URL pattern, using parameters
  • default values: default value for each parameter (optional)
  • constraints: constraints for each parameter (optional)

Task 2: Add constraint

If you use http://localhost:3382/members/Details/2 then it will map to the Details action method of the MembersController, and member with ID 2 will be shown. This works, because ‘2’ will be mapped to the id parameter of the Details action method.

However, http://localhost:3382/members/Details/a also matches the URL pattern “{controller}/{action}/{id}” and so ‘a’ will be mapped to the id parameter. But, id is of type int, and so you will get the error:

Server error

Obviously, we know that id should be a valid number, but the routing system doesn’t know that. To solve this, we can add a constraint to be sure that the mapping is only done when id is a valid number:

routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home",
action = "Index",
id = 0 }, // Parameter defaults
new { id = @"d{1,6}" } // Constraints
);

Now, if id is not according to the regular expression ‘d{1,6}’ (numeric, 1 to 6 digits long) then the rule does not match and  the URL http://localhost:3382/members/Details/a isn’t mapped:

Server error

Task 3: Add new route

Suppose we want the following URL to work:

URL         Maps to
/member1 controller = Members, action = Details, id = “1”
/member3 controller = Members, action = Details, id = “3”

If you try that, obviously it does not match any URL pattern yet, so you’ll get an error:

Server error

To make this work, add a new route to the global.asax.cs file (it has to be the first one):

public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"", // Route name
"member{id}", // URL with parameters
new { controller = "Members",
action = "Details",
id = 0 }, // Parameter defaults
new { id = @"d{1,6}" } // Constraints
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home",
action = "Index",
id = 0 }, // Parameter defaults
new { id = @"d{1,6}" } // Constraints
);
}

Note: add routes in the correct order: from most specific to least specific!

Now try http://localhost:3382/member3:

My MVC application

Task 4: Generate outgoing URL’s

Generating hyperlinks should never be done hardcoded, always use the built in helper methods like Html.ActionLink because they use the routing system to correctly build the URL.

In previous examples we already used Html.ActionLink to construct the links to view, create, edit and delete members, for example:

<%= Html.ActionLink("Details", "Details", new { id = item.ID }) %>

This generated the links as follows:

Generated links

However, in task 3 we added a new route to view the details of a member, so that URL’s in the form of http://localhost:3382/member3 work. After adding this route, the generated URL’s look different:

Generated links

This is because Html.ActionLink uses the routing configuration to construct the URL’s.

Note: If we would have constructed the links hard coded, changing routing configuration may have broken those links!

Go to next part.

ASP.NET MVC QuickStart 4: implement validation

… continued from part 3.

Objectives

In this Hands-On Lab, you will create functionality to add validation of members. In particular, you will:

  • Task 1: understand validation
  • Task 2: add convenience method to ModelState
  • Task 3: implement validation logic in model
  • Task 4: handle validation errors in controller

System requirements

You must have the following items to complete this lab:

  • Microsoft Visual Studio 2008 SP1 (professional edition)
  • Microsoft ASP.NET MVC 1.0

Prequisites

You must have the following skills to understand this lab:

  • Fundamental knowledge of software development in .NET 3.5
  • Some experience in ASP.NET web development

This lab builds further on the QuickStart 3 code.

Task 1: understand validation

At this moment, when the user enters an empty first name and/or last name, everything works. Of course, we know that these properties are mandatory, so we have to add some validation logic to make sure that the user can’t enter invalid information.

Due to the separation of concerns, it is straightforward that this validation checking has to be done in the model, and that the view is responsible for showing any validation messages to the user. So what should happen is that the controller uses the model to do validation and then it passes the error information to the view.

Now, how can the controller pass error information to the view? The way to do this is by registering the errors in ModelState, which is a temporary storage area. The steps are:

  • Controller asks model to do validation and if validation fails, it receives the list of validation errors from the model.
  • Controller registers the validation errors in ModelState, by calling the ModelState.AddModelError method for each error. This will add all errors to the ModelStateDictionary.
  • The view has access to this ModelStateDictionary and you can use Html helper methods to display the information:
    • Html.ValidationSummary: returns a list of validation errors.
    • Html.ValidationMessage: returns validation message for specific field.

Task 2: add convenience method to ModelState

The ModelState.AddModelError method only allows adding one validation error at a time, so first we will create an extension method called ModelState.AddModelErrors that allows us to add a collection of validation errors in one method call.

Right click the web project MvcApplication1, and select Add -> New folder, and call it Helpers. Then right click the Helpers folder and select Add -> Class, and call it ModelStateHelpers.cs. Write the extension method as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using MvcApplication1.Models;
using System.Web.Mvc;
namespace MvcApplication1.Helpers
{
public static class ModelStateHelpers
{
public static void AddModelErrors(
this ModelStateDictionary modelState,
IEnumerable<RuleViolation> errors)
{
foreach (RuleViolation issue in errors)
{
modelState.AddModelError(
issue.PropertyName, issue.ErrorMessage);
}
}
}
}

Task 3: implement validation logic in model

Now we will create the validation logic in our model.

First we will create a new class that can hold information (property name and related error message) about a validation error. Add a new class called RuleValidation to the Models folder:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace MvcApplication1.Models
{
public class RuleViolation
{
public string ErrorMessage { get; private set; }
public string PropertyName { get; private set; }
public RuleViolation(string errorMessage)
{
ErrorMessage = errorMessage;
}
public RuleViolation(string errorMessage, string propertyName)
{
ErrorMessage = errorMessage;
PropertyName = propertyName;
}
}
}

A very convenient way of telling whether there were validation errors is by throwing exceptions. So add a new class called RuleException to the Models folder, which is a special kind of exception that will hold all validation errors that have occurred:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Collections.Specialized;
namespace MvcApplication1.Models
{
public class RuleException : Exception
{
public IEnumerable<RuleViolation>
RuleViolations { get; private set; }
public RuleException(IEnumerable<RuleViolation> ruleViolations)
{
RuleViolations = ruleViolations;
}
}
}

Now it’s time to do the actual validation. Add the following two methods to the Member class in the Models folder:

public bool IsValid
{
get { return (GetRuleViolations().Count() == 0); }
}
public IEnumerable<RuleViolation> GetRuleViolations()
{
if (String.IsNullOrEmpty(FirstName))
yield return new RuleViolation(
"First name is required", "FirstName");
if (String.IsNullOrEmpty(LastName))
yield return new RuleViolation(
"Last name is required", "LastName");
yield break;
}

To keep things simple we demand that first name and last name have to be filled in.

Finally, we have to apply these validation checks in the MemberService operations. Add the following code to the CreateMember method in MemberService class in the Models folder:

public static void CreateMember(Member member)
{
if (!member.IsValid)
throw new RuleException(member.GetRuleViolations());
// Set member id to next free id
member.ID = members.Max(m => m.ID) + 1;
// add member to collection
members.Add(member);
}

And do the same in the UpdateMember method:

public static void UpdateMember(Member member)
{
if (!member.IsValid)
throw new RuleException(member.GetRuleViolations());
// Find member in collection
Member foundMember = members.Find(m => m.ID == member.ID);
// Update member
foundMember.FirstName = member.FirstName;
foundMember.LastName = member.LastName;
}

So when a new member is added or updated, before the actual creation or update happens the member object is checked to see if it’s valid – if it isn’t, a RuleException is thrown to the caller (which is the controller) that has to handle it.

Task 4: handle validation errors in controller

Modify code of the Create action method in the MembersController class as follows:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Member member)
{
try
{
// Use model to create this new member
MemberService.CreateMember(member);
// Redirect to Details action method and pass the new id
return RedirectToAction("Details", new { id = member.ID });
}
catch (RuleException ex)
{
ModelState.AddModelErrors(ex.RuleViolations);
return View(member);
}
}

And do the same for the Update action method:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(Member member)
{
try
{
// Use model to create this new member
MemberService.CreateMember(member);
// Redirect to Details action method and pass the new id
return RedirectToAction("Details", new { id = member.ID });
}
catch (RuleException ex)
{
ModelState.AddModelErrors(ex.RuleViolations);
return View(member);
}
}

Note: The method ModelState.AddModelErrors is the extension method that we wrote earlier, so in order to use it we also have to add a using statement on top of the MembersController class:

using MvcApplication1.Helpers;

Right, let’s test this. Start the web application, and create a new member but don’t fill in last name:

My MVC application

Click Create.

My MVC application

The validation does its work and validation errors are displayed. But why does it also display the ‘A value is required’ message?

Actually, except for the manual validation messages that you add yourself, ASP.NET MVC also does its own validation checks on properties. There is a validation of all bound properties of a member object, which are FirstName, LastName but also… ID – because default, all properties of an object are bound to the view, even if this property is not used in the view. In our case, the ID is of type int, and therefore it cannot be null; so the validation message is added.

To solve this, we have to tell MVC not to bind the ID property, because it’s our responsibility to generate it. You can do this by decorating the Member class with the Bind attribute as follows:

[Bind(Include = "FirstName,LastName")]
public class Member
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }

}

Now only FirstName and LastName properties are bound and validated; and so ID is not validated anymore:

My MVC application

Go to next part.