前面阅读第六波导航:
原著:ASP.NET MVC 4 IN ACTION
大家好!
我是茗洋芳竹,首先声明,我不是一个翻译人员,我是个90后程序员.
夏天到了,哎,人也变懒了,开始写吧…
读者约定:
”网页标签 element“代表 网页上的一个DOM元素,例如 div element就是网页中有个div的DOM元素
7.3 Ajax返回JSON 和 客户端模版的使用
前面几节都是对Ajax请求返回HTML,虽然用这种方法没有任何错误,但是你没有简化HTML的返回,其实你可以返回任意种格式的数据,例如,纯文本(plain text),XML,JSON。在下面一节我们将使用通过Ajax返回JSON的模式写程序
7.3.1 Ajax with JSON
JSON全称:JavaScript Object Notation
它提供了一种很简单的方式表达数据,如果你熟悉JavaScript Object,你就会很容易熟悉JSON。
两种表达数据的方式的比较
XML数据:
5 Jeremy Skinner /content/jeremy.jpg Jeremy Skinner is a C#/ASP.NET software developer in the UK.
JSON格式
{ "Id":5, "FirstName":"Jeremy", "LastName":"Skinner", "PictureUrl":"/content/jeremy.jpg", "Bio":"Jeremy Skinner is a C#/ASP.NET software developer in the UK." }
如果你熟悉C#中的集合,那你就很快会用JSON,通过键,获得对象,然后你自己再操作对象就行了,这里不多说了
为了演示JSON,我们添加一个SpeakerController,然后在Models文件夹下添加一个Speaker实体类:
1: public class Speaker
2: {
3: public int Id { get; set; }
4: public string FirstName { get; set;}
5: public string LastName { get; set; }
6: public string PictureUrl { get; set; }
7: public string Bio { get; set; }
8: public string FullName
9: {
10: get { return string.Format("{0} {1}", FirstName, LastName); }
11: }
12: }
再手动创建一个SpeakerRepository仓库类,因为我们没有使用到数据库,我们添加的数据都在内存里面,真实的项目都是使用数据库,这里用于演示用的。在model的构造函数中,创造一个SpeakerRepository集合,初始化数据,代码如下:
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Web;
5:
6: namespace AjaxExamples.Models
7: {
8: public class SpeakerRepository
9: {
10: private static readonly List_speakers = new List ();
11:
12: static SpeakerRepository()
13: {
14: _speakers = new List{
15:
16: new Speaker
17: {
18: Id = 1,
19: FirstName = "Jimmy",
20: LastName = "Bogard",
21: PictureUrl = "/content/jimmy.png",
22: Bio = "Jimmy is a Principal Consultant at Headspring Systems in Austin, TX.",
23: },
24: new Speaker
25: {
26: Id = 2,
27: FirstName = "Jeffrey",
28: LastName = "Palermo",
29: PictureUrl = "/content/jeffrey.jpg",
30: Bio = "Jeffrey Palermo is a Microsoft MVP and Chief Technology Officer of Headspring Systems in Austin, TX.",
31: },
32: new Speaker
33: {
34: Id = 3,
35: FirstName = "Eric",
36: LastName = "Hexter",
37: PictureUrl = "/content/eric.jpg",
38: Bio = "Eric Hexter is an Enterprise Architect at Dell in Austin, TX.",
39: },
40: new Speaker
41: {
42: Id = 4,
43: FirstName = "Matt",
44: LastName = "Hinze",
45: PictureUrl = "/content/matt.jpg",
46: Bio = "Matt Hinze is a Principal Consultant at Headspring Systems in Austin, TX.",
47: },
48:
49: new Speaker
50: {
51: Id = 5,
52: FirstName = "Jeremy",
53: LastName = "Skinner",
54: PictureUrl = "/content/jeremy.jpg",
55: Bio = "Jeremy Skinner is a C#/ASP.NET software developer in the UK.",
56: }
57: };
58:
59:
60: }
61:
62:
63:
64: }
65: }
我们再在这里面写几个获得数据的方法
1: public IEnumerableFindAll()
2: {
3: return _speakers;
4: }
5:
6: public Speaker FindSpeaker(int id)
7: {
8: return _speakers.SingleOrDefault(x => x.Id == id);
9: }
最后我们处理那个SpeakerController
在Index action中添加如下代码,返回所有的Speaker信息,添加对应的Index view
1: private readonly SpeakerRepository _repository = new SpeakerRepository();
2:
3: public ActionResult Index()
4: {
5: var speakers = _repository.FindAll();
6: return View(speakers);
7: }
这个方法,用于首页显示Speaker信息,然后单击一项,我们要显示详情,那么我们添加一个Details action,参数为id
代码如下:
1: public ActionResult Details(int id)
2: {
3: var speaker = _repository.FindSpeaker(id);
4: return Json(speaker, JsonRequestBehavior.AllowGet);
5: }
看的话,估计你也懂了,但是我还是要讲一下JSON方法,更深入一下,比如为什么后面加了个参数,其实可以不要的
这里,我们使用了Id从仓库里获得 Speaker的信息,通过Controller的JSON方法可以将一个对象序列化成JSON。这个方法返回一个JSONResult,JSONResult是实现ActionResult的,当开始把一个对象序列化成一个JSON的时候,它会把结果写入 result stream(结果流)里面,就可以返回信息了。
ASP.NET MVC,JSON和GET请求
你也注意到了,在上面的代码,Controller中的JSON方法的参数,我们使用了一个枚举值JsonRequestBehavior.AllowGet。默认的JsonResult都是以Post方式响应,如果你想以Get方式,就这样用这个行为。
在这里用这个行为可以避免 JSON hijacking(劫持,是一种跨站点的脚本的form)
如果一个站点去响应Get请求,返回的JSON数据含有敏感数据,那么一个恶意的网站就可以在一个敏感的位置(容易攻击的位置,适合坏人下手的位置)通过嵌入脚本可以潜在地让不知情的用户看到他们(坏人)的数据。
如果一个认证了的用户访问了这个恶网站,这些数据就会被下载,然后恶意的网站就可以得到它,我们将会在下一波讲 JSON hihacking
在我们的特殊的例子中,我们不会返回敏感的数据,所以让JSON以GET请求访问绝对安全。
接下来我们看下Index view,要使用到的一些图片下载, 下载好后,放到Content文件夹下(css文件提供,因为主要讲MVC)
我们先建立一个空的Speakers.js,用于写js代码
关于Index view代码如下:
1: @model IEnumerable
2: @{
3: ViewBag.Title = "JSON Example";
4: }
5: @section head {
6:
7:
8: }
9:
10:Speakers
11:
12:
13: @foreach (var speaker in Model) {
14:
15: @Html.ActionLink(speaker.FullName, "Details", new { id = speaker.Id })
16:
17: }
18:
19:
20:
21:
22:
23:
我们后台传过来一个list,视图中标记了Model,使得视图变成一个强类型视图,我们使用foreach循环,显示展示结果,每个结果添加超链接,单击显示详情。
我们使用一个div element,显示单击后的详情信息,我们现在不使用,我们将在7.3.2中使用
接下来我们在Speakers.js文件中写入Js
1: $(document).ready(function () {
2: $("ul.speakers a").click(function (e) {
3: e.preventDefault();
4: $("#indicator").show();
5: var url = $(this).attr('href');
6: $.getJSON(url, null, function (speaker) {
7: $("#indicator").hide();
8: alert(speaker.FirstName);
9: });
10: });
11: });
$.getJSON方法具体参考 Jquery手册,如果你使用过jquery,这点绝对不是问题
getJSON方法会自动将JSON string转换成JavaScript对象。
运行项目,效果图:
光显示一个 FirstName是没有用的,接下来我们在页面中添加更多的标签,来显示说话者的详细信息,还有照片。
下面我们讲客户端模版。
7.3.2 Client-side templates
我们在服务端大多都是使用Razor文件作为模版,我们也可以在客户端创建一个模版
客户端模版 允许我们不需要返回服务器或者通过JavaScript构建elements就可以立即在浏览器中立即生成markup(标签,网页中的元素),我们有几种客户端模版库可供我们选择,但是我们使用jQuery-tmpl , 一个jQuery写的模版库,微软写的,已经贡献到jQuery 项目,开源。
我们修改speaker列表,让speaker name可以click,然后他们的bio(个人简历)和照片将会显示:
为了引用jQuery-tmpl,我们可以从 下载,然后把他们放到我们项目的scripts文件夹里面。或者我们可以直接引用来自 Microsoft’s CDN 的 ,一旦引用好了,我们就可以在view中添加 模版了
点击直接下载 注意,这个tmpl.js文件一定要放在 Speakers.js上面,注意加载顺序
1:
2:
3:
4: alt="Speaker image" class="speaker-pic" />
5:
6: ${Bio}
7:
8:
9:
10:
这里我们引用了 jQuery-tmpl脚本,然后生命了一个模版,模版在一个script element中,用的是type=”text/x-jquery-tmpl”,把template的标签放到一个script element中可以保证,页面载入时,不会直接显示模版。
我们的模版包括了照片和个人简历的信息,我们引用JSON对象,通过使用${}代码块(nuget)来包裹(wrap)它们,这些在模版呈现的时候,会被替换的。下面我们需要修改Speakers.js中的JavaScript来呈现这个模版。
修改的代码如下:
1: $(document).ready(function () {
2: $("ul.speakers a").click(function (e) {
3: e.preventDefault();
4:
5: $(".selected-speaker").hide().html('');
6: $("#indicator").show();
7:
8: var url = $(this).attr('href');
9:
10: $.getJSON(url, null, function (speaker) {
11: $("#indicator").hide();
12:
13: $("#speakerTemplate")
14: .tmpl(speaker)
15: .appendTo('.selected-speaker');
16:
17: $('.selected-speaker').show();
18: });
19: });
20: });
这里代码只有一点点不同,第5行,每次查看详情的时候,先清空原来的数据
第13行,模版的位置 点 tmpl(JSON转换过的JavaScript对象)点 appendTo(在哪里显示)
7.3.3 完成 这个例子
这个例子已经大大地完成了,但是还有个缺点。如果JavaScript禁用了,单击的时候,JSON就作为文件下载了,而不是呈现一个模版。
为了完成这个,我们可以一个最简单的方法,修改SpeakersController中的 Details action
1: public ActionResult Details(int id)
2: {
3: var speaker = _repository.FindSpeaker(id);
4: if (Request.IsAjaxRequest()) {
5: return Json(speaker, JsonRequestBehavior.AllowGet);
6: }
7: return View(speaker);
8: }
除了依靠在我们的代码中使用if,我们还可以使用一个action method selector,用于Ajax和non-Ajax请求。在第一波我们已经看到了 action selector怎么使用的了,我们可以创建一个继承ActionMethodSelector的AcceptAjaxAttribute
我们在Filters文件下建立
代码如下:
1: using System;
2: using System.Reflection;
3: using System.Web.Mvc;
4:
5: namespace AjaxExamples.Filters
6: {
7: [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
8: public class AcceptAjaxAttribute : ActionMethodSelectorAttribute
9: {
10: public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
11: {
12: return controllerContext.HttpContext.Request.IsAjaxRequest();
13: }
14: }
15: }
如果当前的请求是通过Ajax请求的,IsValidForRequest方法就会返回true
现在我们在SpeakersController中使用这个attribute,通过定义两个action-----一个是Ajax请求,另一个是正常的请求
1: public ActionResult Details(int id)
2: {
3: var speaker = _repository.FindSpeaker(id);
4: if (Request.IsAjaxRequest()) {
5: return Json(speaker, JsonRequestBehavior.AllowGet);
6: }
7: return View(speaker);
8: }
9:
10: [ActionName("Details")]
11: public ActionResult Details_NonAjax(int id)
12: {
13: var speaker = _repository.FindSpeaker(id);
14: return View(speaker);
15: }
第一个重载的Details action被我们创建的AcceptAjaxAttribute注解(annotated)了。这个保证了这个方法只能被Ajax方法请求到,这个版本的action会返回JSON序列化后的speaker数据。
另一个重载的没有使用AcceptAjaxAttribute,这就意味着它将可以被非Ajax请求访问,这个action简单的传递了个speaker实例对象给视图,注意,C#中不能定义同名字的相同签名的两个方法,这第二个版本的action被命名为Details_NonAjax,但是他依然可以被URL/Speakers/Details访问,因为它被ActionName注解了。
这个AcceptAjaxAttribute作为ASP.NET MVC Futures DLL的一部分可以被找到,您可以通过
在这个特殊的例子中,使用AcceptAjaxAttribute不会带来太多的方便,但是在一个action 表单Ajax和非Ajax处理的操作是不一样的情形的时候,把处理的代码分开写,会具有可读性。
我们也可以定义一个视图,针对非Ajax版本的action。这个视图显示speaker的详细信息,很多跟客户端模版一样
我们添加 Details_NonAjax对应的视图;
1: @model AjaxExamples.Models.Speaker
2:
3: @section head {
4:
5: }
6:Speaker Details: @Model.FullName
7:
8:
9:
10: alt="@Model.FullName" />
11: @Model.Bio
12:
13:
14:
15: @Html.ActionLink("Back to speakers", "index")
Details中的代码如下:
1: @model AjaxExamples.Models.Speaker
2: @{
3: ViewBag.Title = "Speaker Details";
4: }
5: @section head {
6:
7:
8: .speaker img
9: {
10: float: left;
11: margin: 5px;
12: }
13:
14:
15: }
16:
17:Speaker Details: @Model.FullName
18:
19:
20:
21:@Model.Bio
22:
23:
24:
25: @Html.ActionLink("Back to speakers", "index")
我们注释掉 Views/Shared/_Layout.cshtml中底部的一行jquery代码
在顶部重新引用 一个jquery库
运行项目,效果如下:
代码下载:http://download.csdn.net/download/yangyanghaoran/5393947