In this exercise you will learn how to create an ASP.NET MVC application in Visual Studio 2010.
As a first step, we will use a stub data repository, which will be replaced, in exercise 2, with another one that uses Entity Framework as data provider.
You will also learn how to add paging, filtering, and sorting functionality to an existing ASP.NET MVC application, as well as adding simple validation using Data Annotation Validators.
Task 1 – Creating PlanMyNight ASP.NET MVC Web Application Project
In this task you will create and configure an empty ASP.NET MVC application project using the MVC Visual Studio template.
- Start Microsoft Visual Studio 2010 from Start | All Programs | Microsoft Visual Studio 2010 | Microsoft Visual Studio 2010.
- On the File menu, point to New, and click Project.
- In the New Project dialog box make sure that .NET Framework 4 is selected, and select the Visual C# | ASP.NET MVC 2 Web Application project type. You may set the location to the %TrainingKitInstallFolder%\Labs\AspNetMvcPlanMyNight\Source\Ex01-CreatingPlanMyNight\begin folder.
- Change the Name to PlanMyNight, and click OK.

Figure 1 Create New Project Dialog Box
- After selecting the OK button, you will be prompted to create a test project. Select
No, because you will not create test cases on this lab.
Note:When you create a new MVC Web application, Visual Studio gives you the option to create two projects at the same time. The first project is a Web project where you can implement your application. The second project is a testing project where you can write unit tests for your MVC components. It is a good practice to create both projects and test the whole application. On this lab we will avoid testing for simplicity. - Configure the web site to use port 50000.
- To do this, in Solution Explorer, right-click the PlanMyNight project and in the context menu select Properties.
- In the Property page open the Web tab.
- In the Servers section, select Specific Port.
- Set the port number to 50000.
- Press Ctrl + S to save changes.

Figure 2 Specifying a port number
Task 2 – Creating PlanMyNight Model
In this task, you will create the PlanMyNight Entities and Data Model. Also, you will see how paging, validation, filtering and sorting functionalities are implemented in the PlanMyNight application.
- In Solution Explorer, right-click the Models folder, and add a new one named Entities
- Create a class named Activity, which will be the main entity of your application.
- Right-click the Entities folder.
- Point to Add and click Class.
- Type Activity.cs as the class Name and click Add.
- Replace the default implementation of the class with the following code:
(Code Snippet – PlanMyNight MVC App – Activity Class)
namespace PlanMyNight.Models.Entities { public partial class Activity { public string Id { get; set; } public string PhoneNumber { get; set; } public string Name { get; set; } public string State { get; set; } public string Zip { get; set; } public string Street { get; set; } public string City { get; set; } public int ActivityTypeId { get; set; } public int RatingCount { get; set; } public double? Rating { get; set; } } }
Note:Activities will be the main entity in your application. A user will perform a search providing certain search criteria for the activities repository, which you will create later on this exercise, in order to filter and return the group of activities which matches it
Activities will be mapped to a certain type (ActivityTypeId.) To do this, you will use a class that wraps the activity-type related information.
- Create a class named ActivityType.
- In the Solution Explorer, right-click the Entities folder.
- Point to Add and click Class.
- Type ActivityType.cs as the class name, and click Add.
- Replace the default implementation of the class with the following code:
(Code Snippet – PlanMyNight MVC App – ActivityType Class)
namespace PlanMyNight.Models.Entities { public partial class ActivityType { public int Id { get; set; } public string Name { get; set; } public string PluralizedName { get; set; } } }
- Create an enumerator named SortCriteria, which will be used by the activities repository to determine how to sort the results.
- In the Solution Explorer, right-click the Models folder.
- Point to Add and click Class.
- Type SortCriteria.cs as the enumerator name, and click Add.
- Replace the default implementation of the class with the following code:
(Code Snippet – PlanMyNight MVC App – SortCriteria Enumerator)
namespace PlanMyNight.Models { public enum SortCriteria { Name = 0, ActivityType = 1, Rating = 2 } }
Note:Even though we define only three sorting values, by using an enumerator to identify the sort criteria you are providing an extensibility point for your application. You could easily add new sorting criteria without much effort later if needed.
To manage the search criteria provided by the user, you will create the ActivitySearchCriteria class to wrap all the filtering parameters.
- Create a class named ActivitySearchCriteria.
- In the Solution Explorer, right-click the Entities folder.
- Point to Add and click Class.
- Type ActivitySearchCriteria.cs as the class name, and click Add.
- Replace the default implementation of the class with the following code:
(Code Snippet – PlanMyNight MVC App – ActivitySearchCriteria Class)
namespace PlanMyNight.Models.Entities { public class ActivitySearchCriteria { public int? ActivityTypeId { get; set; } public string StreetAddress { get; set; } public string City { get; set; } public string State { get; set; } public string Zip { get; set; } public SortCriteria SortBy { get; set; } public int Page { get; set; } public int PageSize { get; set; } } }
Note:ActivitySearchCriteria contains a property of type SortCriteria. This is how the activities repository will know how to sort the results.
Because the ActivitySearchCriteria class will have information provided by the user, it is a good practice to add validation to it. To do this, you will use a Custom Validator, using Data Annotation Validators.
Note:for more information on using Data Annotation Validators on an ASP.NET MVC application you can visit: http://www.asp.net/learn/mvc/tutorial-39-cs.aspx - Open the ActivitySearchCriteria.cs file(if not already opened) and replace the class signature with the following:
(Code Snippet – PlanMyNight MVC App – ActivitySearchCriteria Class Header)
using System.ComponentModel.DataAnnotations; [CustomValidation(typeof(ActivitySearchCriteria), "IsValid")] public class ActivitySearchCriteria
Note:The CustomValidation attribute allows you to define a custom validation method for the entity. In this case, the method will be named IsValid. - Add the IsValid method to ActivitySearchCriteria.cs by copying the following code bellow the PageSize property.
(Code Snippet – PlanMyNight MVC App – ActivitySearchCriteria IsValid Method)
public static ValidationResult IsValid(ActivitySearchCriteria criteria) { if (!string.IsNullOrEmpty(criteria.City) || !string.IsNullOrEmpty(criteria.State) || !string.IsNullOrEmpty(criteria.Zip) || !string.IsNullOrEmpty(criteria.StreetAddress)) { return ValidationResult.Success; } else { return new ValidationResult("Please provide a search criteria."); } }
Note:The custom validation method must return a ValidationResult. If no errors are found, ValidationResult.Success is returned; otherwise, a new instance of ValidationResult with the error message provided through the constructor method is returned. To implement paging functionality in the model, you will create a wrapper of the list of activities returned from the activities repository. This wrapper adds paging-related information to it.
- Create a class named PagingResult.
- In the Solution Explorer, right-click the Entities folder.
- Point to Add and click Class.
- Type PagingResult.cs as the class name, and Add.
- Replace the default implementation of the class with the following code:
(Code Snippet – PlanMyNight MVC App – PagingResult Class)
namespace PlanMyNight.Models.Entities { using System; using System.Collections.Generic; public class PagingResult<T> { public PagingResult(IEnumerable<T> items) { this.Items = new List<T>(items); this.ItemType = typeof(T).ToString(); } public int PageSize { get; set; } public int TotalItems { get; set; } public int CurrentPage { get; set; } public int TotalPages { get { return (int)Math.Ceiling((decimal)this.TotalItems / this.PageSize); } } public ICollection<T> Items { get; private set; } public string ItemType { get; private set; } } }
Note:Note: PagingResult will be used by the HomeController, to page back and forward within the results by sending paging-related information to the activities repository when invoking the search method.
The next step is to create the repository Interface. This interface will be implemented by the stub repository on this exercise, and by the Entity Framework activities repository on exercise 2.
- Create an interface named IActivitiesRepository. To do this, perform the following steps:
- In the Solution Explorer, right-click the Models folder
- Point to Add and click New Item.
- Select Interface under Visual C#.
- Type IActivitiesRespository.cs as the name, and click Add.
- Replace the default implementation of the interface with the following code:
(Code Snippet – PlanMyNight MVC App – IActivitiesRepository Interface)
namespace PlanMyNight.Models { using System.Collections.Generic; using Entities; public interface IActivitiesRepository { Activity Retrieve(string id); PagingResult<Activity> Search(ActivitySearchCriteria criteria); IEnumerable<ActivityType> RetrieveActivityTypes(); IEnumerable<string> RetrieveStates(); void RateActivity(string activityId, byte rating); } }
Note:The IActivitiesRespository interface has 5 methods: - Retrieve: This method returns the activity that matches the provided id. It is used for retrieving the information of a single activity. - Search: This method returns a PagingResult instance, which contains the filtered set of activities that matches the search criteria (which includes sorting and paging information.) - RetrieveActivityTypes and RetrieveStates: These methods are used for retreiving meta-information from the activities stored in the repository. This information will be used for injecting data into the search form, and populate the fields for the user to choose. - RateActivity: This method allows to rate a given activity. It is the only method that modifies the data model.
Once all entities are created you will proceed by adding the stub implementation of IActivitiesRespository interface. To keep the exercise simple, the Hands-On lab provides this implementation in the Assets folder.
- Add the ActivitiesStubRepository.cs class implementation from the Assets folder. To do this, proceed as follows.
- In the Solution Explorer, right-click the Models folder.
- Point to Add and click Existing Item.
- Browse to the %TrainingKitInstallFolder%\Labs\AspNetMvcPlanMyNight\Source\Assets\Models folder.
- Select the ActivitiesStubRespository.cs and SearchHelper.cs classes and click
Add.

Figure 3 Adding ActivitiesStubRepository Implementation
Note:The ActivitiesStubRepository class implements the IActivityRepository interface, using two lists to simulate the database tables. As this repository is created to test the site’s functionality, the data is hardcoded into the constructor method.
- Press CTRL+SHIFT+B to build the solution.
At this point, you have completed the application model. Your solution should look like the following:

Figure 4 Solution Explorer Models Structure
Task 3 – Creating PlanMyNight Views
In this task you will modify the existing views and create new ones to show information to the user, and get its input for searching and rating activities.
- In the Solution Explorer, double click the Site.Master file, located at
Views\Shared, to open it

Figure 5 Site.Master in Solution Explorer
- Replace the stylesheet definition, adding the following bolded code inside the
header tag of Site.Master.
<head runat="server"> <title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title> <meta http-equiv="content-type" content="text/html; charset=UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=8" /> <link href="../../Content/Site.css" rel="stylesheet" type="text/css" /> <asp:ContentPlaceHolder ID="HtmlHeadContent" runat="server" /> </head>
Note:In the meta-tag, the character encoding of the site is defined as UTF-8. The HtmlHeadContent asp:ContentPlaceHolder will be used to define the keywords meta-tag with search-related information. This meta-tag is used by crawler based search engines when indexing a website. - Replace Site.Master’sbody definition with the following bolded code:
(Code Snippet – PlanMyNight MVC App – SiteMaster BodyDefinition)
<body> <div id="container"> <div id="header"> <div id="logo"> <h1><a href="<%=Url.Content("~/")%>">Plan My Night</a></h1> </div> <hr /> <div id="navigation"> <ul> <li><%=Html.ActionLink("Search", "Index", "Home")%></li> <li><a href="#">About</a></li> </ul> </div> </div> <div id="pageWrapper"> <div id="page"> <hr /> <div id="main"> <asp:ContentPlaceHolder ID="MainContent" runat="server" /> </div> </div> </div> <br /> </div> </body>
Note:The code you have just added has two main sections. In the first one, the header, you defined the logo and the navigation bar. Notice that the navigation bar has an Html.ActionLink, which invokes the HomeController’s Index meth, and displays “search” on the page. In the second section, the pageWrapper, you defined the ContentPlaceHolder for the MainContent. The changes done to this second section are for styling purposes only, and they will be added later in this exercise. The Index.aspx page will define the content of the three ContentPlaceHolders of Site.Master.
- In the Solution Explorer, double-click the Index.aspx file, located in the
Views/Home folder, to open it.

Figure 6 Index.aspx in Solution Explorer
- Add the definition of the HtmlHeadContent ContentPlaceholder. To do this, paste the bolded code before the
TitleContent content definition.
<asp:Content runat="server" ContentPlaceHolderID="HtmlHeadContent"> <% if(!String.IsNullOrEmpty(ViewData["KeywordsMetatag"] as string)) { %> <meta name="keywords" content="<%=Html.AttributeEncode(ViewData["KeywordsMetatag"].ToString())%>" /> <% } %> </asp:Content> <asp:Content ID="indexTitle" ContentPlaceHolderID="TitleContent" runat="server">
Note:Note: The if statement checks if the KeywordsMetatag is provided, if so, it defines the meta-tag named keywords, and set its content. As explained before, this tag is used by crawler-based search engines to index your web-site. - Replace the TitleContent content definition with the following code to provide different
PageTitle depending on the used search criteria.
<asp:Content ID="indexTitle" ContentPlaceHolderID="TitleContent" runat="server"> Plan My Night - <%= ViewData.ContainsKey("CriteriaDescription") ? ViewData["CriteriaDescription"].ToString() : "Search Activities"%> </asp:Content>To provide the MainContent content, you will use two MVC View User Controls, SearchForm and ActivitiesSearchResults. To render these controls, you will use the Html.RenderPartial helper method.
- Replace the MainContent content definition with the bolded code that follows.
<asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server"> <% Html.RenderPartial("SearchForm"); %> <hr /> <% Html.RenderPartial("ActivitiesSearchResults"); %> </asp:Content>
Note:Note: SearchForm is the view used to capture search parameters input, while ActivitiesSearchResults will be used to render the search result. - Create ActivitiesSearchResults MVC 2 View User Control. To do this, proceed as follows.
- In the solution explorer, right-click the Home folder, located inside Views
- Point to Add and click New Item.
- Select MVC 2 View User Control, under Visual C#\Wev\MVC.
- Type ActivitiesSearchResults.ascx as the user control name, and click Add.
- Open the ActivitiesSearchResult.ascx file, if not already opened, and add the bolded code that follows:
(Code Snippet – PlanMyNight MVC App – ActivitiesSearchResultSearchResultDiv)
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %> <div id="searchResultsStatic" class="panel searchResults"> <% var model = ViewData["PagingResult"] as PagingResult<Activity>; %> <% var searchCriteria = ViewData["SearchCriteria"] as ActivitySearchCriteria; %> <div class="innerPanel"> <h2> <ul> <li><strong>Activities</strong></li> </ul> </h2> <div> </div> </div> </div>
Note:NOTE: In the code above, you have defined two variables, model and searchCriteria. These variables were provided through the ViewData dictionary and will store the search results (model) and the search criteria (searchCriteria.) This information will be provided by the HomeController and used to render the results by the Views. - To provide the end-user with sorting functionality Add the sorting controls to
ActivitiesSearchResult user control. To do this, proceed as follows.
- Insert the bolded code that follows inside the <div> tag at the bottom of
ActivitiesSearchResult user control:
(Code Snippet – PlanMyNight MVC App – ActivitiesSearchResult SortingControls)
<div> <% if(searchCriteria != null) { %> <div class="subheader"> Sort by: <% if(searchCriteria.SortBy == SortCriteria.Name) { %> <strong>Name</strong> <% } else { %> <%=Html.ActionLink("Name", "Search", new ActivitySearchCriteria { SortBy = SortCriteria.Name, State = searchCriteria.State, City = searchCriteria.City, StreetAddress = searchCriteria.StreetAddress, Zip = searchCriteria.Zip, ActivityTypeId = searchCriteria.ActivityTypeId })%> <% } %> | <% if(searchCriteria.SortBy == SortCriteria.Rating) { %> <strong>Rating</strong> <% } else { %> <%=Html.ActionLink("Rating", "Search", new ActivitySearchCriteria { SortBy = SortCriteria.Rating, State = searchCriteria.State, City = searchCriteria.City, StreetAddress = searchCriteria.StreetAddress, Zip = searchCriteria.Zip, ActivityTypeId = searchCriteria.ActivityTypeId })%> <% } %> | <% if(searchCriteria.SortBy == SortCriteria.ActivityType) { %> <strong>Type</strong> <% } else { %> <%=Html.ActionLink("Type", "Search", new ActivitySearchCriteria { SortBy = SortCriteria.ActivityType, State = searchCriteria.State, City = searchCriteria.City, StreetAddress = searchCriteria.StreetAddress, Zip = searchCriteria.Zip, ActivityTypeId = searchCriteria.ActivityTypeId })%> <% } %> </div> <% } %> </div>
Note:The code above renders the different sorting options (Rating, Type and Name.) If the items are sorted using the sorting criteria which is being rendered, the view will render the sort option in a <strong> tag; otherwise it will render an ActionLink. The ActionLink invokes the HomeController’sSearch method, maintaining the search criteria but with the updated sorting option. - Insert the bolded code that follows inside the <div> tag at the bottom of
ActivitiesSearchResult user control:
- To render the search results into the view, add the bolded code that follows after the sorting controls in
ActivitiesSearchResult.ascx.
(Code Snippet – PlanMyNight MVC App – ActivitiesSearchResult SearchResults)
</div> <% } %> <div class="items"> <% if(model == null) %> <% { %> <h3>Please provide a search criteria...</h3> <% } else { %> <% if(model.TotalItems == 0) %> <% { %> <h3>No activities found...</h3> <% } else {%> <ul class="activities"> <% foreach(var activity in model.Items) %> <% { %> <li> <% var rating = activity.Rating ?? 0; rating = Math.Round(rating * 2) / 2; %> <span class="off id"><%=Html.Encode(activity.Id)%></span> <h3> <% if (rating > 0) { %> <span class="rating rating_<%=rating.ToString("0.0").Replace(".", "_")%>"><span>Rating: </span><%=rating.ToString("0.0")%></span> <%} %> <%=Html.ActionLink(activity.Name, "Details", "Activities", new { id = activity.Id }, null)%> </h3> <p><%=Html.Encode(activity.Street)%> | <%=Html.Encode(activity.City)%>, <%=Html.Encode(activity.State)%> <%=Html.Encode(activity.Zip)%> | Phone: <%=Html.Encode(activity.PhoneNumber)%></p> </li> <% } %> </ul> <% } %> <% } %> </div> </div>
Note:The code above provides three different render options. 1. - If the model is null, it means that no search was done. In that case, it will render a message asking the user to provide search criteria. 2. - If the model has no items, it means that no activities matching the search criteria were found. In that case, it will render a message informing the user that no activities were found. 3. - Otherwise it will render the search results. Notice that the code has an ActionLink which invokes ActivitiesController’s Details method. This method searches for the details of a given activity and returns the Details view populated the information found. - To Complete the ActivitiesSearchResult user control, you will need to add the paging controls. To do so, copy the bolded code below after the code you have just added.
(Code Snippet – PlanMyNight MVC App – ActivitiesSearchResult PagingControls)
</div> <div class="toolbox"> <% if (model != null && model.TotalPages > 0) %> <% { %> <div class="pager"> <% if(model.CurrentPage == 1) %> <% { %> <strong>«</strong> <% } else { %> <%=Html.ActionLink("«", "Search", new ActivitySearchCriteria { Page = model.CurrentPage - 1, SortBy = searchCriteria.SortBy, State = searchCriteria.State, City = searchCriteria.City, StreetAddress = searchCriteria.StreetAddress, Zip = searchCriteria.Zip, ActivityTypeId = searchCriteria.ActivityTypeId })%> <% } %> | <% for(int i=1; i<=model.TotalPages; i++) %> <% { %> <% if(i == model.CurrentPage) { %> <strong><%=i%></strong> <% } else { %> <%=Html.ActionLink(i.ToString(), "Search", new ActivitySearchCriteria { Page = i, SortBy = searchCriteria.SortBy, State = searchCriteria.State, City = searchCriteria.City, StreetAddress = searchCriteria.StreetAddress, Zip = searchCriteria.Zip, ActivityTypeId = searchCriteria.ActivityTypeId })%> <% } %> | <% } %> <% if(model.CurrentPage == model.TotalPages) %> <% { %> <strong>»</strong> <% } else { %> <%=Html.ActionLink("»", "Search", new ActivitySearchCriteria { Page = model.CurrentPage + 1, SortBy = searchCriteria.SortBy, State = searchCriteria.State, City = searchCriteria.City, StreetAddress = searchCriteria.StreetAddress, Zip = searchCriteria.Zip, ActivityTypeId = searchCriteria.ActivityTypeId })%> <% } %> </div> <% } %> </div> </div>
Note:Note: The code above renders the paging controls. It possible action is rendered as an ActionLink that invokes the HomeController’sSearch method, maintaining the search criteria but with the updated page number. - Add the SearchForm.ascx user control to the solution. This user control is provided as an
Asset for this lab.
- In the Solution Explorer, right-click the Home folder, located under Views
- Point to Add and click Existing Item,
- Browse to the %TrainingKitInstallFolder%\Labs\AspNetMvcPlanMyNight\Source\Assets\Views\Home folder
- Select SearchFrom.ascx user control and click Add.

Figure 7 Adding SearchForm.ascx
To populate SearchForm.ascx’s fields, with the data on your IActivitiesRepository implementation, you will need to provide two sets to it, the ActivityTypes, and States. These values will be provided by the IActivitiesRepository’sRetreiveActivityTypes and RetrieveStates methods.
- Add a new class named ActivitiesSearchFields. To do so, proceed as follows.
- In the Solution Explorer, right-click the PlanMyNight project.
- Point to Add, and click New Folder.
- Change its name to ViewModels.
- Right-click the ViewModels folder.
- Point to Add, and click Class
- Type ActivitiesSearchFields.cs as class Name, and click Add.
- Replace the default class implementation with the following code:
(Code Snippet – PlanMyNight MVC App – ActivitySearchFields Class)
namespace PlanMyNight.ViewModels { using System.Collections.Generic; using System.Web.Mvc; public class ActivitySearchFields { public IEnumerable<SelectListItem> ActivityTypes { get; set; } public IEnumerable<SelectListItem> States { get; set; } } }
- Add the Details view, which will be used to render the details of an activity. This view is provided as an
Asset for this lab.
- In the Solution Explorer, right-click the Views folder.
- Point to Add, and click New Folder.
- Change its name to Activities.
- Right-click the Activities folder.
- Point to Add, and click Existing Item
- Browse to the %TrainingKitInstallFolder%\Labs\AspNetMvcPlanMyNight\Source\Assets\Views\Activities folder.
- Select Details.aspx view and click Add.
To make the view’s code more readable, when using types you avoided including the namespace to it. For the view to find these types, you need to add the namespaces to the Web.Config file.
- In the Solution Explorer, double-click the Web.Config file, located under
PlanMyNight project, to open it.

Figure 8 Web.Config in Solution Explorer
- Scroll down to the <namespaces> tag, located inside <pages>, and add the following bolded code:
<namespaces> <add namespace="System.Web.Mvc"/> <add namespace="System.Web.Mvc.Ajax"/> <add namespace="System.Web.Mvc.Html"/> <add namespace="System.Web.Routing"/> <add namespace="PlanMyNight.Models" /> <add namespace="PlanMyNight.Models.Entities" /> <add namespace="PlanMyNight.Controllers" /> <add namespace="PlanMyNight.ViewModels"/> </namespaces> </pages>
- Press CTRL+SHIFT+B to build the solution.
Task 4 – Creating PlanMyNight Controllers
In this task you will modify HomeController, and add the ActivitiesController in order to handle user interaction.
- In the Solution Explorer, double-click HomeController.cs file, located under the Controllers folder to open it.
- Add the following namespace directives to the file:
(Code Snippet – PlanMyNight MVC App – HomeController Using Directives)
using System.Collections.ObjectModel; using System.ComponentModel.DataAnnotations; using System.Globalization; using System.Text; using PlanMyNight.Models; using PlanMyNight.Models.Entities; using PlanMyNight.ViewModels;
- Add the following class variables and properties to HomeController:
(Code Snippet – PlanMyNight MVC App – HomeController VariablesAndProperties)
private const int DefaultPageSize = 5; private readonly IActivitiesRepository activitiesRepository; private IEnumerable<ActivityType> activityTypes; private IEnumerable<ActivityType> ActivityTypes { get { if (activityTypes == null) { this.activityTypes = this.activitiesRepository.RetrieveActivityTypes(); } return this.activityTypes; } } - Add a constructor method with an IActivitiesRepository as parameter, and set the activitiesRepository
class property with the received values.
(Code Snippet – PlanMyNight MVC App – HomeController Constructors)
public HomeController() : this(new ActivitiesStubRepository()) { } public HomeController(IActivitiesRepository activitiesRepository) { this.activitiesRepository = activitiesRepository; }
Note:Notice that you have also added the default constructor which creates a new instance of ActivitiesStubRepository and invokes the added constructor with it as parameter. This is a good practice, since it will let you mock the repository when testing, and provides an extensibility point for your application. - In the HomeController, add a method named InjectActivitySearchFields, and add the following behavior to it.
(Code Snippet – PlanMyNight MVC App – HomeController InjectActivitySearchFields)
private void InjectActivitySearchFields(IDictionary<string, object> viewData, ActivitySearchCriteria searchCriteria) { var types = this.ActivityTypes.Select(o => new SelectListItem { Text = o.Name, Value = o.Id.ToString(), Selected = (searchCriteria != null && searchCriteria.ActivityTypeId.HasValue && o.Id == searchCriteria.ActivityTypeId.Value) }).ToList(); types.Insert(0, new SelectListItem { Text = "Any activity", Value = "0" }); var states = this.activitiesRepository.RetrieveStates() .Select(o => new SelectListItem { Text = o, Value = o, Selected = (searchCriteria != null && o == searchCriteria.State) }).ToList(); states.Insert(0, new SelectListItem { Text = "Any state", Value = string.Empty }); viewData["SearchFields"] = new ActivitySearchFields { ActivityTypes = types, States = states, }; }
Note:InjectActivitySearchFields method creates an instance of ActivitySearchFields, it adds the ActivityTypes and States available in the activities repository and stores it in the ViewData dictionary for the view to consume. SearchForm user control uses the ActivitySearchFields to populate its controls with data. - In the HomeController, add a method named GetCriteriaDescription,
which will parse the ActivitySearchCriteria into a string.
(Code Snippet – PlanMyNight MVC App – HomeController GetCriteriaDescription)
private string GetCriteriaDescription(ActivitySearchCriteria searchCriteria, string separator = " | ") { StringBuilder title = new StringBuilder(); if (searchCriteria.ActivityTypeId > 0) { title.Append(this.ActivityTypes.Where(a => a.Id == searchCriteria.ActivityTypeId).Select(a => a.PluralizedName).FirstOrDefault()); title.Append(separator); } if (!string.IsNullOrEmpty(searchCriteria.City)) { title.Append(searchCriteria.City); title.Append(separator); } if (!string.IsNullOrEmpty(searchCriteria.State)) { title.Append(searchCriteria.State); title.Append(separator); } if (!string.IsNullOrEmpty(searchCriteria.Zip)) { title.Append(searchCriteria.Zip); title.Append(separator); } title.AppendFormat(CultureInfo.CurrentUICulture, "Page {0}", searchCriteria.Page); return title.ToString(); }
Note:This is a helper method, and simply concatenates the information provided as search criteria, separated by the given separator (| by default.) - Replace HomeController’sIndex method implementation to invoke
InjectActivitySearchFields method before creating the View. To do so, copy the bolded code that follows into the body of the method.
public ActionResult Index() { this.InjectActivitySearchFields(this.ViewData, null); return View("Index"); }To complete the HomeController you will add the Search method. Among other tasks, this method will use the CustomValidator you previously added to ActivitySearchCriteria entity to validate if the search criteria provided is valid or not.
If the validation succeeds, it executes the actual search, pages the result and stores it in the ViewData dictionary for the view to use.
- Add the Search method to the HomeController.cs file:
(Code Snippet – PlanMyNight MVC App – HomeController SearchAction)
public ActionResult Search(ActivitySearchCriteria searchCriteria) { if (searchCriteria.ActivityTypeId.HasValue && searchCriteria.ActivityTypeId == 0) { searchCriteria.ActivityTypeId = null; } // Validation var errors = new Collection<ValidationResult>(); Validator.TryValidateObject(searchCriteria, new ValidationContext(searchCriteria, null, null), errors); if (errors.Any()) { ModelState.AddModelError("ActivitySearchCriteria", "Please provide a search criteria."); return this.Index(); } searchCriteria.PageSize = DefaultPageSize; if (searchCriteria.Page <= 0) { searchCriteria.Page = 1; } // search activities var activities = this.activitiesRepository.Search(searchCriteria); this.ViewData["PagingResult"] = activities; this.ViewData["SearchCriteria"] = searchCriteria; this.InjectActivitySearchFields(this.ViewData, searchCriteria); this.ViewData["CriteriaDescription"] = this.GetCriteriaDescription(searchCriteria, " | "); this.ViewData["KeywordsMetatag"] = this.GetCriteriaDescription(searchCriteria, ", "); return View("Index"); } - Add the ActivitiesController controller to your project. This controller is provided as an
Asset for this lab.
- In the Solution Explorer, right-click the Controllers folder.
- Point to add, and click Existing Item.
- Browse to the %TrainingKitInstallFolder%\Labs\AspNetMvcPlanMyNight\Source\Assets\Controllers folder
- Select ActivitiesController.cs and click Add.

Figure 9 Adding ActivitiesController.cs
Note:The ActivitiesController handles the user actions to a certain activity. It provides the rating functionality, and injects the details of an activity into the Details view.
- Press CTRL+SHIFT+B to build the solution.
Task 5 – Enhancing Views by Adding CSS and Images
In this task you will add CSS and images to enhance the views of your solution, both are provided as Assets to keep the exercise simple.
- Delete Site.css file provided by default
- In the Solution Explorer, right-click Site.css file, located under Content
- Click Delete.
- When prompted, confirm the action by choosing OK.
- Add Site.css file provided as an Asset which contains PlanMyNight’s style sheet.
- In the Solution Explorer, right-click the Content folder.
- Point to Add, and click Existing Item.
- Browse to the %TrainingKitInstallFolder%\Labs\AspNetMvcPlanMyNight\Source\Assets\Content folder.
- Select Site.css and click Add.
- Create the Images folder that will store all the images used in your application.
- In the Solution Explorer, right-click the Content folder.
- Point to Add, and click New Folder.
- Change its name to Images
- Add the images provided as Assets to the Images folder
- In the Solution Explorer, right-click the Images folder, located under Content.
- Point to Add, and click Existing Item.
- Browse to the %TrainingKitInstallFolder%\Labs\AspNetMvcPlanMyNight\Source\Assets\Content\Images folder.
- Select All images and click Add.
- Press CTRL+SHIFT+B to build the solution.