构建 Web 应用程序

概述

本动手实验基于 Plan My Night 演示,使用了以下技术:MVC 2 Framework、Visual Studio 2010、.Net Framework 4.0 和 ASP.NET AJAX。

通过整个实验,您将了解将所有这些技术结合使用如此简单,同时又如此功能强大。您将从一个简单的应用程序开始,将对其进行构建,直到拥有一个功能完善的 MVC Web 应用程序。

本动手实验假定开发人员具有 HTML、JavaScript、ASP.NET MVC Framework 和 Entity Framework 方面的基本经验。在该培训套件中,您将找到介绍这些技术的实验。

目标

在本次动手实验中,您将学习如何:

•              基于 PlanMyNight 演示,从头创建 ASP.NET MVC 应用程序

•              从现有数据库创建 Entity Framework 数据模型

•              使用 Entity Framework 作为 MVC 应用程序的存储库

•              向 MVC 应用程序添加一些小 AJAX 功能

               

系统要求

您必须拥有以下工具才能完成本实验:

•             Microsoft Visual Studio 2010

•              Microsoft SQL 2005 或 Microsoft SQL 2008(速成版或更高版本)

•              ASP.NET 4.0 AJAX Preview 5(在本动手实验中,您将找到有关如何下载并安装它的说明)

               

安装

使用 Configuration Wizard 验证本实验的所有先决条件。要确保正确配置所有内容,请按照以下步骤进行。

注意: 要执行安装步骤,您需要使用管理员权限在命令行窗口中运行脚本。

1.            如果之前没有执行,运行 Training Kit 的 Configuration Wizard。为此,运行位于 %TrainingKitInstallationFolder%\Labs\AspNetMvcPlanMyNight\Setup 文件夹下的 CheckDependencies.cmd 脚本。安装先决条件中没有安装的软件(如有必要请重新扫描),并完成向导。

注意: 为了方便,本实验中管理的许多代码都可用于 Visual Studio 代码片段。CheckDependencies.cmd 文件启动 Visual Studio 安装程序文件安装该代码片段。

               

练习

本次动手实验由以下练习组成:

1.            练习 1:创建 ASP.NET MVC 应用程序 PlanMyNight

2.            练习 2:创建 Entity Framework 数据模型

3.            练习 3:添加 AJAX 来搜索活动

               

完成本实验的估计时间:90 分钟。

注意:每个练习都随附了一个 End 文件夹,其中包含完成练习之后应该得到的解决方案。如果需要其他帮助来完成练习,您可以使用该解决方案作为指南。

               

下一步

练习 1:创建 ASP.NET MVC 应用程序 PlanMyNight

               

练习 1:创建 ASP.NET MVC 应用程序 PlanMyNight

在本练习中,您将学习如何在 Visual Studio 2010 中创建 ASP.NET MVC 应用程序。

在第一步中,我们将使用一个存根数据存储库,该存储库在练习 2 中将替换为另一个使用 Entity Framework 作为数据提供程序的存储库。

您还将学习如何向现有 ASP.NET MVC 应用程序中添加分页、筛选和排序功能,以及如何使用 Data Annotation Validators 添加简单的验证。

任务 1 –创建 PlanMyNight ASP.NET MVC Web 应用程序项目

在该任务中,您将使用 MVC Visual Studio 模板创建和配置一个空的 ASP.NET MVC 应用程序项目。

1.            从 Start | All Programs | Microsoft Visual Studio 2010 | Microsoft Visual Studio 2010 启动 Microsoft Visual Studio 2010。

2.            在 File 菜单中,指向 New,然后单击 Project。 

3.            在 New Project 对话框中,确保已选中 .NET Framework 4 ,然后选择 Visual C# | ASP.NET MVC 2 Web Application 项目类型。您可以将位置设置为 %TrainingKitInstallFolder%\Labs\AspNetMvcPlanMyNight\Source\Ex01-CreatingPlanMyNight\begin 文件夹。

4.            将 Name 更改为 PlanMyNight,然后单击 OK。

 

图 1

Create New Project 对话框

5.            在选择 OK 按钮后,系统将提示您创建一个测试项目。请选择 No,因为在本实验中您不会创建测试用例。

注意:当您创建新 MVC Web 应用程序时,Visual Studio 将让您选择是否同时创建两个项目。第一个项目是 Web 项目,您可以在这里实现您的应用程序。第二个项目是一个测试项目,您可以在这里为您的 MVC 组件编写单元测试。

同时创建这两个项目并测试整个应用程序是一种不错的做法。在本实验中,为了保持简单性,我们将避免进行测试。

6.            配置网站以使用端口 50000。

a.            为此,在 Solution Explorer 中,右键单击 PlanMyNight 项目,然后在上下文菜单中选择 Properties。

b.            在 Property 页中,打开 Web 选项卡。

c.             在 Servers 部分中,选择 Specific Port。

d.            将端口号设置为 50000。

e.            按 Ctrl + S 保存更改。

 

图 2

指定端口

               

任务 2 –创建 PlanMyNight 模型

在本任务中,您将创建 PlanMyNight 实体和数据模型。另外,您将了解如何在 PlanMyNight 应用程序中实现分页、验证、筛选和排序功能。

1.            在 Solution Explorer 中,右键单击 Models 文件夹,然后添加一个名为 Entities 的新模型。

2.            创建一个名为 Activity 的类,该类将成为您的应用程序的主要实体。

a.            右键单击 Entities 文件夹。

b.            指向 Add 并单击 Class。

c.             键入 Activity.cs 作为类的 Name,然后单击 Add。

d.            将类的默认实现替换为以下代码:

(代码片段 – PlanMyNight MVC 应用程序– Activity 类)

C#

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; }

    }

}

注意: 活动将是您应用程序中的主要实体。

用户将执行一次搜索,为活动存储库(稍后您将在本练习中创建该存储库)提供某些搜索条件,以便筛选并返回与其匹配的活动组。

活动 将被映射到特定的类型 (ActivityTypeId)。为此,您将使用一个类来包装与活动类型相关的信息。

3.            创建一个名为 ActivityType 的类。

a.            在 Solution Explorer 中,右键单击 Entities 文件夹。

b.            指向 Add 并单击 Class。

c.             键入 ActivityType.cs 作为类名称,然后单击 Add。

d.            将类的默认实现替换为以下代码:

(代码片段 – PlanMyNight MVC 应用程序– ActivityType 类)

C#

namespace PlanMyNight.Models.Entities

{

    public partial class ActivityType

    {

        public int Id { get; set; }

   

        public string Name { get; set; }

   

        public string PluralizedName { get; set; }

    }

}

4.            创建一个名为 SortCriteria 的枚举器,该枚举器将由活动存储库用来确定如何对结果进行排序。

a.            在 Solution Explorer 中,右键单击 Models 文件夹。

b.            指向 Add 并单击 Class。

c.             键入 SortCriteria.cs 作为枚举器名称,然后单击 Add。

d.            将类的默认实现替换为以下代码:

(代码片段 – PlanMyNight MVC 应用程序– SortCriteria 枚举器)

C#

namespace PlanMyNight.Models

{

    public enum SortCriteria

    {

        Name = 0,

        ActivityType = 1,

        Rating = 2

    }

}

注意:虽然我们只定义了三个排序值,但通过使用枚举器来标识排序条件,可以为您的应用程序提供扩展点。以后,如果需要,您可以轻松地添加新的排序条件。

为了管理由用户提供的搜索条件,您将创建 ActivitySearchCriteria 类来包装所有筛选参数。

5.            创建一个名为 ActivitySearchCriteria 的类。

a.            在 Solution Explorer 中,右键单击 Entities 文件夹。

b.            指向 Add 并单击 Class。

c.             键入 ActivitySearchCriteria.cs 作为类名称,然后单击 Add。

d.            将类的默认实现替换为以下代码:

(代码片段 – PlanMyNight MVC 应用程序– ActivitySearchCriteria 类)

C#

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; }

    }

}

注意: ActivitySearchCriteria 包含一个 SortCriteria 类型的属性。活动存储库通过该属性获知如何对结果进行排序。

因为 ActivitySearchCriteria 将包含由用户提供的信息,所以向其添加验证是一种好的做法。为此,您将通过 Data Annotation Validators 使用 Custom Validator。

注意:有关在 ASP.NET MVC 应用程序中使用 Data Annotation Validators 的详细信息,请访问:https://www.asp.net/learn/mvc/tutorial-39-cs.aspx

6.            打开 ActivitySearchCriteria.cs 文件(如果尚未打开)并将类签名替换为如下形式:

(代码片段 – PlanMyNight MVC 应用程序– ActivitySearchCriteria 类头)

C#

using System.ComponentModel.DataAnnotations;

    [CustomValidation(typeof(ActivitySearchCriteria), "IsValid")]

    public class ActivitySearchCriteria

注意:使用 CustomValidation 属性可以为实体定义一个自定义验证方法。在本例中,该方法将命名为 IsValid。

7.            通过复制 PageSize 属性下的以下代码,将 IsValid 方法添加到 ActivitySearchCriteria.cs。

(代码片段 – PlanMyNight MVC 应用程序– ActivitySearchCriteria IsValid 方法)

C#

        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.");

            }

        }

注意:自定义验证方法必须返回一个 ValidationResult。

如果未发现错误,则返回 ValidationResult.Success;否则,将返回一个新的 ValidationResult 实例,该实例包含通过构造函数提供的错误消息。

要在模型中实现分页功能,您将创建从活动存储库中返回的活动列表的包装器。该包装器将向模型中添加与分页相关的信息。

8.            创建一个名为 PagingResult 的类。

a.            在 Solution Explorer 中,右键单击 Entities 文件夹。

b.            指向 Add 并单击 Class。

c.             键入 PagingResult.cs 作为类名称,然后单击 Add。

d.            将类的默认实现替换为以下代码:

(代码片段 – PlanMyNight MVC 应用程序– PagingResult 类)

C#

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; }

    }

}

注意:PagingResult 将由 HomeController 使用,以便通过在调用搜索方法时向活动存储库中发送与分页相关的信息,在结果内部后退一页或前进一页。

下一步是创建存储库接口。在本练习中,该接口将由存根存储库实现,在练习 2 中,该接口将由 Entity Framework 活动存储库实现。

9.            创建一个名为 IActivitiesRepository 的接口。为此,执行以下步骤:

a.            在 Solution Explorer 中,右键单击 Models 文件夹

b.            指向 Add 并单击  New Item。

c.             选择 Visual C# 下的 Interface。

d.            键入 IActivitiesRespository.cs 作为类名称,然后单击 Add。

e.            将接口的默认实现替换为以下代码:

(代码片段– PlanMyNight MVC 应用程序– IActivitiesRepository 接口)

C#

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);

    }

}

注意: The IActivitiesRespository 接口具有 5 个方法:

- Retrieve:此方法返回与提供的 id 相匹配的活动。它用于检索单个活动的信息。

- Search:此方法返回一个 PagingResult 实例,它包含与搜索条件(包括排序和分页信息)匹配的经过筛选的活动集。

- RetrieveActivityTypes 和 RetrieveStates:这些方法用于从存储库中存储的活动检索元信息。此信息将用于将数据注入到搜索表单中,并填充字段供用户进行选择。

- RateActivity:使用此方法可以评定给定活动的等级。它是唯一会修改数据模型的方法。

在创建所有实体之后,接下来您将添加 IActivitiesRespository 接口的存根实现。为保持练习的简单性,本动手实验在 Assets 文件夹中提供了此实现。

10.          从 Assets 文件夹添加 ActivitiesStubRepository.cs 类实现。为此,执行以下步骤。

a.            在 Solution Explorer 中,右键单击 Models 文件夹。

b.            指向 Add 并单击 Existing Item。

c.             浏览到 %TrainingKitInstallFolder%\Labs\AspNetMvcPlanMyNight\Source\Assets\Models 文件夹。

d.            选择 ActivitiesStubRespository.cs 和 SearchHelper.cs 类,然后单击 Add。

 

图 3

添加 ActivitiesStubRepository 实现

注意:ActivitiesStubRepository 类实现 IActivityRepository 接口,并使用两个列表来模拟数据库表。因为创建该存储库的目的是测试站点的功能,所以我们将数据硬编码到构造函数方法中。

11.          按 CTRL+SHIFT+B 生成解决方案。

现在您已拥有完整的应用程序模型。您的解决方案应该如下所示:

 

图 4

Solution Explorer 模型结构

               

任务 3 –创建 PlanMyNight 视图

在本任务中,您将修改现有视图并创建新的新视图,以便向用户显示信息,并且获得其输入以便对活动进行搜索和评级。

1.            在 Solution Explorer 中,双击 Site.Master 文件(位于 Views\Shared 下)将其打开。

 

图 5

Solution Explorer 中的 Site.Master

2.            替换样式表定义,将以下粗体显示的代码添加到 Site.Master 的 header 标记内部。

ASP.NET

<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" />

</Header>

注意:在 meta 标记中,站点的字符编码被定义为 UTF-8。

HtmlHeadContent asp:ContentPlaceHolder 将用于定义包含与搜索相关的信息的 keywords meta 标记。该 meta 标记由基于爬网程序的搜索引擎在索引网站时使用。

3.            用以下粗体显示的代码替换 Site.Master 的 body 定义:

(代码片段– PlanMyNight MVC 应用程序– SiteMaster BodyDefinition)

ASP.NET

<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>

注意:刚才添加的代码具有两个主要的部分。

在第一个部分 header 中,您定义了徽标和导航栏。请注意,导航栏具有一个 Html.ActionLink,该链接调用 HomeController 的 Index 方法,并在页面上显示“search”。

在第二个部分 pageWrapper 中,您为 MainContent 定义了 ContentPlaceHolder。对第二个部分所做的更改仅用于设置样式,稍后将在本练习中添加这些更改。

Index.aspx 页将定义 Site.Master 的三个 ContentPlaceHolder 的内容。

4.            在 Solution Explorer 中,双击 Index.aspx 文件(位于 Views/Home 文件夹中)将其打开。

 

图 6

Solution Explorer 中的 Index.aspx

5.            添加 HtmlHeadContent ContentPlaceholder 的定义。为此,将粗体显示的代码粘贴到 TitleContent 内容定义之前。

ASP.NET

<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">

注意:if 语句检查是否提供了 KeywordsMetatag;如果已提供,则定义名为 keywords 的 meta 标记并设置它的内容。如前面所解释的那样,该标记由基于爬网程序的搜索引擎用来索引您的网站。

6.            将 TitleContent 内容定义替换为以下代码,以根据所使用的搜索条件提供不同的 PageTitle。

ASP.NET

<asp:Content ID="indexTitle" ContentPlaceHolderID="TitleContent" runat="server">

    Plan My Night - <%= ViewData.ContainsKey("CriteriaDescription") ?ViewData["CriteriaDescription"].ToString() :"Search Activities"%>

</asp:Content>

要提供 MainContent 内容,使用两个 MVC 视图用户控件:SearchForm 和 ActivitiesSearchResults。要呈现这些控件,使用 Html.RenderPartial 帮助程序方法。

7.            将 MainContent 内容定义替换为以下粗体显示的代码。

ASP.NET

<asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">

<% Html.RenderPartial("SearchForm"); %>

<hr />

<% Html.RenderPartial("ActivitiesSearchResults"); %>

</asp:Content>

注意:SearchForm 是用于捕获搜索参数输入的视图,而 ActivitiesSearchResults 将用于呈现搜索结果。

8.            创建 ActivitiesSearchResults MVC 2 视图用户控件。为此,执行以下步骤。

a.            在 Solution Explorer 中,右键单击 Home 文件夹(位于 Views 内)。

b.            指向 Add 并单击  New Item。

c.             在 Visual C#\Wev\MVC 下选择 MVC 2 View User Control。

d.            键入 ActivitiesSearchResults.ascx 作为用户控件名称,然后单击 Add。

9.            打开 ActivitiesSearchResult.ascx 文件(如果尚未打开),然后添加以下粗体显示的代码:

(代码片段 – PlanMyNight MVC 应用程序– ActivitiesSearchResult SearchResultDiv)

ASP.NET

<%@ 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>

注意:在上面的代码中,您定义了两个变量:model 和 searchCriteria。这些变量通过 ViewData 字典提供,并且将存储搜索结果 (model) 和搜索条件 (searchCriteria)。

此信息将由 HomeController 提供并且由 Views 用来呈现结果。

10.          要向最终用户提供排序功能,向 ActivitiesSearchResult 用户控件添加排序控件。为此,执行以下步骤。

a.            将以下粗体显示的代码插入到 ActivitiesSearchResult 用户控件底部的 <div> 标记中。

(代码片段 – PlanMyNight MVC 应用程序– ActivitiesSearchResult 排序控件)

ASP.NET

        <div>

            <% if(searchCriteria != null) { %>

3.            将以下类变量和属性添加到 HomeController:

(代码片段 – PlanMyNight MVC 应用程序– HomeController 变量和属性)

C#

        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;

            }

        }

4.       添加一个用 IActivitiesRepository 作为参数的构造函数方法,并将 activitiesRepository 类属性设置为收到的值。

(代码片段 – PlanMyNight MVC 应用程序– HomeController 构造函数)

C#

        public HomeController() :

            this(new ActivitiesStubRepository())

        {

        }

        public HomeController(IActivitiesRepository activitiesRepository)

        {

            this.activitiesRepository = activitiesRepository;         

        }

注意:请注意,您还添加了默认构造函数,该构造函数创建 ActivitiesStubRepository 的一个新实例,并使用它作为参数来调用所添加的构造函数。这是一种不错的做法,因为它将使您可以在测试时模拟存储库,并为您的应用程序提供一个扩展点。

5.            在 HomeController 中,添加一个名为 InjectActivitySearchFields 的方法,然后向其添加以下行为。

(代码片段 – PlanMyNight MVC 应用程序– HomeController InjectActivitySearchFields)

C#

        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,

            };

        }

注意: InjectActivitySearchFields 方法创建 ActivitySearchFields 的一个实例,它添加活动存储库中可用的 ActivityTypes 和 States,并将其存储在 ViewData 字典中以供视图使用。

SearchForm 用户控件通过 ActivitySearchFields 来用数据填充它的控件。

6.            在 HomeController 中,添加一个名为 GetCriteriaDescription 的方法,该方法会将 ActivitySearchCriteria 解析为字符串。

(代码片段 – PlanMyNight MVC 应用程序– HomeController GetCriteriaDescription)

C#

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();

        }

注意:这是一个帮助程序方法,它连结作为搜索条件提供的信息,并使用给定的分隔符分隔搜索条件(默认情况下为 |)。

7.            替换 HomeController 的 Index 方法实现,在创建视图之前调用 InjectActivitySearchFields 方法。为此,将以下粗体显示的代码复制到方法体中。

C#

        public ActionResult Index()

        {

            this.InjectActivitySearchFields(this.ViewData, null);

            return View("Index");

        }

要完成 HomeController,您将添加 Search 方法。除了其他任务以外,该方法还将使用您先前添加到 ActivitySearchCriteria 实体的 CustomValidator 来验证所提供的搜索条件是否有效。

如果验证通过,它将执行实际的搜索,对结果进行分页,并将其存储到 ViewData 字典中以供视图使用。

8.            将 Search 方法添加到 HomeController.cs 文件中:

(代码片段 – PlanMyNight MVC 应用程序– HomeController SearchAction)

C#

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");

        }

9.            将 ActivitiesController 控制器添加到您的项目中。此控制器已作为本实验的资产提供。

a.            在 Solution Explorer 中,右键单击 Controllers 文件夹。

b.            指向 add,然后单击 Existing Item。

c.             浏览到 %TrainingKitInstallFolder%\Labs\AspNetMvcPlanMyNight\Source\Assets\Controllers 文件夹。

d.            选择 ActivitiesController.cs ,然后单击 Add。

 

图 9

添加 ActivitiesController.cs

注意:ActivitiesController 处理针对特定活动的用户操作。它提供了评级功能,并且将活动的详细信息注入到 Details 视图中。

10.          按 CTRL+SHIFT+B 生成解决方案。

               

任务 5 –通过添加 CSS 和图像增强视图

在本任务中,您将添加 CSS 和图像来增强解决方案的视图,两者都已作为资产提供,以保持练习的简单性。

1.            删除默认提供的 Site.css 文件。

a.            在 Solution Explorer 中,右键单击 Site.css 文件(位于 Content 下)。

b.            单击 Delete。

c.             出现提示时,选择 OK 确认操作。

2.            添加作为资产提供的 Site.css 文件,该文件包含 PlanMyNight 的样式表。

a.            在 Solution Explorer 中,右键单击 Content 文件夹。

b.            指向 Add,并单击 Existing Item。

c.             浏览到 %TrainingKitInstallFolder%\Labs\AspNetMvcPlanMyNight\Source\Assets\Content 文件夹。

d.            选择 Site.css,然后单击 Add.

3.            创建 Images 文件夹以存储您的应用程序中使用的所有图像。

a.            在 Solution Explorer 中,右键单击 Content 文件夹。

b.            指向 Add,并单击 New Folder。

c.             将其名称更改为 Images。

4.            将作为资产提供的图像添加到 Images 文件夹。

a.            在 Solution Explorer 中,右键单击 Images 文件夹(位于 Content 下)。

b.            指向 Add,并单击 Existing Item。

c.             浏览到 %TrainingKitInstallFolder%\Labs\AspNetMvcPlanMyNight\Source\Assets\Content\Images 文件夹。

d.            选择所有图像,然后单击 Add。

5.            按 CTRL+SHIFT+B 生成解决方案。

               

后续步骤

练习 1:验证

练习 1:验证

在本验证过程中,您将通过运行 PlanMyNight ASP.NET MVC 应用程序、搜索活动并对其进行评级来确保您已经正确执行了该练习的所有步骤。

1.            按 CTRL + F5 在不调试的情况下运行程序。

 

图 10

PlanMyNight HomePage

2.            选择 OH 作为州并单击 Search。存根存储库将返回与给定的搜索约束相匹配的活动。

3.            单击 Activity 名称,将出现 Details 视图,其中显示与您已经选择的活动有关的所有信息,并使您可以对其进行评级。

 

图 11

搜索结果

4.            要对活动进行评级,选择所需的评级值,然后单击评级链接。

 

图 12

对活动评级

注意:评级链接是一个 ActionLink,它将用选定的评级作为参数来调用 ActivitiesController 评级方法。

5.            最后,转至主页,并重新在 OH 中搜索活动。

 

图 13

在搜索结果中显示的评级

注意:请注意,评级是持续性的。因为存根存储库是内存型存储库,所以更改将不会持续生效;如果停止 ASP.Net 开发服务器,更改将丢失。

6.            随意使用应用程序并测试排序、分页、验证和筛选功能。

               

下一步

练习 2:创建 Entity Framework 数据模型

               

练习 2:创建 Entity Framework 数据模型

在本练习中,您将学习如何将 Entity Framework 数据模型添加到现有应用程序中,以及如何创建存储库来对该数据模型中的项进行筛选、分页和排序。

任务 1 –创建 Entity Framework 数据模型

在本任务中,您将创建一个 Entity Framework 数据模型,并使用它提供的实体来替换在练习 1 中创建的实体。

1.            从 Start | All Programs | Microsoft Visual Studio 2010 启动 Microsoft Visual Studio 2010。

注意: 要将数据库添加到 SQL,需要以管理员权限启动 Visual Studio。为此,右键单击开始菜单中的链接并选择 Run as Administrator。

2.            打开文件夹 %TrainingKitInstallFolder%\Labs\AspNetMvcPlanMyNight\Source\Ex02-CreatingEFDataModel\Begin 中的解决方案文件 Begin.sln 并生成解决方案。也可以继续使用上一个练习完成时获得的解决方案。

3.            移除存根实体,以便您可以用 Entity Framework 数据模型的实体替换它们。

a.            在 Solution Explorer 中,选择 Activity.cs 和 ActivityType.cs(位于 Model\Entities 文件夹中)。

b.            右键单击所选文件之一,然后单击 Delete。

c.             出现提示时,单击 OK 确认删除。

 

图 14

在 Solution Explorer 中删除实体

4.            创建数据模型

a.            在 Solution Explorer 中,右键单击 Entities 文件夹(位于 Models 下)。

b.            指向 Add,并单击  New Item。

c.             选择 Visual C# 下的 ADO.NET Entity Data Model。

d.            键入 PlanMyNight.edmx 作为该数据模型的 Name,然后单击 Add。

注意:选择之后会弹出一个向导弹出菜单,以使您可以配置连接字符串以及要映射到哪些表、视图和存储过程。

e.            在 Configuration Wizard 中,选择 Generate from database,然后单击 Next。

f.             单击 New Connection 按钮创建一个新的数据库连接。

i.              选择 Microsoft SQL Server Database File (SqlClient) 作为数据源。

ii.             单击 Browse 按钮设置要使用的数据库文件名。

iii.            浏览到 %TrainingKitInstallFolder%\Labs\AspNetMvcPlanMyNight\Source\Assets\App_Data 文件夹。

iv.           选择 PlanMyNight.mdf。

v.            单击 OK 保存连接。

 

图 15

New Connection 对话框

g.            保留其他默认设置,然后单击 Next。

h.            将出现提示,询问您是否想要将文件复制到您的项目目录内并相应地更改连接字符串。单击 Yes 继续。

i.              在 Choose your database objects 对话框中,展开 Tables 项。

j.             选中 Activity 和 ActivityType 表。

注意:您在此步骤中选中的表将被映射为您的数据模型中的实体。

k.            键入 PlanMyNight.Models.Entities 作为命名空间,然后单击 Finish。

注意:有关 Entity Framework 的详细信息,请访问:https://msdn.microsoft.com/en-us/library/bb386876.aspx

5.            按 CTRL+SHIFT+B 生成解决方案。

注意:如果编译过程显示与 Activity 或 ActivityType 类相关的错误,打开 ADO.NET Entity Data Model 文件 PlanMyNight.edmx 并按 CTRL+S,保存图表并强制重新生成 PlanMyNight.Designer.cs,从而创建实体类。

               

任务 2 –创建 ActivitiesRepository

在本任务中,您将创建一个 ActivitiesRepository,它使用 ADO.NET Entity Data Model 作为数据源。您还将向其添加筛选、分页和排序功能。

1.            添加一个名为 ActivitiesRepository.cs 的新类。

注意: ActivitiesRepository 将拥有与 ActivitiesStubRepository 相同的功能,但它不是内存型存储库,而是使用 SQL(通过 Entity Framework)并且是持久性的。

a.            在 Solution Explorer 中,右键单击 Models 文件夹。

b.            选择 Add,然后选择 Class。

c.             将其命名为 ActivitiesRepository.cs,然后单击 Add。

d.            替换类头以实现 IActivitiesRepository。

C#

public class ActivitiesRepository :IActivitiesRepository

e.            将以下命名空间指令添加到文件中:

C#

using PlanMyNight.Models.Entities;

在创建 ActivitiesRepository 之后,您将必须实现 IActivitiesRepository 的下列方法:Retrieve、Search、RetrieveActivityTypes、RetrieveStates 和 RateActivity。

2.            添加一个名为 Retrieve 的方法,该方法将返回与指定的 Id 相匹配的 Activity。

(代码片段 – PlanMyNight MVC 应用程序– ActivitiesRepository Retrieve 方法)

C#

        public Activity Retrieve (string id)

        {

            using (var context = new PlanMyNightEntities())

            {

                return context.Activities.Where(a => a.Id == id).SingleOrDefault();

            }

        }

注意:由于在存根存储库中,您使用了集合来存储活动,因此每个方法的代码实际上是类似的。其差别主要存在于信息的来源方面。在 ActivitiesRepository 中,您使用 ADO.NET Entities DataModel 上下文,而 ActivitiesStubRepository 使用存储在内存中的集合。

3.            添加一个名为 RetrieveActivityTypes 的方法,该方法将返回数据库中的所有 ActivityTypes。

(代码片段 – PlanMyNight MVC 应用程序– ActivitiesRepository RetrieveActivityTypes 方法)

C#

        public IEnumerable<ActivityType> RetrieveActivityTypes()

        {

            using (var context = new PlanMyNightEntities())

            {

                return context.ActivityTypes.ToList();

            }

        }

4.            添加一个名为 RetrieveStates 的方法,该方法将返回 Activities 中的所有不同 States。

(代码片段 – PlanMyNight MVC 应用程序– ActivitiesRepository RetrieveStates 方法)

C#

        public IEnumerable<string> RetrieveStates()

        {

            using (var context = new PlanMyNightEntities())

            {

                return context.Activities.Select(a => a.State).Distinct().ToList();

            }

        }

5.            添加一个名为 RateActivity 的方法,该方法将更新 Activity 的评级。

(代码片段 – PlanMyNight MVC 应用程序– ActivitiesRepository RateActivity 方法)

C#

        public void RateActivity(string activityId, byte rating)

        {

            using (var context = new PlanMyNightEntities())

            {

                var activity = context.Activities.Where(a => a.Id == activityId).SingleOrDefault();

                if (activity != null)

                {

                    if (activity.Rating == null)

                    {

                        activity.Rating = rating;

                        activity.RatingCount++;

                    }

                    else

                    {

                        activity.Rating = (activity.Rating * activity.RatingCount + rating) / ++activity.RatingCount;

                    }

                    context.SaveChanges();

                }

            }

        }

注意: RateActivity 检索要更新的活动,更新该活动,然后调用 savesChanges() 方法来更新数据源。

在更新数据库中的资产时,需要考虑一些并发事项,而这超出了本实验的范围。有关详细信息,请访问:https://msdn.microsoft.com/en-us/library/bb738618.aspx

6.            添加一个名为 Search 的方法,该方法将搜索与特定条件相匹配的活动。

(代码片段 – PlanMyNight MVC 应用程序– ActivitiesRepository Search 方法)

C#

        public PagingResult<Activity> Search(ActivitySearchCriteria criteria)

        {

            using (var context = new PlanMyNightEntities())

            {

                IEnumerable<Activity> query = SearchHelper.ActivitySearchQuery(criteria, context.Activities);

                switch (criteria.SortBy)

                {

                    case SortCriteria.ActivityType:

                        query = query.OrderBy(a => a.ActivityTypeId).ThenBy(a => a.Name);

                        break;

                    case SortCriteria.Rating:

                        query = query.OrderByDescending(a => a.Rating).ThenBy(a => a.Name);

                        break;

                    default:

                        query = query.OrderBy(a => a.Name);

                        break;

                }

                return SearchHelper.PageResults(query, criteria.Page, criteria.PageSize);

            }

        }

注意: Search 方法将搜索操作委派给 SearchHelper。搜索完成后,该方法将对结果进行排序以匹配 SortingCriteria,并最终将分页功能再次委派给 SearchHelper。

7.            按 CTRL+SHIFT+B 生成解决方案。

               

任务 3 –更新控制器以使用 Activities Repository

在本任务中,您将更新 Home 和 ActivitiesController,以使用 ActivitiesRepository 而不是 ActivitiesStubRepository。

1.            在 Solution Explorer 中双击 HomeController.cs 将其打开。

2.            修改默认的构造函数,以创建新的  ActivitiesRepository  实现而不是创建 ActivitiesStubRepository。

C#

        public HomeController() :

            this(new ActivitiesRepository())

        {

        }

3.            在 Solution Explorer 中双击 ActivitiesController.cs 将其打开。

4.            修改默认的构造函数,以创建新的  ActivitiesRepository  实现而不是创建  ActivitiesStubRepository。

C#

        public ActivitiesController() :

            this(new ActivitiesRepository())

        {

        }

注意:请注意更改 Repository 非常简单;这归功于 MVC 模式所提供的非耦合式架构。除了更改默认的构造函数实现以外,无需对视图进行更改,也无需对控制器进行更改。

5.            按 CTRL+SHIFT+B 生成解决方案。

               

后续步骤

练习 2:验证

练习 2:验证

在本次验证中,您将通过检查应用程序是否像使用 ActivitiesStubRepository 一样使用 ActivitiesRepository 来确保您已经正确执行了该练习的所有步骤。

1.            按 CTRL + F5 在不调试的情况下运行程序。

 

图 16

PlanMyNight HomePage

2.            选择 OH 作为州并单击 Search。存储库将返回 Ohio 中的活动列表。

 

图 17

搜索结果

3.            单击某个活动(在其名称上单击),将显示该活动的详细信息。

4.            选择评级值,然后单击 Rate,对活动进行评级。

 

图 18

对活动评级

5.            单击 Navigation Bar 中的 Search 转至主页。

6.            通过选择 OH 作为州并单击 Search 来搜索 Ohio 内的活动。评级将显示在 SearchResults 面板中。

 

图 19

在搜索结果中显示的评级

7.            关闭浏览器并停止 ASP.Net 开发服务器以停止应用程序。

a.            在托盘栏中,右键单击 ASP.Net 开发服务器图标。

b.            选择 Stop。

 

图 20

停止 ASP.Net 开发服务器

8.            按 Ctrl+F5 重新启动应用程序。

9.            选择 OH 作为州并单击 Search。您将看到评级是持久性的。

 

图 21

持久性的评级

10.          随意使用应用程序并测试排序、分页、验证和筛选功能。

下一步

练习 3:添加 AJAX 来搜索活动

               

练习 3:添加 AJAX 来搜索活动

在本练习中,您将学习如何以不引人注目的方式向现有的 ASP.NET MVC 应用程序添加 AJAX,而无需更改视图。

您添加 AJAX 的方式将使应用程序能够在禁用 JavaScript 的情况下继续工作。

任务 1 –添加 ASP.NET 4.0 AJAX Preview 5

在本任务中,您将下载 ASP.NET 4.0 AJAX Preview 5 Client Templates 并且将其添加到您的 PlanMyNight 解决方案中。

除了其他控件和功能以外,ASP.NET 4.0 AJAX Preview 5 还包括将在未来的任务中使用的 Sys.UI.DataView 控件。

注意:有关此预览版的详细信息,请访问:https://aspnet.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=32770

1.            从以下地址下载 ASP.NET 4.0 AJAX Preview 5 源代码:https://aspnet.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=32770

2.            将已下载的 zip 文件提取到 %TrainingKitInstallFolder%\Labs\AspNetMvcPlanMyNight\Source\Assets 文件夹中。

3.            从 Start | All Programs | Microsoft Visual Studio 2010 启动 Microsoft Visual Studio 2010。

注意: 要将数据库添加到 SQL,需要以管理员权限启动 Visual Studio。为此,右键单击开始菜单中的链接并选择 Run as Administrator。

4.            打开文件夹 %TrainingKitInstallFolder%\Labs\AspNetMvcPlanMyNight\Source\Ex02-CreatingEFDataModel\Begin 中的解决方案文件 Begin.sln 并生成解决方案。也可以继续使用上一个练习完成时获得的解决方案。

5.            删除默认提供的 MicrosoftAjax.js 文件。

a.            在 Solution Explorer 中,右键单击 MicrosoftAjax.js 文件(位于 Scripts 文件夹中)。

b.            单击 Delete。

c.             出现提示时,选择 OK 确认操作。

6.            将下载的 MicrosoftAjax.js 和 MicrosoftAjaxTemplates.js 脚本添加到您的解决方案中。

a.            在 Solution Explorer 中,右键单击 Scripts 文件夹。

b.            指向 Add,并单击 Existing Item。

c.             浏览到 %TrainingKitInstallFolder%\Labs\AspNetMvcPlanMyNight\Source\Assets\ 文件夹。

d.            选择 MicrosoftAjax.js 和 MicrosoftAjaxTemplates.js,然后单击 Add。

注意:如果您需要调试 JavaScript,您可能还需要添加 MicrosoftAjax.debug.js 和 MicrosoftAjaxTemplates.debug.js,并改而使用它们。

               

任务 2 –创建 ClientTemplatesSearchResults 视图

在本任务中,您将创建一个新视图,该视图是 ActivitiesSearchResult 视图的“动态版本”。该视图将用于通过 JavaScript 呈现搜索结果。

1.            创建 ClientTemplatesSearchResults MVC 视图用户控件。

a.            在 Solution Explorer 中,右键单击 Home 文件夹(位于 Views 下)。

b.            指向 Add,并单击 New Item。

c.             在 Visual C# 下选择 MVC View User Control。

d.            键入 ClientTemplatesSearchResults.ascx 作为用户控件 Name,然后单击 Add。

注意: ClientTemplatesSearchResults 将包含所有将用于呈现搜索结果的模板。将向这些模板填充数据,并使用 JavaScript 呈现。

2.            打开 ClientTemplatesSearchResults.ascx(如果尚未打开),然后将以下粗体显示的代码添加到文档头下面:

(代码片段 – PlanMyNight MVC 应用程序– ClientTemplatesSearchResults SearchResultDiv)

ASP.NET

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>

<style type="text/css">

</style>

<div id="searchResultsDynamic" class="panel searchResults" style="display:none">

    <div class="innerPanel">

        <h2>

    <ul>

                <li class="linkActivities">

                    <strong>Activities</strong>

</li>

    </ul>

        </h2>

       

        <div id="resultsTemplateContainer">

            <div class="items loading"></div>

            <div class="toolbox"></div>

</div>

</div>

</div>

<%-- Templates --%>

<div style="display:none" id="templates">

    <%-- Activities template --%>

    <div id="dynamicActivitiesResults" style="display:none;">

</div>

</div>

注意:在上面的代码中,您为搜索结果定义了模板容器。您还创建了 resultsTemplateContainer,它将用于呈现 AJAX 预加载程序图像。

3.            将排序控件添加到 ClientTemplatesSearchResults.ascx,以便向最终用户提供排序功能。

a.            将以下代码插入到 dynamicActivitiesResult div 定义的下面:

(代码片段 – PlanMyNight MVC 应用程序– ClientTemplatesSearchResults 排序控件)

ASP.NET

<%-- Activities template --%>

<div id="dynamicActivitiesResults" style="display:none;">

    <div id="dynamicActivitiesSort" class="sys-template subheader">

        Sort by:

        <strong sys:if="$dataItem.orderBy=='Name'">Name</strong>

        <a sys:if="$dataItem.orderBy!='Name'" sys:href="{binding null, convert=sortByConverter, defaultValue=Name}">Name</a>

        |

        <strong sys:if="$dataItem.orderBy=='Rating'">Rating</strong>

        <a sys:if="$dataItem.orderBy!='Rating'" sys:href="{binding null, convert=sortByConverter, defaultValue=Rating}">Rating</a>

        |

        <strong sys:if="$dataItem.orderBy=='ActivityType'">Type</strong>

        <a sys:if="$dataItem.orderBy!='ActivityType'" sys:href="{binding null, convert=sortByConverter, defaultValue=ActivityType}">Type</a>

    </div>

注意:您刚才添加的代码将动态呈现不同的排序选项 (Rating、Type 和 Name)。

如果项使用呈现的排序条件进行排序,则视图将在 <strong> 标记中呈现排序选项;否则,它将呈现一个使用 JavaScript 的 sortByConverter 函数动态生成的链接。

4.            将以下粗体显示的代码添加到排序控件的下面。

(代码片段 – PlanMyNight MVC 应用程序– ClientTemplatesSearchResults 搜索结果)

ASP.NET

        </div>

        <div class="items">

            <h3 id="dynamicActivitiesEmpty" style="display:none">No activities found...</h3>

            <ul id="dynamicActivitiesItems"  class="sys-template activities" >

                <li>

                    <span class="off id">{{ Id }}</span>

                    <h3>

                        <span class="rating"><span>Rating:</span>{binding Rating, convert=ratingConverter}</span>

                        <a sys:href="{binding Id, convert=activityDetailsLinkConverter}">{{ Name }}</a>

                    </h3>

                    <p>{{ Street }} | {{ City }}, {{ State }} {{ Zip }} | Phone:{{ PhoneNumber }}</p>

                </li>

                </ul>

        </div>

注意:添加的代码提供了两个不同的模板选项。

如果 Item 计数为 0,则第一个选项呈现以下消息:“No activities found…”,而第二个选项显示搜索结果。通过 JavaScript 决定显示哪些内容。

                请注意,没有用于呈现消息“Please provide a search criteria...”的模板,这是因为您将使用 JavaScript 验证搜索参数,从而仅当提供了所需参数时才执行搜索请求。

5.            要完成 ClientTemplatesSearchResults 用户控件,请添加将用于呈现分页控件的模板。将以下代码复制到“Items”的结束 <div> 的下面。

(代码片段 – PlanMyNight MVC 应用程序– ClientTemplatesSearchResults 分页控件)

ASP.NET

</div>

<div class="toolbox">

    <div class="pager">

        <span id="dynamicActivitiesPager" class="sys-template">

            <span sys:if="$index==0">

                <strong sys:if="$dataItem.currentPage===1"></strong>

                <a sys:if="$dataItem.currentPage!==1" sys:href="{binding currentPage, convert=previousPageConverter}" class="ajaxSearch"></a>

                |

            </span>

            <strong sys:if="$dataItem.currentPage===$dataItem.pageNumber">{{ pageNumber }}</strong>

            <a sys:if="$dataItem.currentPage!==$dataItem.pageNumber" sys:href="{binding pageNumber, convert=pageConverter}" class="ajaxSearch">{{ pageNumber }}</a>

             |

             <span sys:if="$dataItem.totalPages==$dataItem.pageNumber">

                 <strong sys:if="$dataItem.currentPage===$dataItem.pageNumber"></strong>

                 <a sys:if="$dataItem.currentPage!==$dataItem.pageNumber" sys:href="{binding currentPage, convert=nextPageConverter}" class="ajaxSearch"></a>

             </span>

         </span>

    </div>

</div>

注意:上面的代码提供了用于动态呈现分页控件的模板。链接 URL 将通过 JavaScript 生成,并使用 previousPageConverter、nextPageConverter 和 pageConverter。

6.            修改 Index.aspx 文件以呈现 ClientTemplatesSearchResults 用户控件。

a.            在 Solution Explorer 中,双击 Index.aspx 文件(位于 Views\Home 下)将其打开。

b.            在 IndexContent asp:Content 起始标记之前,粘贴以下粗体显示的代码:

ASP.NET

    <% Html.RenderPartial("ClientTemplatesSearchResults"); %>

</asp:Content>

               

任务 3 –创建搜索脚本

在本任务中,您将创建对搜索操作进行拦截的脚本,并将其替换为 AJAX 请求。

本任务还将创建其他脚本,例如,转换程序和帮助程序函数。

1.            创建 ajax.search.js javascript 文件。

a.            在 Solution Explorer 中,右键单击 Scripts 文件夹。

b.            指向 Add,并单击 New Item。

c.             选择 Web 下的 JScript File。

d.            键入 ajax.search.js 作为 Name,然后单击 Add。

2.            将以下代码粘贴到 ajax.search.js 文件中,将一个代理绑定到应用程序的初始化事件。

(代码片段 – PlanMyNight MVC 应用程序– ajax.search 初始化事件代理)

JScript

var isSearching = false;

Sys.Application.add_init(function() {

    // history

    Sys.Application.set_enableHistory(true);

    Sys.Application.add_navigate(function(sender, e) {

        if (isSearching) return;

        // invoke search

        var criteria = e.get_state();

        var searchUrl = $("#searchForm form")[0].action + "?"+ Sys.Application._serializeState(criteria);

        updateModel(searchUrl, onSearchRequest, onSearchResponse);

        // select fields

        selectedTypeIdOpt = $("#ActivityTypeId option[value=" + criteria.ActivityTypeId + "]");

        selectedStateOpt = $("#State option[value=" + criteria.State + "]");

        if (selectedTypeIdOpt.length == 0) selectedTypeIdOpt = $("#ActivityTypeId option:first-child");

        if (selectedStateOpt.length == 0) selectedStateOpt = $("#State option:first-child");

        selectedTypeIdOpt[0].selected = true;

        selectedStateOpt[0].selected = true;

        $("#City").val(criteria.City ? criteria.City : "");

        $("#Zip").val(criteria.Zip ? criteria.Zip : "");

        document.title = getPageTitle(criteria);

    });

   

    // activity templates

    $create(Sys.UI.DataView, null, null, null, $get("dynamicActivitiesItems"));

    $create(Sys.UI.DataView, null, null, null, $get("dynamicActivitiesPager"));

    $create(Sys.UI.DataView, null, null, null, $get("dynamicActivitiesSort"));

    // auto-search on change

    function autoRefresh() {

        isSearching = false;    // force cancel

        var form = $("#searchForm form");

        var url = form[0].action + "?"+ form.serialize();

        updateModel(url, onSearchRequest, onSearchResponse);

    };

    $("#State").bind("change", autoRefresh);

    $("#ActivityTypeId").bind("change", autoRefresh);

    $("#City").bind("change", autoRefresh);

    $("#Zip").bind("change", autoRefresh);

    $("#StreetAddress").bind("change", autoRefresh);

    // intercept form to prevent from posting, and enable ajax calls instead

    updateModelOnFormSubmit($("#searchForm form"), onSearchRequest, onSearchResponse);

    // intercept pager links to prevent them from loading, and enable ajax calls instead

    $(".searchResults .pager a").each(function() {

        updateModelOnClickLink($(this), onSearchRequest, onSearchResponse)

    });

});

注意:在上面的代码中,您已经将一个代理绑定到您的 ASP.NET MVC 应用程序的初始化事件。

该代理具有三个主要部分:

– 在第一个部分中,它将一个代理绑定到导航属性。当在浏览器中向前和向后浏览时,该代理执行搜索操作。

– 第二部分创建了三个 DataView 控件,这些控件将用于存储搜索结果,并且将 autoRefresh() 函数绑定到 SearchForm 中的所有输入框。

autoRefresh 只是用更新后的参数执行搜索操作。

– 最后,在第三部分中,您将窗体 POST 和分页器链接替换为 AJAX 调用。

3.            创建将在 XML HTTP 请求之前调用的 onSearchRequest 函数。

(代码片段 – PlanMyNight MVC 应用程序– ajax.search onSearchRequest)

JScript

function onSearchRequest(href) {

    if (isSearching) return false;

    // validation

    var criteria = Sys.Application._deserializeState(href.split("?")[1]);

    if (!criteria || (!criteria.StreetAddress && !criteria.State && !criteria.City && !criteria.Zip)) return false;

    isSearching = true;    

    if (!criteria.page) criteria.page = null;

    if (!criteria.ActivityTypeId || criteria.ActivityTypeId == "0") criteria.ActivityTypeId = null;

    Sys.Application.addHistoryPoint(criteria, getPageTitle(criteria));

    // preloading

    $("#dynamicActivitiesEmpty").hide();

    $(".searchResults .items").addClass("loading");

    $(".searchResults .pager").addClass("disabled");

    $("#searchForm .field-validation-error").hide();

    return true;

};

注意:onSearchRequest 将执行简单的验证;如果失败,该函数将取消请求;如果成功,它会为当前搜索添加一个历史记录点并让请求继续进行处理。

4.            创建将在检索和绑定数据之后调用的 onSearchResponse 函数。

(代码片段 – PlanMyNight MVC 应用程序– ajax.search OnSearchResponse)

JScript

function onSearchResponse(data) {

    isSearching = false;

    var orderBy = $("#searchForm input[name=SortBy]").val();

    if (orderBy == "") orderBy = "Name";

    $("#searchResultsStatic").remove();

    $("#searchResultsDynamic").show();

    $("#searchResultsDynamic h2 ul").attr("class", "active");

    // move active template to templates container

    $("#resultsTemplateContainer #dynamicActivitiesResults").remove().appendTo($("#templates"));

    var template = template = $("#dynamicActivitiesResults");

    if (data.TotalItems == 0) $("#dynamicActivitiesEmpty").show();

    // rebind items

    $find("dynamicActivitiesItems").set_data(data.Items);

    // rating labels

    showRatingLabels($("#dynamicActivitiesItems"));

    // rebind pager

    refreshPager("dynamicActivitiesPager", data);

    // rebind sort

    $find("dynamicActivitiesSort").set_data({ orderBy:orderBy, timestamp:new Date() });

    $("#dynamicActivitiesSort a").each(function(ix, el) {

        el.onclick = function() {

            var url = this.href.split("#")[0];     // strip anchors

            var sortBy = url.substring(url.indexOf('SortBy=') + 7).split('&')[0]

            $("#searchForm input[name='SortBy']").val(sortBy);

            updateModel(url, onSearchRequest, onSearchResponse, $find("dynamicSearchResults"));

            return false;

        }

    });

    // remove from templates and set into active container

    template.remove();

    $("#resultsTemplateContainer").empty().append(template);

    template.show();

    // hide loading

    $(".searchResults .items").removeClass("loading").scrollTop(0);

    $(".searchResults .items li").hide().fadeIn();

    $(".searchResults .pager").removeClass("disabled");

};

注意:onSearchResponse 将更新模板,并将 SearchResults、Pager 和 Sorting 控件与从 HomeController中获取的数据进行绑定。

5.            添加 refreshPager 函数,该函数将在搜索完成后,使用从 HomeController 获取的数据更新分页器值和链接。

(代码片段 – PlanMyNight MVC 应用程序– ajax.search RefreshPager)

JScript

function refreshPager(pagerId, resultsData) {

    // pager

    var pages = [];

    for (var i = 1; i <= resultsData.TotalPages; i++) {

        pages.push({ pageNumber:i, currentPage:resultsData.CurrentPage, totalPages:resultsData.TotalPages });

    }

    $find(pagerId).set_data(pages);

    $("#" + pagerId + " a").each(function(ix, el) {

        el.onclick = function() {

            var url = this.href.split("#")[0];     // strip anchors

            updateModel(url, onSearchRequest, onSearchResponse);

            return false;

        }

    });

}

6.            添加 showRatingLabels 函数,该函数将用其评级值的类名称来装饰评级跨度。

(代码片段 – PlanMyNight MVC 应用程序– ajax.search ShowRatingLabels)

JScript

function showRatingLabels(container) {

    container.find("span.rating").each(function() {

        var $el = $(this);

        $el.find("span").remove();

        if ($el.text() == "0.0") {

            $el.remove();

        }

        else {

            $el.addClass("rating rating_" + $el.text().replace(".", "_"));

        }

    });

}

注意:类名称标识了跨度,后者在样式表中用来适当呈现评级图像。

7.            添加 updateModelOnClickLink、updateModelOnFormSubmit 和 updateModel 函数。

(代码片段 – PlanMyNight MVC 应用程序– ajax.search updateModelOnClickLink)

JScript

function updateModelOnClickLink($link, callbackRequest, callbackResponse, targetModel) {

    $link.click(function() {

        var url = this.href.split("#")[0];     // strip anchors

        updateModel(url, callbackRequest, callbackResponse, targetModel);

        return false;

    });

}

(代码片段 – PlanMyNight MVC 应用程序– ajax.search updateModelOnFormSubmit)

JScript

function updateModelOnFormSubmit($form, callbackRequest, callbackResponse, targetModel) {

    $form.bind("submit", function() {

        var url = this.action + '?'+ $(this).serialize();

        updateModel(url, callbackRequest, callbackResponse, targetModel);

        return false;

    });

}

(代码片段 – PlanMyNight MVC 应用程序– ajax.search updateModel)

JScript

function updateModel(url, callbackRequest, callbackResponse, targetModel) {

    if (!callbackRequest(url)) return false;

    $.ajax({

        url:url,

        method:"GET",

        type:this.method,

        dataType:"json",

        beforeSend:function(xhr) { xhr.setRequestHeader("Content-Type", "application/json"); },

        success:function(json) {

            if (targetModel) targetModel.set_data(json);

            callbackResponse(json);

        }

    });

}

注意:updateModel 对给定的 URL 执行 AJAX 调用,并在执行此调用之前调用 onSearchRequest,在收到 JSON Response 之后调用 onSearchResponse。

尽管该函数需要接受在 AJAX 调用之前调用以及在收到响应之后调用的函数作为参数,但仅用 onSearchRequest 和 onSearchResponse 调用它们。

8.            添加以下帮助程序方法:

(代码片段 – PlanMyNight MVC 应用程序– ajax.search HelperMethods)

JScript

// helpers

function getPageTitle(criteria) {

    var descriptions = [];

    if (criteria.ActivityTypeId != null) descriptions.push($('#searchForm').find('#ActivityTypeId option:selected').text());

    if (criteria.City != null) descriptions.push(criteria.City);

    if (criteria.State != null) descriptions.push(criteria.State);

    if (criteria.Zip != null) descriptions.push(criteria.Zip);

    descriptions.push('Page ' + (criteria.page || "1"));

    return 'Plan My Night - Search:' + descriptions.join(' | ');

};

function replaceQueryString(url, param, value) {

    var re = new RegExp("([?|&])" + param + "=.*?(&|$)", "i");

    if (url.match(re))

        return url.replace(re, '$1' + param + "=" + value + '$2');

    else

        return url + '&' + param + "=" + value;

}

注意: getPageTitle 将要显示的搜索条件解析为页标题。

replaceQueryString 将一个查询字符串参数和值插入到 URL 中。

9.            添加以下转换器:

(代码片段 – PlanMyNight MVC 应用程序– ajax.search 转换器)

JScript

// converters

function ratingConverter(value) {

    var val = (Math.round((value * 2)) / 2).toString();

    if (val % 1 == 0) {

        return val.toString() + ".0";

    } else {

        return val.toString()

    }

}

function activityDetailsLinkConverter(value) {

    return baseUrl + "Activities/Details/" + value;

}

function sortByConverter(value, o) {

    var $form = $("#searchForm form");

    var formAction = $form[0].action + "?"+ $form.serialize();

    formAction = replaceQueryString(formAction, "SortBy", o.get_defaultValue());

    return formAction;

}

function pageConverter(value) {

    var $form = $("#searchForm form");

    return $form[0].action + "?"+ $form.serialize() + "&page=" + value;

}

function previousPageConverter(value) {

    return pageConverter(value - 1);

}

function nextPageConverter(value) {

    return pageConverter(value + 1);

}

注意:在上面的代码中,您添加了 6 个不同的转换器来正确地呈现 ClientTemplatesSearchResults.ascx 中的链接或动态添加 CSS 类。

- ratingConverter:转换评级值以匹配评级类的格式。(如果值为整数,则添加一个“.0”。)

- activityDetailsLinkConverter:创建在向活动详细信息视图中呈现链接时使用的 URL。

- sortByConverter:创建在呈现排序链接时使用的 URL。

- pageConverter:创建在呈现分页控件时使用的 URL。

- previousPageConverter:只是将 pageConverter 当前页值减 1。

- nextPageConverter:只是将 pageConverter 当前页值加 1。

               

任务 4 –创建 AJAX 客户端脚本管理器

在本任务中,您将创建一个脚本管理器来向 AjaxHelper 提供一组扩展方法,以便注册和管理 AJAX 脚本文件。

注意:使用脚本管理器将优化脚本的用法,使所有脚本都可以被各个视图根据需要注册,但只呈现一次,并且仍然可在整个应用程序中访问。

您可以增强脚本管理器以用简略的方式呈现脚本(删除空白、换行、注释等),使其变得更加轻便。或者,您可以作为一个整体呈现所有脚本,以便最大限度地减少 HTTP 请求,提高性能。但是,相关内容超出了本实验的范围。

1.            创建一个名为 AjaxClientScriptExtensions.cs 的类。

a.            在 Solution Explorer 中,右键单击 ViewModels 文件夹。

b.            指向 Add,并单击 Class。

c.             键入 AjaxClientScriptExtensions.cs 作为类 Name,然后单击 Add。

d.            将默认类实现替换为以下代码:

(代码片段 – PlanMyNight MVC 应用程序– AjaxClientScriptExtensions 类头)

C#

namespace PlanMyNight.ViewModels

{

    using System;

    using System.Collections.Generic;

    using System.Globalization;

    using System.Linq;

    using System.Text;

    using System.Web.Mvc;

    using System.Web.UI;

    public static class AjaxClientScriptExtensions

    {

    }

}

2.            创建一个名为 JavaScriptEnabled 的扩展方法:

C#

        public static bool JavaScriptEnabled(this AjaxHelper ajaxHelper) {

            return System.Web.HttpContext.Current.Request.Browser.EcmaScriptVersion.Major >= 1;

        }

注意: JavaScriptEnabled 将检查当前上下文中是否启用了 JavaScript,并相应地返回 true 或 false。

3.            在 AjaxClientScriptExtensions 类内部,创建一个名为 ScriptIncludeReference 的私有类,该类将包装所注册脚本的 URL。

(代码片段 – PlanMyNight MVC 应用程序– AjaxClientScriptExtensions ScriptIncludeReference 类)

C#

        private class ScriptIncludeReference

        {

            public ScriptIncludeReference(string url)

            {

                this.Url = url;

            }

            public string Url { get; private set; }

            public string Render()

            {

                return string.Format(CultureInfo.InvariantCulture, "<script type=\"text/javascript\" src=\"{0}\"></script>", this.Url);

            }

        }

注意: ScriptIncludeReference 仅提供所注册脚本的包装程序。它在一个属性中存储 URL,并公开一个用于将脚本呈现到视图中的 Render 方法。

4.            创建一个 private static 方法,并将其命名为 GetScriptReferences。

(代码片段 – PlanMyNight MVC 应用程序– AjaxClientScriptExtensions GetScriptReferences)

C#

        private static List<ScriptIncludeReference> GetScriptReferences(AjaxHelper ajaxHelper)

        {

            var contextItems = ajaxHelper.ViewContext.HttpContext.Items;

            if (!contextItems.Contains("AjaxClientScripts"))

            {

                contextItems["AjaxClientScripts"] = new List<ScriptIncludeReference>();

            }

            return (List<ScriptIncludeReference>)contextItems["AjaxClientScripts"];

        }

注意: GetScriptReferences 从 ajaxHelper 的 ViewContext 中检索所注册的脚本。

5.            创建一个名为 RegiterClientScriptInclude 的扩展方法。

(代码片段 – PlanMyNight MVC 应用程序– AjaxClientScriptExtensions RegisterClientScriptInclude)

C#

        public static void RegisterClientScriptInclude(this AjaxHelper ajaxHelper, string url)

        {

            var scripts = GetScriptReferences(ajaxHelper);

            if (scripts.OfType<ScriptIncludeReference>().FirstOrDefault(s => s.Url == url) == null)

            {

                scripts.Add(new ScriptIncludeReference(url));

            }

        }

注意:RegiterClientScriptInclude 负责在 ajaxHelper 的 ViewContext 中注册新脚本。如果该脚本已经存在,则不执行任何操作。

6.            创建一个名为 RenderClientScripts 的扩展方法以呈现所有已注册的脚本。

(代码片段 – PlanMyNight MVC 应用程序– AjaxClientScriptExtensions RenderClientScripts)

C#

        public static string RenderClientScripts(this AjaxHelper ajaxHelper)

        {

            StringBuilder sb = new StringBuilder();

            foreach (var reference in GetScriptReferences(ajaxHelper))

            {

                sb.Append(reference.Render());

            }

            return sb.ToString();

        }

7.            为了使脚本在整个应用程序中都可用,我们需要在 Site.Master 中呈现它们。将完成此工作的代码添加到 <body> 结束标记之前。

a.            在 Solution Explorer 中,双击 Site.Master 文件(位于 Views\Shared 文件夹内)。

b.            将以下粗体显示的代码粘贴到 <body> 结束标记之前。

ASP.NET

    <script type="text/javascript">

        var baseUrl = '<%=Url.Content("~/")%>';

    </script>

    <%= Ajax.RenderClientScripts() %>

   

</body>

</html>

8.            最后,添加相应的代码,以注册 ClientTemplateSearchResults.ascx 用户控件需要的所有脚本。

a.            在 Solution Explorer 中,双击 ClientTemplateSearchResults.ascx 文件(位于 Views\Home 文件夹内)以将其打开。

b.            在控件头下面,添加代码以注册所需的脚本:

ASP.NET

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>

<% Ajax.RegisterClientScriptInclude(Url.Content("~/Scripts/jquery-1.3.2.min.js")); %>

<% Ajax.RegisterClientScriptInclude(Url.Content("~/Scripts/MicrosoftAjax.js")); %>

<% Ajax.RegisterClientScriptInclude(Url.Content("~/Scripts/MicrosoftAjaxTemplates.js")); %>

<% Ajax.RegisterClientScriptInclude(Url.Content("~/Scripts/ajax.search.js")); %>

               

任务 5 –修改 HomeController 以使用 AJAX

在本任务中,您将修改 HomeController Search 方法,以便在请求为 AJAX 调用时返回 JSON 响应。

1.            在 Solution Explorer 中,双击 HomeController.cs 文件(位于 Controllers 文件夹内)。

2.            添加一个名为 Ajax 的属性,该属性将返回一个 Boolean 值,指示请求是否为 Ajax。

C#

        private bool Ajax

        {

            get

            {

                return !string.IsNullOrEmpty(this.Request.ContentType) && this.Request.ContentType.Contains("application/json");

            }

        }

3.            在 Search 方法中,将在视图数据中设置活动之后的代码替换为以下粗体显示的代码。

(代码片段 – PlanMyNight MVC 应用程序– AjaxClientScriptExtensions 搜索方法)

C#

            // search activities

            var activities = this.activitiesRepository.Search(searchCriteria);

            this.ViewData["PagingResult"] = activities;

            if (this.Ajax)

            {

                // json

                return new JsonResult()

                {

                    JsonRequestBehavior = JsonRequestBehavior.AllowGet,

                    Data = activities

                };

            }

            else

            {

                this..ViewData["SearchCriteria"] = searchCriteria;

                this..InjectActivitySearchFields(this.ViewData, searchCriteria);

                this.ViewData["CriteriaDescription"] = this.GetCriteriaDescription(searchCriteria, " | ");

                this.ViewData["KeywordsMetatag"] = this.GetCriteriaDescription(searchCriteria, ", ");

                return View("Index");

            }

注意:如果请求为 Ajax,则代码以 JsonResult 形式返回活动;否则,以以前的形式返回活动。

4.            按 CTRL+SHIFT+B 生成解决方案。

               

后续步骤

练习 3:验证

练习 3:验证

在本次验证中,您将在启用和禁用 JavaScript 这两种情况下测试应用程序,检查您是否已经正确执行了该练习的所有步骤。

1.            按 CTRL + F5 在不调试的情况下运行程序。

 

图 22

PlanMyNight HomePage

2.            选择 OH 作为州并单击 Search。

 

图 23

AJAX 请求

注意:请求是通过 AJAX 发出的,而结果是在不重新加载整页的情况下呈现的。

3.            在浏览器中禁用 JavaScript

a.            在 Internet Explorer 8 中,转至Tools >> Internet Options 菜单项。

b.            打开 Security 选项卡,然后单击 Custom level 按钮

c.             在 Scripting 下,将 Active scripting 设置为 Disable。

 

图 24

禁用 JavaScript

4.            浏览到 https://localhost:50000

5.            选择 OH 作为州并单击 Search。

注意:请求现在是通过基本 http 发出的,并且呈现了整个页面以显示搜索结果。

6.            随意使用应用程序并通过 AJAX 来测试排序、分页、验证和筛选功能。

               

总结

通过完成本动手实验,您学会了如何创建带有分页、筛选、排序和简单验证功能的全功能 MVC 应用程序。您还学会了如何将 Entity Framework 数据提供程序和 AJAX 添加到现有 MVC Web 应用程序中。