前言

在传统的web应用程序中,客户端()通过请求页面来启动与服务器的通信。然后服务器处理该请求,并发送html页面到客户端。在随后页面上的操作中——例如,用户导航到一个链接或提交一个包含数据的表单——一个新的请求便被发送到服务器,并且重新开始了数据流:服务器处理请求,并将新页面发送到浏览器以响应客户端的新动作请求。

在单页面应用程序(spa)中,在初始化请求后整个页面在浏览器中被加载出来,但通过ajax请求来进行后续的交互。这意为着部分页面改变后浏览器仅需要更新,而不需要整个重新加载。spa实现减少了应用程序对于用户动作的响应时间,从而制造了跟流畅的体验。

在这个动手实验中,你将使用这些先进技术的优势来实现geek quiz,这是一个基于spa概念的网站。你将首先用asp.net web api实现服务层以暴露所需的操作来检索问题和存储答案。然后,你将使用angularjs和css3的变换效果来构建丰富多彩的ui。

回顾

收获

在本动手实验中,你将学习到:
1, 创建asp.net web api服务来发送和接受json数据
2, 使用angularjs来创建响应的ui
3, 使用css3变化来加强ui体验

前提工作

以下是完成这个动手实验所必须的:
visual studio express 2013 for web 或更先进的版本

安装

为了在这个动手实验中运行这个项目,你需要先安装好开发环境:
1, 打开资源管理器并跳转到lab’s的source文件夹。
2, 右击setup.cmd并选择run as administrator来运行安装程序,这会配置你的开发环境,并为本实验安装visual studio代码片(code snippet)。
3, 如果弹出了用户控制窗口,请确认该操作。
备注:在运行安装前请确保你已经检查了本实验所需的所以依赖项。

使用代码片

在实验文档各处,你都被提示去插入代码块。为了更便利,所有这些代码都通过visual studio code snippets提供,这能够让你在visual studio 2013下读取这些代码,而不用手动来做。

备注:每个练习都伴随着该练习begin文件夹下的开始解决方案,这能让你分别完成每个练习。你要知道这些在练习过程中加入的代码片可能在开始解决方案中被遗失或在你完成练习前无法工作。在每个练习的中,你也会发现一个包含了完成相应联系所需的剩余步骤的visual studio解决方案的end文件夹。如果你在完成这个动手实验过程中需要额外的帮助,你就可以将这些解决方案当作指导。

练习

该动手实验包含以下练习:
1, creating a web api
2, creating a spa interface

预计完成本实验的时间:60分钟

备注:当你第一次启动visual studio时,你必须选择一个预定义的设置。每个预定义都被设计成匹配不同的开发风格,并且决定了窗体布局、编辑器行为、智能感知代码片和对话框选项。当使用general development settings集合时,实验过程中会给出动作描述以帮助在visual studio中完成给定的任务。如果你为你的开发环境选择了不同的设置,那么你该考虑到在接下来的步骤中可能会有差别。

练习1:创建web api

服务层是spa的一个关键部分,它能够处理来自ui发送的ajax请求和在响应调用时返回数据。数据的返回应该是机器可读的格式,这样才能被客户端解析和使用。

web api框架是asp.net栈的一部分,并被设计成更容易地实现http服务,通常是通过restful api发送和接收json或xml格式的数据。在本练习中,你将创建一个web站点用以托管geek quiz应用程序,然后使用asp.net web api实现后台服务用以暴露和维持知识竞赛(quiz)的数据。

任务1:为geek quiz创建初始项目

在本任务中,你将基于visual studio的one asp.net项目类型来创建一个支持asp.net web api的新的asp.net mvc项目。one asp.net合并了所有asp.net技术,并给予你随意搭配和选择的权利。你将添加entity framework的模板类和添加quiz问题的。

打开visual stuio express 2013 for web,并选择file | new project… 来创建一个新解决方案。

在new project对话框,选择visual c# ” web节点下的asp.net web application。请确保选择了.net framework,命名为geekquiz,选择一个location并点击ok。

在new asp.net project对话框,选择mvc模板并选择web api选项。同样请确保authentication选项被设置到inpidual user account。点击ok以继续。

在solution explorer,右击geekquiz项目的models文件,并选择add ” existing item…

在add existing item对话框,导航到source/assets/model文件夹,并选中所有文件。点击add。

备注:通过添加这些文件,你其实就为geek quiz应用程序添加了数据模型、entity framework数据库上下文和数据库初始化类。

entity framework(ef)是一个对象关系映射,它使你能够通过对抽象化的应用程序模型编程来取代关系型存储编程来创建数据库访问。<喎? f/ware/vc/”=”” target=”_blank” class=”keylink”>vcd4ncjxwptluz8lkx8tjunw41cztvno1xnxi0kna4lxew+jk9qo6pc9wpg0kphbyzsbjbgfzcz0=”brush:java;”> triviaoption: 代表了对应于quiz问题的单个选项 triviaques:代表了一个quiz问题和通过options属性暴露了该问题相关的选项 triviaanswer: 代表了对应于一个quiz问题的用户选择的选项 triviacontext: 代表了geek quiz应用程序的entity framework数据库上下文。这个类从dcontext继承而来并暴露了dbset属性,后者表示以上所描述的实体对象集合。 triviadatabaseinitializer: 通过继承自createdatabaseifnotexists为triviacontext类实现了entity framework初始化功能。该类的默认行为是如果数据库不存在则创建它,以及在seed方法内插入实体对象。

6 打开global.asax.cs文件,并添加以下using语句。

using geekquiz.models;

7 在application_start方法的开始添加以下代码,它将triviadatabaseinitializer设置成数据库初始器。

public class mvcapplication : system.web.httpapplication
{
    protected void application_start()
    {
        system.data.entity.database.setinitializer(new triviadatabaseinitializer()); 

        arearegistration.registerallareas();
        globalconfiguration.configure(webapiconfig.register);
        filterconfig.registerglobalfilters(globalfilters.filters);
        routeconfig.registerroutes(routetable.routes);
        bundleconfig.registerbundles(bundletable.bundles);
    }
}

8 修改home控制器以约束对用户的认证。打开controller文件夹下的homecontroller.cs文件,并为homecontroller类的定义添加authorize属性。

namespace geekquiz.controllers
{
    [authorize]
    public class homecontroller : controller
    {
        public actionresult index()
        {
            return view();
        }

        ...
    }
}

备注:authorize过滤器会检查用户是否是经过认证的。如果用户未认证,它会返回http状态码401(unauthorized)而不会执行用户操作。你可以将过滤器应用成全局的,也可以是控制器级别的,也可以是独立操作级别的。

9 现在你应该来修改web页面布局和绑定。打开views | shared文件夹下的_layout.cshtml文件,通过将my asp.net application修改成geek quiz来更新title元素的内容。


@styles.render(“~/content/css”) @scripts.render(“~/bundles/modernizr”)

10 在同一个文件中,通过移除about与contact链接并重命名home链接为play来更新导航栏。另外,将applicaiton name链接也修改成geek quiz。导航栏的html代码应该看起来是下面这样的:


            @html.actionlink("geek quiz", "index", "home", null, new { @class = "navbar-brand" })
        
        
            
  • @html.actionlink("play", "index", "home")

@html.partial("_loginpartial")

11 通过将my asp.net application替换成geek quiz以更新页面布局的footer节点。用以下高亮代码来修改

元素的内容:

 


    @renderbody()
    

© @datetime.now.year - geek quiz

任务2:创建名为triviacontroller的web api

在前一个任务中,你已经创建了geek quiz这个web应用程序的初始结构。现在你要建立一个简单的web api服务,用以和quiz的数据模型互动并暴露以下动作:
get /api/trivia: 从quiz列表取出已认证用户的下一个问题来让用户回答
post /api/trivia: 为已认证用户逐一存储quiz答案
你将使用visual studio提供的asp.net支架工具来为web api控制器类创建基线。
1 打开app_start文件夹下的webapiconfig.cs文件。该文件定义了web api服务的配置项,例如路由如何映射到web api控制器的动作。
2 在文件首部添加如下using语句。

using newtonsoft.json.serialization;

3 通过在register方法内添加如下高亮代码,以全局性配置web api动作方法返回的json数据格式。

public static class webapiconfig
{
    public static void register(httpconfiguration config)
    {
        // web api configuration and services

        // use camel case for json data.
        config.formatters.jsonformatter.serializersettings.contractresolver = new camelcasepropertynamescontractresolver();

        // web api routes
        config.maphttpattributeroutes();

        config.routes.maphttproute(
            name: "defaultapi",
            routetemplate: "api/{controller}/{id}",
            defaults: new { id = routeparameter.optional }
        );
    }
}

备注: camelcasepropertynamescontractresolver会自动的将属性名称转换成camel式,它是javascript中属性的简便名称。
4 在solution explorer,右击geekquiz项目的controller文件夹,选择add | new scaffolded item…

5 在add scaffold对话框,请确保左侧面板的common节点被选中。然后选择中间面板的web api 2 controller – empty模板并点击add。

备注:asp.net scafolding是一个asp.net web应用程序的代码生成框架。visual studio 2013对于mvc和web api项目包含了预安装的代码生成器。当你想快速添加与数据模型交互的代码以减少开发标准的数据操作所需的时间,你就可以在项目中使用支架(scaffolding)。

支架(scaffolding)同样会确保项目中的依赖项都被安装。例如,如果你从一个空的asp.net项目开始,然后使用支架来添加了web api控制器,与之相关的web api nuget包和引用会被自动添加你的项目中。

6 在add controller对话框,在controller name文本框中键入triviacontroller并点击add。

7 之后triviacontroller.cs文件就会被添加到geekquiz项目的controller文件夹下,并包含一个空的triviacontroller类。然后在文件开头添加以下using语句。

using system.data.entity;
using system.threading;
using system.threading.tasks;
using system.web.http.description;
using geekquiz.models;

8 在triviacontroller类开头添加以下代码,在控制器中定义、初始化和配置triviacontext实例。

public class triviacontroller : apicontroller
{
    private triviacontext db = new triviacontext();

    protected override void dispose(bool disposing)
    {
        if (disposing)
        {
            this.db.dispose();
        }

        base.dispose(disposing);
    }
}

备注:triviacontroller的dispose方法执行triviacontext实例的dispose方法,它会确保当triviacontext实例被创建或垃圾回收时,所有被上下文对象使用的资源都被释放掉。这也包括了被entity framework打开的数据库连接。

9 在trivialcontroller类的结尾加上以下helper方法。这个方法根据具体用户从数据库中取出quiz问题给用户回答。

private async task nextquestionasync(string userid)
{
    var lastquestionid = await this.db.triviaanswers
        .where(a => a.userid == userid)
        .groupby(a => a.questionid)
        .select(g => new { questionid = g.key, count = g.count() })
        .orderbydescending(q => new { q.count, questionid = q.questionid })
        .select(q => q.questionid)
        .firstordefaultasync();

    var questionscount = await this.db.triviaquestions.countasync();

    var nextquestionid = (lastquestionid % questionscount) + 1;
    return await this.db.triviaquestions.findasync(cancellationtoken.none, nextquestionid);
}

10 在trivialcontroller类中添加以下get动作的方法。这个动作的方法调用了在上一步中定义的用于为认证用户取出下一个问题的nextquestionasync方法。

// get api/trivia
[responsetype(typeof(triviaquestion))]
public async task get()
{
    var userid = user.identity.name;

    triviaquestion nextquestion = await this.nextquestionasync(userid);

    if (nextquestion == null)
    {
        return this.notfound();
    }

    return this.ok(nextquestion);
}

11 在triviacontroller类中添加以下helper方法。

private async task storeasync(triviaanswer answer)
{
    this.db.triviaanswers.add(answer);

    await this.db.savechangesasync();
    var selectedoption = await this.db.triviaoptions.firstordefaultasync(o => o.id == answer.optionid
        && o.questionid == answer.questionid);

    return selectedoption.iscorrect;
}

12 在triviacontroller类中添加以下post动作方法。这个动作方法将答案和认证用户联系起来,并调用storeasync方法。然后,它会根据帮助方法的返回值发送一个包含布尔变量的响应。

// post api/trivia
[responsetype(typeof(triviaanswer))]
public async task post(triviaanswer answer)
{
    if (!modelstate.isvalid)
    {
        return this.badrequest(this.modelstate);
    }

    answer.userid = user.identity.name;

    var iscorrect = await this.storeasync(answer);
    return this.ok(iscorrect);
}

13 通过向triviacontorller的类定义添加authorize属性,来修改web api控制器以约束对认证用户的访问。

[authorize]
public class triviacontroller : apicontroller
{
    ...
}

任务3:运行解决方案

在本任务中,你将核实你在上一个任务重建立的web api服务是否如预期般工作。你将会使用internet explorer的f12 developer tools来捕获网络数据传输和检测来着web api服务的全部响应。

备注:确保visual studio工具栏的start按钮上被选中为internet explorer。

1 按f5以运行解决方案。log in页面应该会出现在浏览器上。
备注:当应用程序启动时,默认的mvc路由被触发,它会默认地映射到homecontroller类的index动作上。因为homecontroller被约束为认证用户(在练习1中你已经将authorize属性添加到该类上),并且现在还没有用户经过认证,所以应用程序会向登录页面发送原始请求。

2 点击register来创建一个新用户。

3 在register页面,输入一个用户名和密码,然后点击register。

4 接下来应用程序就会注册一个新账号,并且该账号是经过认证的,随后会自动导航到主页。

5 在浏览器上按f12以打开developer tools面板。按ctrl+4或点击network图标,然后点击绿色箭头按钮以开始捕获网络传输。

6 在浏览器地址栏的url上添加api/trivia。你将会检测到来自triviacontroller里的get动作的响应细节。

备注:一旦下载完成,你将会被提示对下载的文件进行操作。留着这个对话框,为了在developers tool窗口查看响应的内容。
7 现在你可以检测到响应体了。点击details,然后点击response body。你能发现下载的数据是一个对应着triviaquestion类的options(它是一个triviaoption对象的列表)、id和title属性的对象。

8 返回到visual studio并按shift+f5以停止调试。