摘要: 昨天在网上看到天龙书局在卖 《ASP.NET 本质论》,繁体版的封面还是很漂亮的。 ASP.NET 本質論郝冠軍 著、賴榮樞 譯出版商: 碁峰出版日期: 2011-09-08台幣定價: $520售價: 8.0 折 $416語言: 繁體中文頁數: 504ISBN: 9862762799EAN: 9789862762790立即出貨產品描述<內容特色>‧以最新ASP.NET為基礎,全面深入剖析ASP.NET的本質‧資深ASP.NET專家執筆,微軟技術社群和MVP聯袂推薦ASP.NET透過一整套封裝了底層處理機制的類别庫提供極其高效率的開發環境,使得許多開發工作透過簡單的控制項拖曳就可以實
阅读全文
摘要: 《ASP.NET 本质论》这本书马上就要上市了,博客园对这本书的出版提供了成长的土壤和营养,更要感谢各位朋友对我的帮助和支持。《ASP.NET 本质论》中没有提供光盘,源码可以在这里直接下载。直接下载全部示例代码: 点击下载也可以针对章节内容,分别下载示例代码:第 1 章 网站应用程序第 2 章 应用程序对象第 3 章 处理请求的七种武器第 4 章 ASP.NET 中的线程与异步第 5 章 页面即对象第 6 章 状态第 7 章 模板和数据绑定第 8 章 自定义控件第 9 章 MVC第 10 章 IIS 与 ASP.NET本书可以在互动出版网直接订购,订购地址:http://www.china-
阅读全文
摘要: 本书的起源经常有人问起:应该如何学习 ASP.NET 开发?为什么开始的时候感觉很容易,但是,遇到问题的时候却感到无从下手?太多的人开始学习的时候,对 ASP.NET 有着深深的误解,包括我自己。 很多人选择 ASP.NET 的理由是因为它简单:中文开发环境、简体中文的文档、简单的拖放式开发、类似于 WinForm 的开发体验等。Visual Studio 和 .NET Framework为我们提供了一个极其方便的开发环境,很多人因此进入了 ASP.NET 开发之门,甚至有相当多的 ASP.NET 程序员都没有了解过 HTTP 协议的内容,或者 HTML 的语法,也同样在完成着开发任务。 这究
阅读全文
根据 W3C DOM 2 Events 描述,EventTarget 接口被所有支持 DOM 事件模型的节点(Node)实现。 该接口提供了 'addEventListener' 和 'removeEventListener' 方法,用来绑定或解绑一个 EventListeners 接口到一个 EventTarget。
DOM 2 Events 中定义了 Event 接口,用来提供事件的上下文信息,它提供了若干标准属性和方法。 实现 Event 接口的对象一般作为第一个参数传入事件处理函数,以用来提供当前事件相关的一些信息。
事件注册
根据 DOM 2 Events 中描述,节点使用 'addEventListener' 和 'removeEventListener' 方法绑定和解绑事件监听器,但 IE6 IE7 IE8 不支持这两个方法, 而使用 'attachEvent' 和 'detachEvent' 方法作为替代方案,Opera 两类方法都支持。Chrome Safari Firefox 只支持标准方法。
为了解决浏览器兼容问题,可以自定义函数来解决。例如:
var EventUtil = {
addHandler: function (element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent("on" + type, handler);
} else {
element["on" + type] = handler;
}
},
removeHandler: function (element, type, handler) {
if (element.removeEventListener) {
element.removeEventListener(type, handler, false);
} else if (element.detachEvent) {
element.detachEvent("on" + type, handler);
} else {
element["on" + type] = null;
}
}
};
关于 'addEventListener' 和 'attachEvent' 有几点需要注意:
- IE 不支持在捕获阶段触发事件监听器,'attachEvent' 方法没有提供参数说明是否响应在捕获阶段触发的事件;
- 'addEventListener' 和 'attachEvent' 都可以注册多个事件监听器;
- 在 Firefox Chrome Safari Opera 中给同一事件注册同一个事件监听器多次,重复注册的会被丢弃;而在 IE 中重复注册的事件监听器会被重复执行多次;
- 当给同一元素注册了多个事件监听器的时候,IE6 IE7 的事件监听器执行顺序是随机的,IE8 是倒序的,Firefox Chrome Safari Opera 是顺序的;
- 当元素注册的事件监听器中有非法的事件监听器时(非函数),在 IE Firefox 中会抛出异常,而在 Chrome Safari Opera 中则会忽略非法的事件监听器,继续执行其他的事件监听器。
事件对象
在ie中,事件对象是作为一个全局变量来保存和维护的。 所有的浏览器事件,不管是用户触发的,还是其他事件, 都会更新window.event 对象。 所以在代码中,只要轻松调用 window.event 就可以轻松获取 事件对象, 再 event.srcElement 就可以取得触发事件的元素进行进一步处理。
对于标准的 DOM 处理来说, 事件对象却不是全局对象,一般情况下,是现场发生,现场使用,把事件对象自动传递给对应的事件处理函数。 在代码中,函数的第一个参数就是事件对象了。
为了解决兼容性问题,通常在代码中如下处理:
function handler(e){
e = e || window.event;
}
需要注意的是,使用 <button id="btn" onclick="foo()">按钮1</button> 进行事件注册,标准方式下却不能在事件处理方法中取得事件对象。
原因是 onclick="foo()" 就是直接执行了, foo() 函数,没有任何参数传递给 foo 函数。
有两个办法解决这个问题。
第一,将注册的方法修改为 <button id="btn" onclick="foo(event)">按钮</button>,注意,这里的 event 不是形参,而是实参,必须名为 event。这样 foo 函数就可以得到事件参数了。
第二,不修改注册的代码,在事件处理方法上进行处理。关键在于此时实际上存在事件对象,只不过没有传递给 foo 函数罢了,我们可以找到调用 foo 函数的那个函数,当然这是一个系统函数,没有关系,通过 foo.caller 可以取得当前调用 foo 函数的函数,这个函数的第一个参数就是事件对象,所以,我们可以这样取得这个事件对象了。foo.caller.arguments[0]。
注意:
- 只有在使用 attachEvent 方法注册事件监听器的时候,IE 才支持使用事件监听器传入的第一个参数作为事件对象的方式;
- Chrome Safari Opera 两种获取事件对象的方式都支持;
- Firefox 只支持获取事件对象的标准方式。
事件对象的属性
IE 对事件对象的标准属性和方法支持有限,针对大部分属性和方法,IE 都提供了一套替代非标准的替代方案; 而 Firefox Chrome Safari Opera 除了全面支持事件对象的标准属性和方法外,还在不同程度上支持了 IE 提供的非标准替代方案。
使用特性判断使用与标准对应的非标准方法及属性
target srcElement
preventDefault() returnValue
stopPropagation() cancelBubble
relatedTarget fromElement toElement
例如:
getEvent: function (event) {
return event ? event : window.event;
},
getTarget: function (event) {
return event.target || event.srcElement;
},
preventDefault: function (event) {
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
},
stopPropagation: function (event) {
if (event.stopPropagation) {
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
参考资料:
SD9011: 事件模型在各浏览器中存在差异
通过 WCF 与页面进行 Ajax 进行数据传递非常方便,可以,遇到日期类型就不同了。
WCF 通过 JavaScriptSerializer 将日期格式化为特殊的格式:\/Date(1318287600+0100)\/,实际上传递到页面上的是一个字符串。而不是真正的 JavaScript 日期。
同样,当浏览器想要向服务器传递日期类型的数据时也必须通过这种特殊的字符串来提供数据。
为了解决这个问题,可以使用下面的方法为 jQuery 扩展一个数据转换的方法。
// 为 jQuery 扩展一个解析 wcf 日期的方法
jQuery.extend(
{
wcfDate2JsDate: function (wcfDate) {
var date = new Date(parseInt(wcfDate.substring(6)));
return date;
},
jsDate2WcfDate: function (jsDate) {
// \/Date(568310400000+0800)\/
return "\/Date(" + jsDate.getTime() + "+0000)\/";
}
}
);
在页面中引用 jQuery 脚本文件之后,加入这段脚本即可。
可以如下方式来使用:
var d = new Date(); // 标准的 JavaScript 日期数据
alert(d);
var wcf = $.jsDate2WcfDate(d); // 转换为 WCF 日期格式
alert(wcf);
alert($.wcfDate2JsDate(wcf)); // WCF 日期格式转化为 JavaScript 日期格式
当然,可以将这段脚本保存在一个文件中,以后在页面中直接引用即可。
下载脚本文件
JSON 格式
json 是 Ajax 中使用频率最高的数据格式,在浏览器和服务器中之间的通讯可离不开它。
JSON 的格式说明可以在可以这里看到,非常详细,还是中文的。
JSON 格式说明
需要特别注意的是,在 JSON 中的属性名是需要使用引号引起来的。
jQuery 中使用 JSON
jQuery 是现在使用广泛的脚本库,那么,在 jQuery 中如何使用 JSON 呢?

解析 JSON
在 jQuery 中已经提供了对于解析 JSON 的内在支持,
jQuery.parseJSON 函数提供了解析的支持,详细的说明见这里。
var obj = jQuery.parseJSON('{"name":"John"}');
alert( obj.name === "John" );
使用对象生成 JSON 格式串
在 jQuery 中并没有提供直接将普通的 JavaScript 对象转换为 JSON 串的方法,可以使用下面的扩展库来完成。
jquery-json 扩展库
这个库用来扩展 jQuery ,对于 JSON 的使用,扩展了两个方法。
toJSON 方法用来将一个普通的 JavaScript 对象序列化为 JSON 串。
var thing = {plugin: 'jquery-json', version: 2.3};
var encoded = $.toJSON( thing ); // '{"plugin":"jquery-json","version":2.3}'
evalJSON 方法将一个 JSON 串解析为一个普通的 JavaScript 对象。
var thing = {plugin: 'jquery-json', version: 2.3};
var encoded = $.toJSON( thing ); // '{"plugin":"jquery-json","version":2.3}'
var name = $.evalJSON( encoded ).plugin; // "jquery-json"
var version = $.evalJSON(encoded).version; // 2.3
这个扩展的下载地址:http://code.google.com/p/jquery-json/
使用 jQuery 配合 WCF
客户端
jQuery 中的 $.post 可以直接向服务器发出请求,将服务器返回的数据按照 JSON 方式进行解析,不过,需要注意下面几点:
请求的内容类型必须为 json 格式,这可以通过上面的 jQuery-json 扩展库来完成,需要特别注意的在请求的 contentType 也必须使用 text/json 进行说明,默认的 post 使用普通的名值对方式请求,因此 contentType 是: application/x-www-form-urlencoded,可以通过 $.ajaxSetup 来进行设置:
// Ajax 设置
$.ajaxSetup({ contentType: 'text/json' });
这样,请求的内容类型就设置为需要的类型。
其次,实际的请求内容必须使用 JSON 方式,这可以通过扩展库的 $.toJSON 来实现,例如:
这样,如果服务器端提供了一个服务方法 Sum,定义如下:
public int Sum(int x, int y)
{
return x + y;
}
就可以如下调用了。注意,WCF 返回的数据在属性 d 中。
// Ajax 设置
$.ajaxSetup({ contentType: 'text/json' });
$("#wcfBtn").click(function () {
$.post("Service1.svc/Sum", $.toJSON({ x: 2, y: 3 }), function (data) {
alert(data.d);
});
});
服务器端的配置
首先,为服务增加标签:[System.ServiceModel.Activation.AspNetCompatibilityRequirements(
RequirementsMode = System.ServiceModel.Activation.AspNetCompatibilityRequirementsMode.Allowed)]
然后,在网站的配置文件中,如下配置。
<system.serviceModel>
<!-- 为了支持在浏览器端调用 WCF 服务的特定配置 -->
<serviceHostingEnvironment aspNetCompatibilityEnabled="true">
<serviceActivations>
<!-- relativeAddress 服务的地址
service 实现服务的类型,全名,包含命名空间,甚至程序集
factory 是 WCF 系统提供,直接使用
-->
<add relativeAddress="Service1.svc" service="MServer.Service1" factory="System.ServiceModel.Activation.WebScriptServiceHostFactory"
/>
</serviceActivations>
</serviceHostingEnvironment>
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
我们已经完成了网站的大部分工作,但是,还有一些添加到站点的导航功能,主页,以及商店的浏览页面。
创建购物车汇总部分视图
我们希望在整个站点的页面上都可以看到购物车中的数量。

通过创建一个部分视图,然后添加到网站的布局中就可以容易地完成,
前面看到,在 ShoppingCart 控制器中包含了一个名为 CartSummary 的 Action 方法返回分部视图。
//
// GET: /ShoppingCart/CartSummary
[ChildActionOnly]
public ActionResult CartSummary()
{
var cart = ShoppingCart.GetCart(this.HttpContext);
ViewData["CartCount"] = cart.GetCount();
return PartialView("CartSummary");
}
在这个 Action 方法上点击鼠标右键,或者在 Views/ShoppingCart 文件夹夹上点击鼠标右键,选择创建新视图,命名为 CartSummary ,注意选中创建分部视图的复选框。

CartSummary 分部视图非常简单,仅仅链接到 ShoppingCart 的 Index ,显示当前购物车中的数量,完整的代码如下:
@Html.ActionLink("Cart (" + ViewData["CartCount"] + ")", "Index", "ShoppingCart", new { id = "cart-status" })
在网站的任何页面中都可以包含分部视图,使用 Html.RenderAction 方法就可以。RenderAction 需要指定 Action 的名字,这里是 CartSummary,以及控制器的名字,这里是 ShoppingCart。
@Html.RenderAction("CartSummary", "ShoppingCart")
在将这个分部视图加入到布局之前,我们还要创建一个流派的菜单,这样我们可以一次更新完站点的 Site.master。
创建流派菜单的分部视图
通过在页面上增加一个流派的菜单,可以是用户在站点内导航的时候更加容易。

我们可以使用类似前面的步骤来创建流派菜单的分部视图,把两个分部视图一起添加到站点的布局中,首先,在 StoreController 中增加 GenreMenu 的控制器方法。
//
// GET: /Store/GenreMenu
[ChildActionOnly]
public ActionResult GenreMenu()
{
var genres = storeDB.Genres.ToList();
return PartialView(genres);
}
这个方法返回流派的列表,在后面创建的视图中用来生成菜单。
注意:在 Action 方法上我们增加了 [ChildActionOnly] 标注,这意味着我们仅仅可以通过分部视图来访问这个 Action,这可以防止通过浏览 /Store/GenreMenu 来访问,对于分部视图来说,这不是必须的,但是一个很好的实践,因为我们希望我们的控制器方法被我们希望的方式使用,这里我们还返回了一个分部视图而不是一个普通的视图,这用来告诉视图引擎,不需要对这个视图使用布局,它将会被包含在其他的视图中。
创建分部视图,使用强类型的 Genre 作为模型类型。使用 List 模板。

更新生成的视图,显示一个列表。
@model IEnumerable<MvcMusicStore.Models.Genre>
<ul id="categories">
@foreach (var genre in Model)
{ <li>@Html.ActionLink(genre.Name,
"Browse", "Store",
new { Genre = genre.Name }, null)
</li>
}
</ul>
更新站点的布局显示我们的分部视图
现在,可以在布局中加入分部视图了,在 /Views/Shared/_Layout.cshtml 中通过调用 Html.RenaderAction() 方法可以调用分部视图,把两个分部视图都加入到布局中,如下所示:
<!DOCTYPE html>
<html>
<head>
<title>@ViewBag.Title</title>
<link href="@Url.Content("~/Content/Site.css")" rel="stylesheet"
type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")"
type="text/javascript"></script>
</head>
<body>
<div id="header">
<h1>
<a href="/">ASP.NET MVC MUSIC STORE</a></h1>
<ul id="navlist">
<li class="first"><a href="@Url.Content("~")" id="current">Home</a></li>
<li><a href="@Url.Content("~/Store/")">Store</a></li>
<li>
@{Html.RenderAction("CartSummary", "ShoppingCart");}
</li>
<li><a href="@Url.Content("~/StoreManager/")">Admin</a></li>
</ul>
</div>
@{Html.RenderAction("GenreMenu", "Store");}
<div id="main">
@RenderBody()
</div>
<div id="footer">
built with <a href="http://asp.net/mvc">ASP.NET MVC 3</a>
</div>
</body>
</html>
更新 Store 的 Browse 页面
商店的浏览页面现在看来还不太好,我们更新这个页面在一个更好地布局中显示专辑,如下更新我们的视图。
@model MvcMusicStore.Models.Genre
@{ ViewBag.Title = "Browse Albums"; }
<div class="genre">
<h3>
<em>@Model.Name</em> Albums</h3>
<ul id="album-list">
@foreach (var album in Model.Albums)
{ <li><a href="@Url.Action("Details", new { id = album.AlbumId })">
<img alt="@album.Title" src="@album.AlbumArtUrl" />
<span>@album.Title</span> </a></li> }
</ul>
</div>
这里,我们将使用 Url.Action 来代替 Html.ActionLink ,以便显示格式化信息,包括艺术家的插画。
注意:我们显示专辑的封面,这些信息保存在数据中,可以通过 StoreManager 进行编辑,也欢迎你加入你的插图。
现在,当我们浏览流派的时候,我们将会看到带有封面的专辑显示在一个网格中。

更新主页来显示畅销专辑
我们希望在首页上增加畅销专辑来增进销售,我们在 HomeController 中增加一下内容来实现,然后增加一些额外的图片来变得更好。
首先,在我们的专辑中增加一个导航属性,以便与 EF 知道关联的的信息。专辑中最后的一行就是新增加的。
public virtual Genre Genre { get; set; }
public virtual Artist Artist { get; set; }
public virtual List<OrderDetail> OrderDetails { get; set; }
注意:这里使用了泛型的集合,需要在代码的前面使用 using 来引用 System.Collections.Generic 命名空间。
首先,我们将要增加 storeDB 的字段和引用 MusicStore.Models 命名空间,类似于其他的控制器。
然后,我们在 HomeController 中增加下面的方法,来查询数据库根据 OrderDetails 找到畅销的唱片。
private List<Album> GetTopSellingAlbums(int count)
{
// Group the order details by album and return
// the albums with the highest count
return storeDB.Albums
.OrderByDescending(a => a.OrderDetails.Count())
.Take(count)
.ToList();
}
这是私有方法,因为我们不希望直接可以访问到,这里为了简单将它写在了 HomeController 中,实际开发的时候,可能需要移到后台的逻辑服务中。
这里,我们更新 Index 来访问前面定义的方法,查询销售前 5 名的专辑,然后将他们传递到视图中。
public ActionResult Index()
{
// Get most popular albums
var albums = GetTopSellingAlbums(5);
return View(albums);
}
完整的代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcMusicStore.Models;
namespace MvcMusicStore.Controllers
{
public class HomeController : Controller
{
private Models.MusicStoreEntities storeDB = new Models.MusicStoreEntities();
//
// GET: /Home/
public ActionResult Index()
{
// Get most popular albums
var albums = GetTopSellingAlbums(5);
return View(albums);
}
private List<Album> GetTopSellingAlbums(int count)
{
// Group the order details by album and return
// the albums with the highest count
return storeDB.Albums
.OrderByDescending(a => a.OrderDetails.Count())
.Take(count)
.ToList();
}
}
}
最后,我们需要更新我们的 Home 控制器的 Index 视图,访问模型在后面加入专辑的列表,借助这个时机,我们还要增加一个标头和一个促销的节。
@model List<MvcMusicStore.Models.Album>
@{
ViewBag.Title = "ASP.NET MVC Music Store";
}
<div id="promotion">
</div>
<h3>
<em>Fresh</em> off the grill</h3>
<ul id="album-list">
@foreach (var album in Model)
{ <li><a href="@Url.Action("Details", "Store",
new { id = album.AlbumId })">
<img alt="@album.Title" src="@album.AlbumArtUrl" />
<span>@album.Title</span> </a></li>
}
</ul>
现在,当运行程序的时候,我们将会看到更新后的主页,带有畅销的专辑和我们的促销信息。

摘要: 在这一节,我们将创建结账的控制器 CheckoutController 来收集用户的地址和付款信息,我们需要用户在结账前注册账户,因为这个控制器需要授权。当用户点击结账 Checkout 按钮的时候,用户将会被导航到结账的处理流程中。如果用户没有登录,将会被提示需要登录。一旦用户成功登陆,用户就可以看到地址和付款的视图。一旦用户填写了这个表单并提交,他们将会看到订单的确认页面。视图访问不存在的订单,或者不属于你的订单,将会看到错误页面。合并购物车在匿名购物的时候,当用户点击结账 Checkout 按钮,用户会被要求注册和登陆,用户会希望继续使用原来的购物车,所以,在匿名用户登录之后,我们需要维
阅读全文
摘要: 示例查询最多的应用场合是组合查询,我们常常需要在界面上提供若干的查询选项,然后根据用户的输入返回符合条件的结果。使用代码直接进行处理往往需要涉及到复杂的条件,由于组合条件并不确定,导致逻辑判断语句结构复杂。对于多个可选的参数,情况会变得更加严重。使用示例查询可以很方便地处理这种问题。在查询的时候,将收集到的查询条件赋予一个对象的属性,当然,这个对象的类型就是需要查询的实体对象。例如,在 NHibernate 中存在一个 User 的类型,我们需要对它的姓名和口令进行组合查询,User 的定义如下:namespace Demo.Dao.Domain{ // 用户对象 public c...
阅读全文
摘要: 在这个项目中,我们将允许用户在没有注册登录的情况下将专辑加入购物车,但是,在完成结账的时候必须完成注册工作。购物和结账将会被分离到两个控制器中:一个 ShoppingCart 控制器,允许匿名用户使用购物车,另一个 Checkout 控制器处理结账。我们先从购物车的控制器开始,然后在下一部分来处理结帐。加入购物车,订单和订单明细的模型类在购物车和结账的处理中将会使用到一些新的类,在 Models 文件夹上右键,然后使用下面的代码增加一个新的类 Cart.using System.ComponentModel.DataAnnotations;namespace MvcMusicStore.Mod
阅读全文
摘要: 目前,我们的 Store Manager 可以被任何人访问,让我们限制一下对站点管理的访问。增加 AccountController 和 相应的视图在全功能的 ASP.NET MVC3 Wb 应用程序与空的 ASP.NET MVC3 应用程序模板之间的区别在于,空的应用程序模板中没有包含账号控制器,我们可以从新创建的全功能的 ASP.NET MVC 应用程序中复制相应的文件,来增加账号控制器。另外,在你下载的 MvcMusicStore-Assets.zip 文件中,也包含了账号管理的文件。复制下面的内容到你的网站中。复制 AccountController.cs 到 Controllers
阅读全文
摘要: 在前面的创建专辑与编辑专辑的表单中存在一个问题:我们没有进行任何验证。字段的内容可以不输入,或者在价格的字段中输入一些字符,在执行程序的时候,这些错误会导致数据库保存过程中出现错误,我们将会看到来自数据库的错误信息。通过为模型类增加数据描述的 DataAnnotations ,我们可以容易地为应用程序增加验证的功能。DataAnnotations 允许我们描述希望应用在模型属性上的验证规则,ASP.NET MVC 将会使用这些 DataAnnotations ,然后将适当的验证信息返回给用户。为专辑表单增加验证我们将会使用下列的 DataAnnotationsRequired 必须 – 表示这
阅读全文