[转载]Session共享的解决方案

mikel阅读(1132)

[转载]Session共享的解决方案 – 心海巨澜 – 博客园.

Session共享的解决方案

1、客户端SessionID值唯一;

对于不同的域名:主域名、子域名、跨站点域名或跨服务器域名,用户在打开页面时会产生不同的SessionID,

为了使这些站点在用户登录时只登录一次,那我们就要解决SessionID的问题,必须使SessionID在这些共享Session的站点中只产 生一次。而SessionID是存储在客户端的cookie之中键值为ASP.NET_SessionId的一个字符串(也可以存储在URL中,这里不作 使介绍),为此只须使各站点存储的SP.NET_SessionId唯一即可。

因每个客户端在打开时会产生一个SessionID,为此我们要做的就是重置SessionID。我们可以在继承HttpModule,在结束请求时重写SessionID。

代码如下

public class MakeSessionIDOneOnly : IHttpModule { private string m_RootDomain = string.Empty; #region IHttpModule Members public void Dispose() { } public void Init(HttpApplication context) { m_RootDomain = ConfigurationManager.AppSettings["RootDomain"]; Type stateServerSessionProvider = typeof(HttpSessionState).Assembly.GetType("System.Web.SessionState.OutOfProcSessionStateStore"); FieldInfo uriField = stateServerSessionProvider.GetField("s_uribase", BindingFlags.Static | BindingFlags.NonPublic); if (uriField == null) throw new ArgumentException("UriField was not found"); uriField.SetValue(null, m_RootDomain); context.EndRequest += new System.EventHandler(context_EndRequest); } void context_EndRequest(object sender, System.EventArgs e) { HttpApplication app = sender as HttpApplication; for (int i = 0; i < app.Context.Response.Cookies.Count; i++) { if (app.Context.Response.Cookies[i].Name == "ASP.NET_SessionId") { app.Context.Response.Cookies[i].Domain = m_RootDomain; } } } #endregion }

为使用以上代码,须配置下面节点项。

<httpModules>

<add name=”节点名称” type=”类名全称, 程序集”/>

</httpModules>

2、Session值的共享;

配置sessionState置节点,使用StateServer或SQLServer来实现Session共享。

为实现跨服务器共享,必须在Web.config配置:

<machineKey decryptionKey=”FD69B2EB9A11E3063518F1932E314E4AA1577BF0B824F369″ validationKey=”5F32295C31223A362286DD5777916FCD0FD2A8EF882783FD3E29AB1FCDFE931F8FA45A8E468B7A40269E50A748778CBB8DB2262D44A86BBCEA96DCA46CBC05C3″ validation=”SHA1″ decryption=”Auto”/>

并且,不同服务器上站点配置必须用相同的Web.config,各站点目录配置也要相同。

2.1、使用StateServer:

存储Session的服务器必须开启StateServer:ASP.NET状态服务。只有机器重起的情况下才导致Session丢失。

<sessionState cookieless=”false” timeout=”50″ mode=”StateServer” stateConnectionString=”tcpip=IpAddress:42424″/>

若StateServer在本机存储,则IpAddress为:127.0.0.1;若StateServer为远程服务器,则为IpAddress为远程服务器IP地址,并且修改注册表项如下:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\aspnet_state\Parameters]

“Port”=dword:0000a5b8

“AllowRemoteConnection”=dword:00000001

2.2、使用SQLServer

必须开启SQLServer代理服务,此服务负责清除过期的Session,若没有开服务,则Session不会过期。

使用SQLServer在机器重启后Session不会丢失。

Web.config配置:

<sessionState mode=”SQLServer” sqlConnectionString=”server=DBIpAddress; uid=myid; pwd=mypwd;”/>

数据库配置:

使用aspnet_regsql.exe工具

ASP.NET 2.0版本后微软提供了aspnet_regsql.exe工具可以方便的配置Session数据库.该工具位于 Web 服务器上的”系统根目录\Microsoft.NET\Framework\版本号”文件夹中.

使用举例:

aspnet_regsql.exe -S . -U sa -P 123456 -ssadd -sstype p

-S参数:

表示数据库实例名称. 可以用”.”表示本机.

-U和-P参数:

表示用户名和密码.

-E参数:

可以再-U –P 与 -E中选择一组. –E表示以当前系统用户通过windows身份验证登录数据库, -U -P则是使用SqlServer用户登录数据库.

-ssadd / –ssremove 参数:

-ssadd表示是添加Session数据库, -ssremove表示移除Session数据库.

sstype 参数说明:

t 将会话数据存储到 SQL Server tempdb 数据库中。这是默认设置。如果将会话数据存储到 tempdb 数据库中,则在重新启动 SQL Server 时将丢失会话数据。
p 将会话数据存储到 ASPState 数据库中,而不是存储到 tempdb 数据库中。
c 将会话数据存储到自定义数据库中。如果指定 c 选项,则还必须使用 -d 选项包括自定义数据库的名称。

sessionState参数说明:

属性 说明
allowCustomSqlDatabase 可选的 Boolean 属性。

指定会话状态 SQL 数据库是否可以是自定义数据库(而不是 ASP.NET 默认数据库)。如果为 false,则不能指定初始目录或数据库作为 sqlConnectionString 属性的值。默认会话状态 SQL 数据库为 ASPState 数据库。有关更多信息,请参见会话状态模式。

此属性是 .NET Framework 2.0 版中的新属性。

默认值为 false。

cookieless 可选的 HttpCookieMode 属性。

指定对于 Web 应用程序使用 Cookie 的方式。

cookieless 属性可以为下列可能值之一。默认值为 UseCookies。

说明
AutoDetect ASP.NET 确定请求浏览器或请求设备是否支持 Cookie。如果请求浏览器或请求设备支持 Cookie,则 AutoDetect 使用 Cookie 来保留用户数据;否则,将在查询字符串中使用一个标识符。如果浏览器或设备支持 Cookie,但当前禁用了 Cookie,则请求功能仍会使用 Cookie。
UseCookies 无论浏览器或设备是否支持 Cookie,都使用 Cookie 来保留用户数据。
UseDeviceProfile ASP.NET 根据 HttpBrowserCapabilities 设置来确定是否使用 Cookie。如果 HttpBrowserCapabilities 设置指示浏览器或设备支持 Cookie,将使用 Cookie;否则,将在查询字符串中使用一个标识符。
UseUri 无论浏览器或设备是否支持 Cookie,调用功能都使用查询字符串来存储标识符。

cookieName 可选的 String 属性。

指定存储会话标识符的 Cookie 的名称。

此属性是 .NET Framework 2.0 版中的新属性。

默认值为 “ASP.NET_SessionId”。

customProvider 可选的 String 属性。

指定用于存储和检索会话状态数据的自定义会话状态提供程序的名称。该提供程序在 providers 元素中指定。仅当会话状态模式设置为 Custom 值时,才使用该提供程序。有关更多信息,请参见会话状态模式。

此属性是 .NET Framework 2.0 版中的新属性。

默认值为空字符串 (“”)。

mode 可选的 SessionStateMode 属性。

指定存储会话状态值的位置。有关更多信息,请参见会话状态模式。

mode 属性可以为下列可能值之一。默认值为 InProc。

说明
Custom 会话状态将使用自定义数据存储区来存储会话状态信息。
InProc 会话处于正在处理 ASP.NET 辅助进程的状态。
Off 会话状态被禁用。
SQLServer 会话状态将使用进程外 SQL Server 数据库来存储状态信息。
StateServer 会话状态将使用进程外 ASP.NET 状态服务来存储状态信息。

partitionResolverType 可选的 String 属性。

指定在哪里存储会话状态。如果 partitionResolverType 属性中指定了值,则忽略 sqlConnectionString 和 stateConnectionString 属性。PartitionResolverType 属性返回的连接字符串将用于每个请求,为请求的其余部分连接到适当的服务器位置。如果连接字符串无效,ASP.NET 将引发一个异常,该异常与当配置的服务器连接字符串无效时引发的异常相同。该属性用于在 SQL 或状态服务器模式下在多个后端节点上划分会话状态数据。

此属性是 .NET Framework 2.0 版中的新属性。

默认值为空字符串。

regenerateExpiredSessionId 可选的 Boolean 属性。

指定当客户端指定了过期的会话 ID 时是否重新发出会话 ID。默认情况下,当启用了 regenerateExpiredSessionId 时,仅为 cookieless 模式重新发出会话 ID。有关更多信息,请参见 IsCookieless。

此属性是 .NET Framework 2.0 版中的新属性。

默认值为 true。

sqlCommandTimeout 可选的 TimeSpan 属性。

指定使用 SQL Server 会话状态模式的 SQL 命令的持续时间超时(秒)。持续时间超时是 SQL 命令可以处于空闲状态的时间(秒),超过此时间之后,该命令将被取消。

此属性是 .NET Framework 2.0 版中的新属性。

默认值为 0:00:30(30 秒)。

sqlConnectionString 可选的 String 属性。

为运行 SQL Server 的计算机指定连接字符串。该属性在 mode 属性设置为 SQLServer 值时是必需的。有关更多信息,请参见会话状态模式。

注意
若要在使用 SQLServer 模式时提高您的应用程序的安全性,请使用受保护的配置来加密配置的 sessionState 节,以帮助保护 sqlConnectionString 值。

默认值为 “data source=127.0.0.1;Integrated Security=SSPI”。

stateConnectionString 可选的 String 属性。

指定远程存储会话状态的服务器名称或地址以及端口。端口值必须为 42424。当 mode 为 StateServer 值时,该属性是必需的。确保运行 ASP.NET 状态服务的服务器是存储会话状态信息的远程服务器。该服务随 ASP.NET 一起安装,默认情况下为 %SystemRoot%\Microsoft.NET\Framework\VersionNumber\aspnet_state.exe。有关更多 信息,请参见会话状态模式。

注意
若要在使用 StateServer 模式时提高您的应用程序的安全性,请使用受保护的配置来加密配置的 <sessionState> 节,以帮助保护 stateConnectionString 值。

默认值为 “tcpip=127.0.0.1:42424″。

stateNetworkTimeout 可选的 TimeSpan 属性。

指定 Web 服务器与状态服务器之间的 TCP/IP 网络连接可以处于空闲状态的时间(秒),超过此时间后,请求将被取消。该属性在 mode 属性设置为 StateServer 值时使用。

默认值为 10 秒。

timeout 可选的 TimeSpan 属性。

指定在放弃一个会话前该会话可以处于空闲状态的分钟数。对于进程内和状态服务器模式,timeout 属性不能设置为大于 525,601 分钟(1 年)的值。

会话 timeout 配置设置仅适用于 ASP.NET 页。更改会话 timeout 值不会影响 ASP 页的会话超时时间。同样,更改 ASP 页的会话超时时间不会影响 ASP.NET 页的会话超时时间。

默认值为 20 分钟。

useHostingIdentity 可选的 Boolean 属性。

指定会话状态将恢复为宿主标识还是使用客户端模拟。

如果为 true,ASP.NET 将使用下列进程凭据之一来连接会话状态存储区:

宿主进程;对于 Microsoft Internet 信息服务 [IIS] 5 和 5.1 版为 ASPNET,对于 Microsoft Windows Server 2003 则为 NETWORK SERVICE。

应用程序模拟标识,当使用了以下配置时使用此凭据:

<identity impersonate=”true” userName=”user” password=”pwd” />

如果为 false,ASP.NET 将使用目前与当前请求的操作系统线程关联的凭据来连接会话状态存储区。对于客户端模拟,ASP.NET 将使用与浏览器协商的安全凭据来连接会话状态存储区。如果为 false,ASP.NET 在连接会话状态存储区时不会恢复为进程标识或应用程序模拟标识。有关更多信息,请参见 ASP.NET 模拟。

此属性是 .NET Framework 2.0 版中的新属性。

默认值为 true。

注意
在 .NET Framework 1.1 版中,如果 mode 属性设置为 SQLServer,并且客户端模拟有效,则 ASP.NET 使用来自 ASP.NET 客户端模拟的客户端凭据连接到运行 SQL Server 的计算机。

继承的属性 可选的属性。

由所有节元素继承的属性。

[转载]MVC模式参数传递的探究

mikel阅读(1165)

[转载]MVC模式参数传递的探究 – 丁丁3 – 博客园.

最近公司一直在用MVC模式写程序,所以最近一直再查MVC的资料,目前在研究MVC的参数传递方法,因此有了此文。
MVC模式既可以通过Model传递参数,也可以用ViewData来保存数据,两种方式的组合传递参数就是一种新的模式了。
使用这种组合传参的新模式可以非常快的实现,但美中不足的是输入错误会导致错误,不能在编译时发现错误原因,而且ViewData必须要用as来转换。
从Controller传递数据到View视图模板
MVC模式一个典型的特征是严格的功能隔离。Model模型、Controller控制器和View视图各自定义了作用和职责,且相互之间以定义好的方式进行沟通。这有助于提升测试性和代码重用。
当Controller决定呈现HTML响应给客户端是,它负责显式传递给View模板所有需要的数据。View模板从不执行任何数据查询或应用程序逻辑 – 仅仅负责呈现Model或Controller传递过来的数据。

当需要从Controller传递不止一个Model对象时,就要用到ViewData字典或ViewModel模式。例如,当我们需要Controller在View呈现Dinner对象和支持的Countries列表的时候,采用如下两种方式来实现。

使用ViewData 字典

Controller基类公开了一个ViewData 字典属性,用来从Controllers传递额外的数据给Views视图。
Controller中代码

1 [Authorize]
2 public ActionResult Edit(int id)
3 {
4 Dinner dinner = dinnerRepository.GetDinner(id);
5 ViewData["Countries"] = new SelectList(PhoneValidator.Countries, dinner.Country);
6 return View(dinner);
7 }

上面代码中SelectList构造函数的第一个参数显示国家列表,第二个参数指定当前选中的国家。
View中代码

1 <%= Html.DropDownList("Country", ViewData["Countries"] as SelectList)%>


上面代码中第一个参数表示输出的HTML表单元素名称,第二个参数是通过ViewData传递的模型类。
使用ViewModel模式:
ViewData的优点:非常快,容易实现
ViewData的缺点:输入错误会导致错误;不能在编译期发现错误原因;在View视图模板中使用强类型时,ViewData要用as来转换。
ViewModel模式:
1.针对特定的View视图创建强类型的类
2.公开View模板需要的动态参数值或内容
3.Controller类填充和传递这些类给View模板去用
4.优点:类型安全、编译期检查和编辑器的智能提示

示例代码:

01 public class DinnerFormViewModel
02 {
03 public Dinner Dinner {get; private set;}
04 public SelectList Countries{get; private set;}
05 public DinnerFormViewModel(Dinner dinner)
06 {
07 Dinner = dinner;
08 Countries = new SelectList(PhoneValidator.Countries,dinner.Country);
09 }
10 }

在Controller类中

1 [Authorize]
2 public ActionResult Edit(int id)
3 {
4 Dinner dinner = dinnerRepository.GetDinner(id);
5 return View(new DinnerFormViewModel(dinner));
6 }

在View视图模板中
头部Inherits = “System.Web.Mvc.ViewPage<NerdDinner.Models.Dinner>”改为
Inherits = “System.Web.Mvc.ViewPage<NerdDinner.Controllers.DinnerFormViewModel>”
绑定列表即为<% = Html.DropDownList(“Country”.Model.Countries) %>

下面我们需要更新视图中的代码。对于表单中的HTML元素的名称不需要更新,仍旧保持为Title、Country等等,我们需要更新HTML辅助方法,使用DinnerFormViewModel类来获取属性值。
01 <p>
02 <label for="Title">Dinner Title:</label>
03 <%= Html.TextBox("Title", Model.Dinner.Title) %>
04 <%= Html.ValidationMessage("Title", "*") %>
05 </p>
06 <p>
07 <label for="Country">Country:</label>
08 <%= Html.DropDownList("Country", Model.Countries) %>
09 <%= Html.ValidationMessage("Country", "*") %>
10 </p>
同样地,我们也需要更新Edit Post方法,在产生错误时,使用DinnerFormViewModel类传递给视图模板:
01 //
02 // POST: /Dinners/Edit/5
03 [
04 AcceptVerbs(HttpVerbs.Post)]
05 public ActionResult Edit(int id, FormCollection collection) {
06 Dinner dinner = dinnerRepository.GetDinner(id);
07 try {
08 UpdateModel(dinner);
09 dinnerRepository.Save();
10 return RedirectToAction("Details", new { id=dinner.DinnerID });
11 }
12 catch {
13 ModelState.AddModelErrors(dinner.GetRuleViolations());
14 return View(new DinnerFormViewModel(dinner));
15 }
16 }
我们也更新Create() Action方法,重用相同的DinnerFormViewModel类,在View中实现Country下拉列表框。下面是HTTP-GET的实现代码:
1 //
2 // GET: /Dinners/Create
3 public ActionResult Create() {
4 Dinner dinner = new Dinner() {
5 EventDate = DateTime.Now.AddDays(7)
6 };
7 return View(new DinnerFormViewModel(dinner));
8 }
下面是HTTP-POST Create方法的实现代码:
01 //
02 // POST: /Dinners/Create
03 [AcceptVerbs(HttpVerbs.Post)]
04 public ActionResult Create(Dinner dinner) {
05 if (ModelState.IsValid) {
06 try {
07 dinner.HostedBy = "SomeUser";
08 dinnerRepository.Add(dinner);
09 dinnerRepository.Save();
10 return RedirectToAction("Details", new { id=dinner.DinnerID });
11 }
12 catch {
13 ModelState.AddModelErrors(dinner.GetRuleViolations());
14 }
15 }
16 return View(new DinnerFormViewModel(dinner));
17 }

定制ViewModel类(Custom-shaped ViewModel Classes
在上面的实现方案中,DinnerFormViewModel类直接公开了2个公有属性:Dinner 模型对象和SelectList模型属性。这一方法适合于View模板中HTML用户界面元素和业务Model对象比较接近的场景。
如果不符合这一情况,可以考虑创建定制的ViewModel类,根据视图的使 用情况创建优化的对象模型 – 该对象模型可能完全不同于底层的业务模型对象(Domain Model Object)。例如,该ViewModel类有可能公开不同的属性或者从多个Model对象中汇总的属性。
定制的ViewModel类不仅可用来从Controller传递数据到 View去呈现,而且可用来处理从表单提交回来给Controller的action方法的数据。针对后一种情况,你可以让Action方法根据表单提交 回来的数据更新ViewModel对象,接着使用ViewModel实例来映射或者获取时间的业务模型对象(Domain Model Object)。
定制ViewModel类提供了很好的灵活性,在任何时候,你发现View模 板中的呈现代码或Action方法中表单提交代码越来越开始复杂时,你可以考虑使用定制的ViewModel了。通常,这意味着业务模型对象和View视 图中的用户界面元素不一致,一个中介的定制ViewModel类就可以发挥作用了。

MVC模式还有很多妙用,以后还会写更多有关MVC的文章。

[转载]JSON资料汇总

mikel阅读(1314)

[转载]JSON资料汇总 – 肖品 – 博客园.

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScript Programming Language, Standard ECMA-262 3rd Edition – December 1999的一个子集。 JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C, C++, C#, Java, JavaScript, Perl, Python等)。这些特性使JSON成为理想的数据交换语言。

JSON建构于两种结构:

  • 名称/对的集合(A collection of name/value pairs)。不同的语言中,它被理解为对象(object,纪录(record),结构(struct),字典(dictionary),哈希表(hash table),有键列表(keyed list),或者关联数组 (associative array)。
  • 值的有序列表(An ordered list of values)。在大部分语言中,它被理解为数组(array)。

这些都是常见的数据结构。事实上大部分现代计算机语言都以某种形式支持它们。这使得一种数据格式在同样基于这些结构的编程语言之间交换成为可能。

JSON具有以下这些形式:

对象是一个无序的“‘名称/集合。一个对象以“{”(左括号)开始,“}”(右括号)结束。每个名称后跟一个“:”(冒号);“‘名称/之间使用“,”(逗号)分隔。

数组是值(value)的有序集合。一个数组以“[”(左中括号)开始,“]”(右中括号)结束。值之间使用“,”(逗号)分隔。

值(value)可以是双引号括起来的字符串(string)、数值(number)truefalse null、对象(object)或者数组(array)。这些结构可以嵌套。

字符串(string)是由双引号包围的任意数量Unicode字符的集合,使用反斜线转义。一个字符(character)即一个单独的字符串(character string)。

字符串(string)与C或者Java的字符串非常相似。

数值(number)也与C或者Java的数值非常相似。除去未曾使用的八进制与十六进制格式。除去一些编码细节。

空白可以加入到任何符号之间。

以下是相关学习资料:

官方介绍:
http://www.json.org/json-zh.html

微软介绍:
http://msdn.microsoft.com/zh-cn/library/bb299886.aspx

IBM介绍:
http://www.ibm.com/developerworks/cn/web/wa-ajaxintro/

实例说明:

JSON 基础

简单地说,JSON 可以将 JavaScript 对象中表示的一组数据转换为字符串,然后就可以在函数之间轻松地传递这个字符串,或者在异步应用程序中将字符串从 Web 客户机传递给服务器端程序。这个字符串看起来有点儿古怪(稍后会看到几个示例),但是 JavaScript 很容易解释它,而且 JSON 可以表示比名称/值对更复杂的结构。例如,可以表示数组和复杂的对象,而不仅仅是键和值的简单列表。

简单 JSON 示例

按照最简单的形式,可以用下面这样的 JSON 表示名称/值对:

{ “firstName”: “Brett” }
这个示例非常基本,而且实际上比等效的纯文本名称/值对占用更多的空间:

firstName=Brett
但是,当将多个名称/值对串在一起时,JSON 就会体现出它的价值了。首先,可以创建包含多个名称/值对的记录,比如:

{ “firstName”: “Brett”, “lastName”:”McLaughlin”, “email”: “brett@newInstance.com” }
从语法方面来看,这与名称/值对相比并没有很大的优势,但是在这种情况下 JSON 更容易使用,而且可读性更好。例如,它明确地表示以上三个值都是同一记录的一部分;花括号使这些值有了某种联系。

值的数组

当需要表示一组值时,JSON 不但能够提高可读性,而且可以减少复杂性。例如,假设您希望表示一个人名列表。在 XML 中,需要许多开始标记和结束标记;如果使用典型的名称/值对(就像在本系列前面文章中看到的那种名称/值对),那么必须建立一种专有的数据格式,或者将键 名称修改为 person1-firstName 这样的形式。

如果使用 JSON,就只需将多个带花括号的记录分组在一起:

{ “people”: [
{ “firstName”: “Brett”, “lastName”:”McLaughlin”, “email”: “brett@newInstance.com” },
{ “firstName”: “Jason”, “lastName”:”Hunter”, “email”: “jason@servlets.com” },
{ “firstName”: “Elliotte”, “lastName”:”Harold”, “email”: “elharo@macfaq.com” }
]}
这不难理解。在这个示例中,只有一个名为 people 的变量,值是包含三个条目的数组,每个条目是一个人的记录,其中包含名、姓和电子邮件地址。上面的示例演示如何用括号将记录组合成一个值。当然,可以使用相同的语法表示多个值(每个值包含多个记录):

{ “programmers”: [
{ “firstName”: “Brett”, “lastName”:”McLaughlin”, “email”: “brett@newInstance.com” },
{ “firstName”: “Jason”, “lastName”:”Hunter”, “email”: “jason@servlets.com” },
{ “firstName”: “Elliotte”, “lastName”:”Harold”, “email”: “elharo@macfaq.com” }
],
“authors”: [
{ “firstName”: “Isaac”, “lastName”: “Asimov”, “genre”: “science fiction” },
{ “firstName”: “Tad”, “lastName”: “Williams”, “genre”: “fantasy” },
{ “firstName”: “Frank”, “lastName”: “Peretti”, “genre”: “christian fiction” }
],
“musicians”: [
{ “firstName”: “Eric”, “lastName”: “Clapton”, “instrument”: “guitar” },
{ “firstName”: “Sergei”, “lastName”: “Rachmaninoff”, “instrument”: “piano” }
]
}
这里最值得注意的是,能够表示多个值,每个值进而包含多个值。但是还应该注意,在不同的主条目(programmers、authors 和 musicians)之间,记录中实际的名称/值对可以不一样。JSON 是完全动态的,允许在 JSON 结构的中间改变表示数据的方式。

在处理 JSON 格式的数据时,没有需要遵守的预定义的约束。所以,在同样的数据结构中,可以改变表示数据的方式,甚至可以以不同方式表示同一事物。

在 JavaScript 中使用 JSON

掌握了 JSON 格式之后,在 JavaScript 中使用它就很简单了。JSON 是 JavaScript 原生格式,这意味着在 JavaScript 中处理 JSON 数据不需要任何特殊的 API 或工具包。

将 JSON 数据赋值给变量

例如,可以创建一个新的 JavaScript 变量,然后将 JSON 格式的数据字符串直接赋值给它:

var people =
{ “programmers”: [
{ “firstName”: “Brett”, “lastName”:”McLaughlin”, “email”: “brett@newInstance.com” },
{ “firstName”: “Jason”, “lastName”:”Hunter”, “email”: “jason@servlets.com” },
{ “firstName”: “Elliotte”, “lastName”:”Harold”, “email”: “elharo@macfaq.com” }
],
“authors”: [
{ “firstName”: “Isaac”, “lastName”: “Asimov”, “genre”: “science fiction” },
{ “firstName”: “Tad”, “lastName”: “Williams”, “genre”: “fantasy” },
{ “firstName”: “Frank”, “lastName”: “Peretti”, “genre”: “christian fiction” }
],
“musicians”: [
{ “firstName”: “Eric”, “lastName”: “Clapton”, “instrument”: “guitar” },
{ “firstName”: “Sergei”, “lastName”: “Rachmaninoff”, “instrument”: “piano” }
]
}
这非常简单;现在 people 包含前面看到的 JSON 格式的数据。但是,这还不够,因为访问数据的方式似乎还不明显。

访问数据

尽管看起来不明显,但是上面的长字符串实际上只是一个数组;将这个数组放进 JavaScript 变量之后,就可以很轻松地访问它。实际上,只需用点号表示法来表示数组元素。所以,要想访问 programmers 列表的第一个条目的姓氏,只需在 JavaScript 中使用下面这样的代码:

people.programmers[0].lastName;
注意,数组索引是从零开始的。所以,这行代码首先访问 people 变量中的数据;然后移动到称为 programmers 的条目,再移动到第一个记录([0]);最后,访问 lastName 键的值。结果是字符串值 “McLaughlin”。

下面是使用同一变量的几个示例。

people.authors[1].genre   // Value is “fantasy”

people.musicians[3].lastName // Undefined. This refers to the fourth entry,
and there isn’t one

people.programmers.[2].firstName // Value is “Elliotte”
利用这样的语法,可以处理任何 JSON 格式的数据,而不需要使用任何额外的 JavaScript 工具包或 API。

修改 JSON 数据

正如可以用点号和括号访问数据,也可以按照同样的方式轻松地修改数据:

people.musicians[1].lastName = “Rachmaninov”;
在将字符串转换为 JavaScript 对象之后,就可以像这样修改变量中的数据。

转换回字符串

当然,如果不能轻松地将对象转换回本文提到的文本格式,那么所有数据修改都没有太大的价值。在 JavaScript 中这种转换也很简单:

String newJSONtext = people.toJSONString();
这样就行了!现在就获得了一个可以在任何地方使用的文本字符串,例如,可以将它用作 Ajax 应用程序中的请求字符串。

更重要的是,可以将任何 JavaScript 对象转换为 JSON 文本。并非只能处理原来用 JSON 字符串赋值的变量。为了对名为 myObject 的对象进行转换,只需执行相同形式的命令:

String myObjectInJSON = myObject.toJSONString();
这就是 JSON 与本系列讨论的其他数据格式之间最大的差异。如果使用 JSON,只需调用一个简单的函数,就可以获得经过格式化的数据,可以直接使用了。对于其他数据格式,需要在原始数据和格式化数据之间进行转换。即使使用 Document Object Model 这样的 API(提供了将自己的数据结构转换为文本的函数),也需要学习这个 API 并使用 API 的对象,而不是使用原生的 JavaScript 对象和语法。

最终结论是,如果要处理大量 JavaScript 对象,那么 JSON 几乎肯定是一个好选择,这样就可以轻松地将数据转换为可以在请求中发送给服务器端程序的格式。

[转载]MSSQL分页:一个高效的二分法TOP MAX/TOP MIN分页存储过程

mikel阅读(1350)

[转载]MSSQL分页:一个高效的二分法TOP MAX/TOP MIN分页存储过程 – 熊哥 www.relaxlife.net – 博客园.

CREATE PROCEDURE [dbo].[getPagerTopMAX] (
@tblName nvarchar(2000), –要显示的表或多个表的连接,如:”myTable”
@ID nvarchar(150), –主表的主键,如:”id”
@page int = 1, –要显示那一页的记录
@pageSize int = 10, –每页显示的记录个数
@fldName nvarchar(500) = *, –要显示的字段列表,如:”id, cdsinger, cdsingertype, area, cdsingerreadme”
@fldSort nvarchar(200) = @ID, –排序字段列表或条件
@Sort bit = 1, –排序方法,0为升序,1为降序(如果是多字段排列Sort指代最后一个排序字段的排列顺序(最后一个排序字段不加排序标记)–程序传参如:’ SortA Asc,SortB Desc,SortC ‘)
@strCondition nvarchar(1000) = null, –查询条件,不需where,如:”and a=1 and b=2″
@Dist bit = 0, –是否添加查询字段的 DISTINCT 默认0不添加/1添加
@Counts int = 1 output, –查询到的记录数
@pageCount int = 1 output –查询结果分页后的总页数
) AS begin
SET NOCOUNT ON
Declare @SQLTmp nvarchar(3000) –存放动态生成的SQL语句
Declare @strTmp nvarchar(3000) –存放取得查询结果总数的查询语句
Declare @strID nvarchar(3000) –存放取得查询开头或结尾ID的查询语句

Declare @strSortType nvarchar(10) –数据排序规则A
Declare @strFSortType nvarchar(10) –数据排序规则B

Declare @SQLSelect nvarchar(50) –对含有DISTINCT的查询进行SQL构造
Declare @SqlCounts nvarchar(50) –对含有DISTINCT的总数查询进行SQL构造

if @Dist = 0
begin
set @SqlSelect = select
set @SqlCounts = Count(*)
end
else
begin
set @SqlSelect = select distinct
set @SqlCounts = Count(DISTINCT +@ID+)
end

if @Sort=0
begin
set @strFSortType= ASC
set @strSortType= DESC
end
else
begin
set @strFSortType= DESC
set @strSortType= ASC
end

——生成查询语句——–

此处@strTmp为取得查询结果数量的语句
if @strCondition is null or @strCondition= 没有设置显示条件
begin
set @sqlTmp = @fldName + From + @tblName
set @strTmp = @SqlSelect+ @Counts=+@SqlCounts+ FROM +@tblName
set @strID = From + @tblName
end
else
begin
set @sqlTmp = + @fldName + From + @tblName + where (1>0) + @strCondition
set @strTmp = @SqlSelect+ @Counts=+@SqlCounts+ FROM +@tblName + where (1>0) + @strCondition
set @strID = From + @tblName + where (1>0) + @strCondition
end

–取得查询结果总数量—–
exec sp_executesql @strTmp,N@Counts int out ,@Counts out
declare @tmpCounts int
if @Counts = 0
set @tmpCounts = 1
else
set @tmpCounts = @Counts

取得分页总数
set @pageCount=(@tmpCounts+@pageSize1)/@pageSize

/**//**当前页大于总页数 取最后一页**/
if @page>@pageCount
set @page=@pageCount

/*—–数据分页2分处理——-*/
declare @pageIndex int 总数/页大小
declare @lastcount int 总数%页大小

set @pageIndex = @tmpCounts/@pageSize
set @lastcount = @tmpCounts%@pageSize
if @lastcount > 0
set @pageIndex = @pageIndex + 1
else
set @lastcount = @pagesize

//***显示分页
if @strCondition is null or @strCondition= 没有设置显示条件
begin
if @pageIndex<2 or @page<=@pageIndex / 2 + @pageIndex % 2 前半部分数据处理
begin
if @page=1
set @strTmp=@SqlSelect+ top + CAST(@pageSize as VARCHAR(20))+ + @fldName+ from +@tblName
+ order by + @fldSort + + @strFSortType
else
begin
set @strTmp=@SqlSelect+ top + CAST(@pageSize as VARCHAR(20))+ + @fldName+ from +@tblName
+ where +@ID+ <(select min(+ @ID +) from (+ @SqlSelect+ top + CAST(@pageSize*(@page1) as Varchar(20)) + + @ID + from +@tblName
+ order by + @fldSort + + @strFSortType+) AS TBMinID)
+ order by + @fldSort + + @strFSortType
end
end
else
begin
set @page = @pageIndex@page+1 后半部分数据处理
if @page <= 1 最后一页数据显示
set @strTmp=@SqlSelect+ * from (+@SqlSelect+ top + CAST(@lastcount as VARCHAR(20))+ + @fldName+ from +@tblName
+ order by + @fldSort + + @strSortType+) AS TempTB+ order by + @fldSort + + @strFSortType
else
set @strTmp=@SqlSelect+ * from (+@SqlSelect+ top + CAST(@pageSize as VARCHAR(20))+ + @fldName+ from +@tblName
+ where +@ID+ >(select max(+ @ID +) from(+ @SqlSelect+ top + CAST(@pageSize*(@page2)+@lastcount as Varchar(20)) + + @ID + from +@tblName
+ order by + @fldSort + + @strSortType+) AS TBMaxID)
+ order by + @fldSort + + @strSortType+) AS TempTB+ order by + @fldSort + + @strFSortType
end
end

else 有查询条件
begin
if @pageIndex<2 or @page<=@pageIndex / 2 + @pageIndex % 2 前半部分数据处理
begin
if @page=1
set @strTmp=@SqlSelect+ top + CAST(@pageSize as VARCHAR(20))+ + @fldName+ from +@tblName
+ where 1=1 + @strCondition + order by + @fldSort + + @strFSortType
else
begin
set @strTmp=@SqlSelect+ top + CAST(@pageSize as VARCHAR(20))+ + @fldName+ from +@tblName
+ where +@ID+ <(select min(+ @ID +) from (+ @SqlSelect+ top + CAST(@pageSize*(@page1) as Varchar(20)) + + @ID + from +@tblName
+ where (1=1) + @strCondition + order by + @fldSort + + @strFSortType+) AS TBMinID)
+ + @strCondition + order by + @fldSort + + @strFSortType
end
end
else
begin
set @page = @pageIndex@page+1 后半部分数据处理
if @page <= 1 最后一页数据显示
set @strTmp=@SqlSelect+ * from (+@SqlSelect+ top + CAST(@lastcount as VARCHAR(20))+ + @fldName+ from +@tblName
+ where (1=1) + @strCondition + order by + @fldSort + + @strSortType+) AS TempTB+ order by + @fldSort + + @strFSortType
else
set @strTmp=@SqlSelect+ * from (+@SqlSelect+ top + CAST(@pageSize as VARCHAR(20))+ + @fldName+ from +@tblName
+ where +@ID+ >(select max(+ @ID +) from(+ @SqlSelect+ top + CAST(@pageSize*(@page2)+@lastcount as Varchar(20)) + + @ID + from +@tblName
+ where (1=1) + @strCondition + order by + @fldSort + + @strSortType+) AS TBMaxID)
+ + @strCondition+ order by + @fldSort + + @strSortType+) AS TempTB+ order by + @fldSort + + @strFSortType
end
end

—-返回查询结果—–
print @strTmp
exec (@strTmp)

SET NOCOUNT OFF

end

[转载]发型试戴原理简析

mikel阅读(1285)

[转载]发型试戴原理简析 – xiaotie – 博客园.

RIA 使技术变得有趣起来,比如CAD,Matlab等工具,未来非常适合直接以RIA方式在线运行。技术转型后,给自己的技术方向定的很窄,RIA这一块也只 关注在线图形图像应用,如在线试戴、试穿、试用、试装修等功能。试发系统已经做了好几套了,当然主要还是借鉴国外的系统taazthehairstyles,下面简析一下发型试戴的原理。

发型试戴的两个核心问题:

(1)如何让千奇百怪的发型自动戴在千奇百怪的模特头上,戴在正确的位置上?

(2)戴上之后,用户如何调整发型?不仅仅限于放大,缩小,上下左右挪动,而是“自由”变形。

====

问题1的解决方案:

这是taaz的解决方案。假定一个标准的模特S,脸上有6个坐标点,见图:

image

根据5,6两点,可以求出它的中点7:

image

由点1,4,7构成一个三角形S(1,4,7)。

用户上传自己的头像U,也需要指定1-6点的坐标,由此也可以算出7点的坐标。U(1,4,7)构成另一个三角形。算出 S(1,4,7)=>U(1,4,7) 的仿射变换ASU。ASU确定了标准模特和用户上传模特的仿射变换。

关于仿射变换,这里有一篇文章给了很直观的解释

对于发型,由于有人脸大,有人脸小,有人脸宽,有人脸窄,因此使用的是另一个定位三角形:

image

使用上图中的8,9,10三点进行定位。对于标准模特S,可以测量得出它的8,9,10三点构成的三角形 S(8,9,10)。对于每个发型H,我们也指定一下以上三点,构成三角形H(8,9,10)。

计算H(8,9,10)=> S(8,9,10)的仿射变换AHS。AHS实现了发型图像到标准模特脸上的仿射变换。

如此一来,对于一个发型H,做两次仿射变换——ASU(AHS(H)),即可将发型戴于用户自己的头像U上。这个映射可以让发型正确“找到”自己的位置。

====

问题2的解决方案:

如果想更精细的调整发型试戴的效果,可在发型位置增加一些控制点,使用TPS样条实现变形效果,taaz是这样做的。TPS样条见我的博文 《TPS(薄板样条) 2D 插值》,这里贴张图:

TPS更详细的描述及算法见F.L. Bookstein, “Principal Warps: Thin-Plate Splines and the Decomposition of Deformations,”

也可以使用局部变形算法(见:《图像变形算法:实现Photoshop液化工具箱中向前变形工具》),我是这样干的:

image image image

对比来说,TPS更好用一些,局部变形做细微调整的能力更强一点。

[转载]ASP.NET MVC Best Practices (Part 1)

mikel阅读(957)

[转载]ASP.NET MVC Best Practices (Part 1) – Kazi Manzur Rashid’s Blog.

In this post, I will share some of the best practices/guideline in developing ASP.NET MVC applications which I have learned in the hard way. I will not tell you to use DI or Unit Test instead I will assume you are already doing it and you prefer craftsmanship over anything.

1. Create Extension methods of UrlHelper to generate your url from Route

Avoid passing the controller, action or route name as string, create extension methods of UrlHelper which encapsulates it, for example:

01 public static class UrlHelperExtension
02 {
03 public static string Home(this UrlHelper helper)
04 {
05 return helper.Content("~/");
06 }
07
08 public static string SignUp(this UrlHelper helper)
09 {
10 return helper.RouteUrl("Signup");
11 }
12
13 public static string Dashboard(this UrlHelper helper)
14 {
15 return Dashboard(helper, StoryListTab.Unread);
16 }
17
18 public static string Dashboard(this UrlHelper helper, StoryListTab tab)
19 {
20 return Dashboard(helper, tab, OrderBy.CreatedAtDescending, 1);
21 }
22
23 public static string Dashboard(this UrlHelper helper, StoryListTab tab, OrderBy orderBy, int page)
24 {
25 return helper.RouteUrl("Dashboard", new { tab = tab.ToString(), orderBy = orderBy.ToString(), page });
26 }
27
28 public static string Update(this UrlHelper helper)
29 {
30 return helper.RouteUrl("Update");
31 }
32
33 public static string Submit(this UrlHelper helper)
34 {
35 return helper.RouteUrl("Submit");
36 }
37 }

Now, You can use the following in your view:

1 <a href="<%= Url.Dashboard() %>">Dashboard</a>
2 <a href="<%= Url.Profile() %>">Profile</a>

Instead of:

1 <%= Html.ActionLink("Dashboard", "Dashboard", "Story") %>
2 <a href="<%= Url.RouteUrl("Profile")%>">Profile</a>

And in Controller I can use:

1 return Redirect(Url.Dashboard(StoryListTab.Favorite, OrderBy.CreatedAtAscending, 1))

Instead of:

1 return RedirectToAction("Dashboard", "Story", new { tab = StoryListTab.Favorite, orderBy = OrderBy.CreatedAtAscending, page = 1 });

Of course you can use the strongly typed version which takes the controller, method and the parameters of the future assembly or create your own to avoid the future refactoring pain, but remember it is not officially supported and might change in the future. You can also use the above with the strongly typed version, certainly “adding another layer of indirection” (Scott Ha favorite quote) has some benefits. Another benefit when writing Unit Test you will only deal with the RedirectResult rather than RediretResult and RedirectToRouteResult.

2. Create Extension Method of UrlHelper to map your JavaScript, Stylesheet and Image Folder

By default ASP.NET MVC creates Content, Scripts folder for these things, which I do not like, Instead I like the following folder structure so that I can only set static file caching  on the Assets folder in IIS instead of going to multiple folders:

Assets
+images
+scripts
+stylesheets

No matter what the structure is, create some extension method of UrlHelper to map these folders, so that you can easily refer it in your view and later on if you need to change the structure, you do not have to do massive find/replace. I would also recommend to create extension methods for those assets which are often refereed in your views. For example:

01 public static string Image(this UrlHelper helper, string fileName)
02 {
03 return helper.Content("~/assets/images/{0}".FormatWith(fileName));
04 }
05
06 public static string Stylesheet(this UrlHelper helper, string fileName)
07 {
08 return helper.Content("~/assets/stylesheets/{0}".FormatWith(fileName));
09 }
10
11 public static string NoIcon(this UrlHelper helper)
12 {
13 return Image(helper, "noIcon.png");
14 }

And when referring the assets you can use:

1 <link href="<%= Url.Stylesheet("site.css")%>" rel="stylesheet" type="text/css"/>

Instead of the default:

1 <link href="../../Content/Site.css" rel="stylesheet" type="text/css" />

3. Use Bootstrapper in Global.asax

I have already covered it in the past, basically if you are doing a lot things in Application_Start of global.asax e.g. Registering Routes, Registering Controller Factory, Model Binders, View Engine, starting application specific background services, create individual task for specific part, then do use Bootstrapper to execute those. This make your code lot more clean and testable. This will be also helpful when developing a portal kind of app in asp.net mvc where each module can have some startup initialization without affecting others. But if you are developing a small app where the above things will never be an issue you can surly go ahead with the default global.asax.

4. Do not make any hard dependency on the DI Container, use Common Service Locator

Do not clutter your code with any specific DI reference, instead use the Common Service Locator, it is an abstraction over the underlying DI and it has the support for all of the popular DI containers, so that you can replace the underlying DI without modifying your application code as each DI Container has some unique features over the others. Tim Barcz recently wrote a excellent post on this subject, how much obsessed we are with our favorite DI Container but I am not sure why he did not mention about it. The Common Service Locator has the support for most of the regular scenarios, but for specific case for example injecting dependency in already instantiated object which as per my knowledge StructureMap, Ninject and Unity supports you can call the static ServiceLocator.Current.GetInstance in the constructor instead of calling the underlying DI. And for those who do know, Common Service Locator is a joint effort of the DI Containers creators initiated by Jeremy D Miller.

Creating Controller Factory with Common Service Locator is very easy:

1 public class CommonServiceLocatorControllerFactory : DefaultControllerFactory
2 {
3 protected override IController GetControllerInstance(Type controllerType)
4 {
5 return (controllerType == null) ? base.GetControllerInstance(controllerType) : ServiceLocator.Current.GetInstance(controllerType) as IController;
6 }
7 }

I hope the MVCContrib guys will follow the same instead of creating separate Controller Factory for each Container.

5. Decorate your Action Methods with Proper AcceptVerbs Attribute

ASP.NET MVC is much more vulnerable comparing to Web Forms. Make sure the action methods that modifies the data only accepts HttpVerbs.Post. If security is too much concern use the ValidateAntiForgeryToken or you can use Captcha. Derik Whittaker has an excellent post as well as a screen cast on how to integrate reCaptcha with ASP.NET MVC application, which I highly recommend. (Side Note: Do not miss a single episode of DimeCasts.net, I have learnt a lot form those short screen casts). My rule of thumb is use HttpVerbs.Post for all data modification actions and HttpVerbs.Get for data reading operations.

6. Decorate your most frequent Action Methods with OutputCache Attribute

Use OutputCache attribute when you are returning the less frequent updated data, prime candidate may be your home page, feed etc etc. You can use it for both Html and Json data types. When using it, only specify the Cache Profile name, do not not specify any other thing, use the web.config output cache section to fine tune it. For example:

1 [AcceptVerbs(HttpVerbs.Get), OutputCache(CacheProfile = "Dashboard")]
2 public ActionResult Dashboard(string userName, StoryListTab tab, OrderBy orderBy, int? page)
3 {
4 }

And in web.config:

01 <system.web>
02 <caching>
03 <outputCacheSettings>
04 <outputCacheProfiles>
05 <clear/>
06 <!-- 15 Seconds -->
07 <add
08 name="Dashboard"
09 duration="15"
10 varyByParam="*"
11 location="Client"
12 />
13 </outputCacheProfiles>
14 </outputCacheSettings>
15 </caching>
16 </system.web>

7. Keep your controller free from HttpContext and its tail

Make sure your controller does not have to refer the HttpContext and its tail. it will make your life easier when unit testing your Controller. If you need to access anything from HttpContext like User, QueryString, Cookie etc use custom action filter or create some interface and wrapper and pass it in the constructor. For example, for the following Route:

1 _routes.MapRoute("Dashboard", "Dashboard/{tab}/{orderBy}/{page}", new { controller = "Story", action = "Dashboard", tab = StoryListTab.Unread.ToString(), orderBy = OrderBy.CreatedAtDescending.ToString(), page = 1 });

But the controller action methods is declared as:

1 [AcceptVerbs(HttpVerbs.Get), OutputCache(CacheProfile = "Dashboard"), UserNameFilter]
2 public ActionResult Dashboard(string userName, StoryListTab tab, OrderBy orderBy, int? page)
3 {
4 }

The UserNameFilter is responsible for passing the UserName:

01 public class UserNameFilter : ActionFilterAttribute
02 {
03 public override void OnActionExecuting(ActionExecutingContext filterContext)
04 {
05 const string Key = "userName";
06
07 if (filterContext.ActionParameters.ContainsKey(Key))
08 {
09 if (filterContext.HttpContext.User.Identity.IsAuthenticated)
10 {
11 filterContext.ActionParameters[Key] = filterContext.HttpContext.User.Identity.Name;
12 }
13 }
14
15 base.OnActionExecuting(filterContext);
16 }
17 }

[Update: Make sure you have decorate either the Action or the Controller with Authorize attribute, check the comments]

8. Use Action Filter to Convert to compatible Action Methods parameters

Use Action Filter to convert incoming values to your controller action method parameters, again consider the Dashboard method, we are accepting tab and orderBy as Enum.

1 [AcceptVerbs(HttpVerbs.Get), OutputCache(CacheProfile = "Dashboard"), StoryListFilter]
2 public ActionResult Dashboard(string userName, StoryListTab tab, OrderBy orderBy, int? page)
3 {
4 }

The StoryListFilter will be responsible to convert it to proper data type from route values/querystrings.

01 public class StoryListFilter : ActionFilterAttribute
02 {
03 public override void OnActionExecuting(ActionExecutingContext filterContext)
04 {
05 const string TabKey = "tab";
06 const string OrderByKey = "orderBy";
07
08 NameValueCollection queryString = filterContext.HttpContext.Request.QueryString;
09
10 StoryListTab tab = string.IsNullOrEmpty(queryString[TabKey]) ?
11 filterContext.RouteData.Values[TabKey].ToString().ToEnum(StoryListTab.Unread) :
12 queryString[TabKey].ToEnum(StoryListTab.Unread);
13
14 filterContext.ActionParameters[TabKey] = tab;
15
16 OrderBy orderBy = string.IsNullOrEmpty(queryString[OrderByKey]) ?
17 filterContext.RouteData.Values[OrderByKey].ToString().ToEnum(OrderBy.CreatedAtDescending) :
18 queryString[OrderByKey].ToEnum(OrderBy.CreatedAtDescending);
19
20 filterContext.ActionParameters[OrderByKey] = orderBy;
21
22 base.OnActionExecuting(filterContext);
23 }
24 }

You can also use the custom Model Binder for the same purpose. In that case you will have to create two custom Model Binders for each Enum instead of one action filter. Another issue with the Model Binder is once it is registered for a type it will always come into action, but action filter can be selectively applied.

9. Action Filter Location

If you need the same action filter to all of your controller action methods,  put it in the controller rather than each action method. If you want to apply the same action filter to all of your controller create a base controller and inherit from that base controller, for example the story controller should be only used when user is logged in and we need to pass the current user name in its methods, also the StoryController should compress the data when returning:

1 [Authorize, UserNameFilter]
2 public class StoryController : BaseController
3 {
4 }
5
6 [CompressFilter]
7 public class BaseController : Controller
8 {
9 }

But if the inheritance hierarchy is going more than 2 /3 level deep, find another way to apply the filters. The latest Oxite code has some excellent technique applying filters dynamically which I highly recommend to check.

10. Use UpdateModel Carefully

I do not want to repeat what Justin Etheredge has mentioned in his post, be careful and do not fall into that trap.

11.Controller will not contain any Domain logic

Controller should be only responsible for:

  • Validating Input
  • Calling Model to prepare the view
  • Return the view or redirect to another action

If you are doing any other thing you are doing it in a wrong place, it is rather the Model responsibility which you are doing in Controller. If you follow this rule your action method will not be more than 20 – 25 lines of code. Ian Cooper has an excellent post Skinny Controller Fat Model, do read it.

12. Avoid ViewData, use ViewData.Model

Depending upon the dictionary key will not only make your code hard to refactor, also you will have to write the casting code in your view. It is completely okay even you end up per class for each action method of your controller.  If you think, creating these kind of classes is a tedious job, you can use the ViewDataExtensions of the MVCContrib project, it has some nice extension for returning strongly typed objects, though you still have to depend upon the string key if you have more than one data type in ViewData Dictionary.

13. Use PRG Pattern for Data Modification

Tim Barcz, Matt Hawley, Stephen Walther and even The Gu has blogged this over here, here, here and here. One of the issue with this pattern is when a validation fails or any  exception occurs you have to copy the ModelState into TempData. If you are doing it manually, please stop it, you can do this automatically with Action Filters, like the following:

01 [AcceptVerbs(HttpVerbs.Get), OutputCache(CacheProfile = "Dashboard"), StoryListFilter, ImportModelStateFromTempData]
02 public ActionResult Dashboard(string userName, StoryListTab tab, OrderBy orderBy, int? page)
03 {
04 //Other Codes
05 return View();
06 }
07
08 [AcceptVerbs(HttpVerbs.Post), ExportModelStateToTempData]
09 public ActionResult Submit(string userName, string url)
10 {
11 if (ValidateSubmit(url))
12 {
13 try
14 {
15 _storyService.Submit(userName, url);
16 }
17 catch (Exception e)
18 {
19 ModelState.AddModelError(ModelStateException, e);
20 }
21 }
22
23 return Redirect(Url.Dashboard());
24 }

And the Action Filers

01 public abstract class ModelStateTempDataTransfer : ActionFilterAttribute
02 {
03 protected static readonly string Key = typeof(ModelStateTempDataTransfer).FullName;
04 }
05
06 public class ExportModelStateToTempData : ModelStateTempDataTransfer
07 {
08 public override void OnActionExecuted(ActionExecutedContext filterContext)
09 {
10 //Only export when ModelState is not valid
11 if (!filterContext.Controller.ViewData.ModelState.IsValid)
12 {
13 //Export if we are redirecting
14 if ((filterContext.Result is RedirectResult) || (filterContext.Result is RedirectToRouteResult))
15 {
16 filterContext.Controller.TempData[Key] = filterContext.Controller.ViewData.ModelState;
17 }
18 }
19
20 base.OnActionExecuted(filterContext);
21 }
22 }
23
24 public class ImportModelStateFromTempData : ModelStateTempDataTransfer
25 {
26 public override void OnActionExecuted(ActionExecutedContext filterContext)
27 {
28 ModelStateDictionary modelState = filterContext.Controller.TempData[Key] as ModelStateDictionary;
29
30 if (modelState != null)
31 {
32 //Only Import if we are viewing
33 if (filterContext.Result is ViewResult)
34 {
35 filterContext.Controller.ViewData.ModelState.Merge(modelState);
36 }
37 else
38 {
39 //Otherwise remove it.
40 filterContext.Controller.TempData.Remove(Key);
41 }
42 }
43
44 base.OnActionExecuted(filterContext);
45 }
46 }

The MVCContrib project also has this feature but they are doing it in a single class which I do not like, I would like to have more control which method to export and which to import.

14. Create Layer Super Type for your ViewModel and Use Action Filter to populate common parts.

Create a layer super type for your view model classes and use action filter to populate common things into it . For example the tiny little application that I am developing I need to know the User Name and whether the User is authenticated.

01 public class ViewModel
02 {
03 public bool IsUserAuthenticated
04 {
05 get;
06 set;
07 }
08
09 public string UserName
10 {
11 get;
12 set;
13 }
14 }

and the action filter:

01 public class ViewModelUserFilter : ActionFilterAttribute
02 {
03 public override void OnActionExecuted(ActionExecutedContext filterContext)
04 {
05 ViewModel model;
06
07 if (filterContext.Controller.ViewData.Model == null)
08 {
09 model = new ViewModel();
10 filterContext.Controller.ViewData.Model = model;
11 }
12 else
13 {
14 model = filterContext.Controller.ViewData.Model as ViewModel;
15 }
16
17 if (model != null)
18 {
19 model.IsUserAuthenticated = filterContext.HttpContext.User.Identity.IsAuthenticated;
20
21 if (model.IsUserAuthenticated)
22 {
23 model.UserName = filterContext.HttpContext.User.Identity.Name;
24 }
25 }
26
27 base.OnActionExecuted(filterContext);
28 }
29 }

As you can see that it not replacing the model, if it is previously set in the controller, rather it populates the common part if it finds it compatible. Other benefit is, the views that only depends the layer super type you can simply return View() instead of creating the model.

That’s it for today, I will post rest of the items tomorrow.

Stay tuned.

[转载]ASP.NET MVC Best Practices (Part 2)

mikel阅读(826)

[转载]ASP.NET MVC Best Practices (Part 2) – Kazi Manzur Rashid’s Blog.

ASP.NET MVC Best Practices (Part 2)

This is the second part of the series and may be the last, till I find some thing new. My plan was to start with routing, controller, controller to model, controller to view and last of all the view, but some how I missed one important thing in routing, so I will begin with that in this post.

15. Routing consideration

If you are developing a pure ASP.NET MVC application, turn off existing file check of routes, it will eliminate unnecessary file system check. Once you do it there are few more things you have to consider. Remember when you are hosting application in IIS7 integrated mode, your ASP.NET application will intercept all kind of request, no matter what the file extension is. So you have to add few more things in the ignore list which your ASP.NET MVC application will not process. This might include static files like html, htm, text file specially robots.txt, favicon.ico, script, image and css etc. This is one of the reason, why I do not like the default directory structure (Contents and Scripts folder) mentioned in my #2 in the previous post. The following is somewhat my standard template for defining routes when hosting in IIS7:

01 _routes.Clear();
02
03 // Turns off the unnecessary file exists check
04 _routes.RouteExistingFiles = true;
05
06 // Ignore text, html, files.
07 _routes.IgnoreRoute("{file}.txt");
08 _routes.IgnoreRoute("{file}.htm");
09 _routes.IgnoreRoute("{file}.html");
10
11 // Ignore axd files such as assest, image, sitemap etc
12 _routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
13
14 // Ignore the assets directory which contains images, js, css & html
15 _routes.IgnoreRoute("assets/{*pathInfo}");
16
17 // Ignore the error directory which contains error pages
18 _routes.IgnoreRoute("ErrorPages/{*pathInfo}");
19
20 //Exclude favicon (google toolbar request gif file as fav icon which is weird)
21 _routes.IgnoreRoute("{*favicon}", new { favicon = @"(.*/)?favicon.([iI][cC][oO]|[gG][iI][fF])(/.*)?" });
22
23 //Actual routes of my application

Next, few of my personal preference rather than guideline, by default ASP.NET MVC generates url like {controller}/{action} which is okay when you are developing multi-module application,  for a small application, I usually prefer the action name without the controller name, so instead of www.yourdomain.com/Story/Dashboard, www.yourdomain.com/Membership/SignIn it will generate www.yourdomain.com/Dashboard, www.yourdomain.com/Signin. So I add few more routes:

01 _routes.MapRoute("SignUp", "SignUp", new { controller = "Membership", action = "SignUp" });
02 _routes.MapRoute("SignIn", "SignIn", new { controller = "Membership", action = "SignIn" });
03 _routes.MapRoute("ForgotPassword", "ForgotPassword", new { controller = "Membership", action = "ForgotPassword" });
04 _routes.MapRoute("SignOut", "SignOut", new { controller = "Membership", action = "SignOut" });
05 _routes.MapRoute("Profile", "Profile", new { controller = "Membership", action = "Profile" });
06 _routes.MapRoute("ChangePassword", "ChangePassword", new { controller = "Membership", action = "ChangePassword" });
07
08 _routes.MapRoute("Dashboard", "Dashboard/{tab}/{orderBy}/{page}", new { controller = "Story", action = "Dashboard", tab = StoryListTab.Unread.ToString(), orderBy = OrderBy.CreatedAtDescending.ToString(), page = 1 });
09 _routes.MapRoute("Update", "Update", new { controller = "Story", action = "Update" });
10 _routes.MapRoute("Submit", "Submit", new { controller = "Story", action = "Submit" });
11
12 _routes.MapRoute("Home", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = string.Empty });

16. Create new ActionResult if required

ASP.NET MVC has quite a number of ActionResult for different purposes, but still we might need new ActionResult. For example xml, rss, atom etc. In those cases, I would suggest instead of using the generic ContentResult, create new ActionResult. The MVCContrib has an XmlResult which you can use for returning xml but no support for feed. Yes it is obviously tricky to convert an unknown object into rss/atom, in those cases you can create model specific ActionResult. For example:

01 public class AtomResult : ActionResult
02 {
03 public AtomResult(string siteTitle, string feedTitle, IEnumerable<IStory> stories)
04 {
05 SiteTitle = siteTitle;
06 FeedTitle = feedTitle;
07 Stories = stories;
08 }
09
10 public string SiteTitle
11 {
12 get;
13 private set;
14 }
15
16 public string FeedTitle
17 {
18 get;
19 private set;
20 }
21
22 public IEnumerable<IStory> Stories
23 {
24 get;
25 private set;
26 }
27
28 public override void ExecuteResult(ControllerContext context)
29 {
30 string xml = Build(context);
31
32 HttpResponseBase response = context.HttpContext.Response;
33 response.ContentType = "application/atom+xml";
34 response.Write(xml);
35 }
36 }

And in Controller:

1 [AcceptVerbs(HttpVerbs.Get), OutputCache(CacheProfile = "Atom")]
2 public ActionResult Shared()
3 {
4 IEnumerable<stories> stories = GetSharedStories();
5
6 return new AtomResult("My Site", "My shared stories in atom", stories);
7 }

17. Split your View into multiple ViewUserControl

Split your view into multiple ViewUserControl when it is getting bigger, it really does not matter whether the same UserControl is reused in another page, it makes the very view much more readable. Consider the following view:

01 <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
02 My Secret App : Dashboard
03 </asp:Content>
04 <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
05 <div id="heading"></div>
06 <div class="columns">
07 <div id="main" class="column">
08 <div id="storyListTabs" class="ui-tabs ui-widget ui-widget-content ui-corner-all">
09 <% Html.RenderPartial("TabHeader", Model);%>
10 <div id="tabContent" class="ui-tabs-panel ui-widget-content ui-corner-bottom">
11 <div id="storyList">
12 <% Html.RenderPartial("SortBar", Model);%>
13 <div class="clear"></div>
14 <% Html.RenderPartial("NoLinkMessage", Model);%>
15 <form id="update" action="<%= Url.Update()%>" method="post">
16 <% Html.RenderPartial("List", Model);%>
17 <% Html.RenderPartial("ActionBar", Model);%>
18 <% Html.RenderPartial("Pager", Model);%>
19 </form>
20 </div>
21 </div>
22 </div>
23 <%Html.RenderPartial("Submit", new StorySubmitViewModel());%>
24 </div>
25 <div id="sideBar" class="column"></div>
26 </div>
27 </asp:Content>

18. HtmlHelper extension

First, read this great post of Rob Conery and I completely agree, you should create helper methods for each condition and I would also suggest to create helper methods for reusable UI elements like the ASP.NET MVC team did, but I have a different suggestion about the placing of these methods that we are currently practicing.

Application Developer

Do only create extension methods of HtmlHelper if you are using it in more than one view. Otherwise create a view specific helper and create an extension method of the HtmlHelper which returns the view specific helper. for example:

01 public class DashboardHtmlHelper
02 {
03 private readonly HtmlHelper _htmlHelper;
04
05 public DashboardHtmlHelper(HtmlHelper htmlHelper)
06 {
07 _htmlHelper = htmlHelper;
08 }
09
10 public string DoThis()
11 {
12 //Your Code
13 }
14
15 public string DoThat()
16 {
17 //Your Code
18 }
19 }
20
21 public static class HtmlHelperExtension
22 {
23 public static DashboardHtmlHelper Dashboard(this HtmlHelper htmlHelper)
24 {
25 return new DashboardHtmlHelper(htmlHelper);
26 }
27 }

Now, you will able to use it in the view like:

1 <%= Html.Dashboard().DoThis() %>

UI Component Developer

If you are developing some family of UI components that will be reusable across different ASP.NET MVC application, create a helper with your component family name like the above, if you are a commercial vendor maybe your company name then add those methods in that helper. Otherwise there is a very good chance of method name collision.

The same is also applied if you are extending the IViewDataContainer like the MVCContrib.org.

19. Encode

Whatever you receive from the User always use Html.Encode(“User Input”) for textNode and Html.AttributeEncode(“User Input”) for html element attribute.

20. Do not put your JavaScript codes in your View

Do not intermix your JavaScript with the html, create separate js files and put your java script in those files. Some time, you might need to pass your view data in your java script codes, in those cases only put your initialization in the view. For example, consider you are developing Web 2.0 style app where you want to pass ajax method url, and some other model data  in the java script codes, in those cases you can use some thing like the following:

The View:

1 <div id="sideBar" class="column"></div>
2 <script type="text/JavaScript">
3 $(document).ready(function(){
4 Story.init('<%= Model.UrlFormat %>', '<%= Url.NoIcon() %>', <%= Model.PageCount %>, <%= Model.StoryPerPage %>, <%= Model.CurrentPage %>, '<%= Model.SelectedTab %>', '<%= Model.SelectedOrderBy %>');
5 });
6 </script>
7 </div>
8 </asp:Content>

And JavaScript:

01 var Story =
02 {
03 _urlFormat: '',
04 _noIconUrl: '',
05 _pageCount: 0,
06 _storyPerPage: 0,
07 _currentPage: 0,
08 _currentTab: '',
09 _currentOrderBy: '',
10
11 init: function(urlFormat, noIconUrl, pageCount, storyPerPage, currentPage, currentTab, currentOrderBy)
12 {
13 Story._urlFormat = urlFormat;
14 Story._noIconUrl = noIconUrl;
15 Story._pageCount = pageCount;
16 Story._storyPerPage = storyPerPage;
17 Story._currentPage = currentPage;
18 Story._currentTab = currentTab;
19 Story._currentOrderBy = currentOrderBy;
20
21 //More Codes
22 }
23 }

And those who are not familiar with the above  JavaScript code, it is an example of creating static class in JavaScript. And one more thing before I forgot to mention, never hard code your ajax method url in your javascript file, no  matter Rob Conery or Phil Haack does it in their demo. It is simply a bad practice and demolish the elegance of ASP.NET Routing.

21. Use jQuery and jQuery UI

Use JQuery and jQuery UI, nothing can beats it and use Google CDN to load these libraries.

1 <link type="text/css" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.1/themes/{YOUR Prefered Theme}/JQuery-ui.css" rel="stylesheet"/>
2 <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
3 <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.1/jquery-ui.js"></script>

Or much better:

1 <link type="text/css" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.1/themes/{YOUR Prefered Theme}/jQuery-ui.css" rel="stylesheet"/>
2 <script type="text/javascript" src="http://www.google.com/jsapi"></script>
3 <script type="text/javascript">
4 google.load("jQuery", "1.3.2");
5 google.load("jQueryui", "1.7.1");
6 </script>

And that’s it for the time being.

At the end, I just want congratulate the ASP.NET MVC Team for developing such an excellent framework and specially the way they took the feedback from the community and I look forward to develop few more killer apps in this framework.

[原创]ASP.NET MVC IgnoreRoute使用实例

mikel阅读(1709)

最近做ASP.NET MVC项目时,发现有些页面的图片链接被MVC Route解析成Cotnroller进行处理,提示错误:

The controller for path ‘/Content/images/xxx.gif’ could not be found or it does not implement the IController interface.
参数名: controllerType

于是修改了Global.asax.cs中的代码:

public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute(“{resource}.axd/{*pathInfo}”);
routes.IgnoreRoute(“{*allgif}”, new { alljs = @”.*\.gif(/.*)?” });//加入了路由解析规则,让ASP.NET默认的解析机制进行解析,跳过ASP.NET MVC的Cotroller解析器避免出现上述错误

routes.MapRoute(
“Default”,                                              // Route name
“{controller}/{action}/{id}”,                           // URL with parameters
new { controller = “Home”, action = “Index”, id = “” }  // Parameter defaults
);

}

备注:IgnoreRoute 写法:

routes.IgnoreRoute(“{*过滤规则名称}”, new { 过滤规则名称 = @”正则表达式” });

实例:routes.IgnoreRoute(“{*allgif}”, new { alljs = @”.*\.gif(/.*)?” });
具体IgnoreRoute的使用规则可以阅读下面两篇文章:

ASP.NET MVC Best Practices (Part 2) /

Routing MSDN文档说明:
http://msdn.microsoft.com/en-us/library/system.web.routing.aspx
http://msdn.microsoft.com/en-us/library/system.web.routing.routecollection.aspx

其他:  http://www.asp.net/learn/mvc/tutorial-23-cs.aspx

[转载]想成为优秀的技术人员你必须做到的几件事情

mikel阅读(850)

[转载]想成为优秀的技术人员你必须做到的几件事情 – 海纳百川 – 博客园.

找工作的这几天,收获颇多。思考得最多的问题可能就是对未来的一个规划。无意中看到下面几条经验,发现和自己想的也差不多,就分享出来。我要求自己做到这些,同时也希望对您也有所帮助。英文的原文是:Things you need to remember to become a successful developer

1、保持学习

一个非常重要的观点是:如果你停留在一个地方不前,并不代表你能一直呆在那里,而是代表你正在落后(不进则退)。往前进并不意味着你是就能进步 – 这至少你不会沦落到最后(付出就会有收获) 。程序员为了保持向前发展,就需要不断学习 ,我们需要的不是慢慢的往前走,而是我们要奔跑起来!下面列出这方面的几个观点:

1)、读书

2)、订阅RSS和阅读一些杂志(我觉得msdn的杂志不错)

3)、参加一些研讨会,自己准备一些简报(社区精英、博客园每月的活动等都不错)

4)、学习一切可以帮助你的东西

5)、教其他人其实也是教自己

2、给自己确定目标

如果不知道向什么方向发展,是很难有成就的。这一点关键是自己要有一个清晰地目标。这个应该是个长期的目标,然后要有一个目标的愿景。首先要把这个目标分 成一些小的任务,建立实现这些目标的路线图。你可以创建每月或每年你必须完成的一个任务列表,然后根据这个任务列表去实现。

3、生活中的每一个挑战都是一个机遇

在日常工作时,总是会遇到各种各样的问题。你可能会遇到一些程序的bug;项目经理分配的新任务;你同事请你帮助;你请别人帮助。这些都是一个一个的挑战。问题是:你如何去解决这些挑战?我的一个答案是:你必要调动你所有的激情去应对这些挑战,因为每个挑战就是一个机遇。

4、抱有积极态度

对待每一件事情都应抱着积极的态度。如果发现你犯了一个错误,请不要在意,因为没有人是不会犯错误的。你可以想象一下,有谁喜欢那种一遇到问题就抱怨的人 呢?如果你说“好吧,伙计们,我能做到的,请给我一分钟,我就会解决这个问题。”,然后笑着转过身就修改自己的错误,这样在别人眼里又是如何看待你呢?当 您解决这个问题之后,你会感觉到异常的高兴。

相信自己的方法是正确的,自己正在快速进步。千万不要老是认为自己无法达到自己的目标。

5、给自己寻找一个导师

这并不是说你需要一个人来帮助你来完成你的工作。导师可能就是比你职别高的职员。而且他的职位是你想要去争取的。如果他的职位你争取到了,你可以再选择另外一个更高的。但是你必要一直都需要有这么一个人。也可以能督促你和支持你的朋友、亲人、恋人等等。

6、让自己公众化

如何让别人知道你呢?一个最简单的办法是写博客、提问题、帮别人解决问题。起码要google知道你吧。学会在团队和项目中分享自己的知识。如果你学了新的知识,就把它分享出来!如果你没有分享,下次再用的时候,你可能就忘记了。

7、时不时的审查一下自己,确保自己的方式正确

时不时的,你需要检查你做的方法是否全正确。检查一下是否完成即将完成的目标。如果没有,赶快找原因,加紧去完成。寻找你的薄弱环节,加强它们。这听起来 很滑稽:我认识一个技术很强的开发人员,但他的打字速度却是很慢。为什么?因为他对键盘不熟悉,又不想花10-20小时进行键盘训练。兄弟,如果你会读到 此,请务必要求自己克服此类的问题。

8、保持健康身体

我 写这篇文章之前,算是“集思广益”的。我是一个年轻男子,跟我的笔记本电脑和另一台计算机工作了太长时间,我不能强迫自己做健身运动了。这个有点像磨刀不 误砍柴工。有一个故事,两个樵夫打赌砍树,一个比较强壮,另外一个比较瘦弱。强壮的人相信他会赢得,因为他没有休息,持续砍了8小时。瘦小的那位,每隔一 个小时就休息15分钟。最后瘦小的那位樵夫却赢了。他赢的秘密在于休息的时候将刀磨锋利了。你的健康就像是斧头,如果斧头钝了,你将无法为自己人生道路披 荆斩刺。

[转载]C# 4动态编程新特性与DLR剖析

mikel阅读(878)

[转载]C# 4动态编程新特性与DLR剖析 – 金旭亮 – 博客园.

拓展动态编程的新领域

——C# 4动态编程新特性与DLR剖析

===================================================

注:
很久没有发文了,贴一篇新文吧。从Word直接贴过来的,没仔细排版,诸位海涵。
有关DLR和C# 4动态特性的详细介绍,请参看本人拙著《.NET 4.0面向对象编程揭秘(应用篇)》,目前该书正处于编辑出版流程中,估计12月上市。
与此书相关的技术资源,将陆续发布于博客园与CSDN的本人博客。

金旭亮

=====================================================================================

近几年来,在TIOBE公司每个月发布的编程语言排行榜[1]中,C#总是能挤进前10名,而在近10年的编程语言排行榜中,C#总体上呈现上升的趋势。C#能取得这样的成绩,有很多因素在起作用,其中,它在语言特性上的锐意进取让人印象深刻( 1)。

1 C#各版本的创新点

2010年发布的C# 4,最大的创新点是拥有了动态编程语言的特性。

1 动态编程语言的中兴

动态编程语言并非什么新鲜事物,早在面向对象编程语言成为主流之前,人们就已经使用动态编程语言来开发了。即使在JavaC#C++等面向对象编程语言繁荣兴旺、大行于世的年代,动态编程语言也在“悄悄”地攻城掠地,占据了相当的开发领域,比如 JavaScript业已成为Web客户端事实上的主流语言。

最近这几年,动态编程语言变得日益流行,比如PythonRuby都非常活跃,使用者众多。

这里有一个问题,为什么我们需要在开发中应用动态编程语言?与C#Java这类已经非常成熟且功能强大的静态类型编程语言相比,动态编程语言有何优势?

简单地说,使用动态编程语言开发拥有以下的特性:

1)支持REPLRead-evaluate-print Loop:“读入à执行à输出”循环迭代)的开发模式,整个过程简洁明了,直指问题的核心。

举个简单的例子, 2所示为使用IronPython[2]编程计算“1+2+……+100”的屏幕截图,我们可以快速地输入一段完成累加求和的代码,然后马上就可以看到结果:

2 使用IronPython编程

如果使用C#开发就麻烦多了,您得先用Visual Studio创建一个项目,然后向其中添加一个类,在类中写一个方法完成求和的功能,再编写调用这一方法的代码,编译、排错,最后才能得到所需的结果……

很明显,对于那些短小的工作任务而言,动态编程语言所具备的这种REPL开发模式具有很大的吸引力。

2)扩展方便。用户可以随时对代码进行调整,需要什么功能直接往动态对象上“加”就是了,不要时又可以移除它们。而且这种修改可以马上生效,并不需要像C#那样必须先修改类型的定义和声明,编译之后新方法才可用。

换句话说:使用动态语言编程,不需要“重量级”的OOAD,整个开发过程迭代迅速而从不拖泥带水

3)动态编程语言的类型解析是在运行时完成的,可以省去许多不必要的类型转换代码,因此,与静态编程语相比,动态编程语言写的代码往往更紧凑,量更少。

动态编程语言主要的弱点有两个:

1)代码中的许多错误要等到运行时才能发现,而且需要特定的运行环境支持,对其进行测试不太方便,也不支持许多用于提升代码质量的各种软件工程工具,因此不太适合于开发规模较大的、包容复杂处理逻辑的应用系统。

2)与静态编程语言相比,动态编程语言编写的程序性能较低。不过随着计算机软硬件技术的不断进步,比如多核CPU的广泛应用,动态编程语言引擎和运行环境不断地优化,动态编程语言编写的程序性能在不断地提升,在特定的应用场景下,甚至可以逼近静态语言编写的程序。

2 拥抱“动态编程”特性的C# 4

为了让C#Visual Basic.NET编程语言能具备动态编程语言的特性,.NET 4.0引入了一个“DLRDynamic Language Runtime:动态语言运行时)”( 3)。

3 DLR:动态语言运行时

DLR运行于CLR之上,提供了一个动态语言的运行环境,从而允许PythonRuby等动态语言编写的程序在.NET平台上运行,同时,现有的.NET静态类型编程语言,比如C#Visual Basic,也可以利用DLR而拥有一些动态编程语言的特性。

1)使用C# 4编写动态的代码

C# 4新增了一个dynamic关键字,可以用它来编写“动态”的代码。

例如,以下代码创建了一个ExpandoObject对象(注意必须定义为dynamic):

dynamic dynamicObj = new ExpandoObject();

这一对象的奇特之处在于,我们可以随时给它增加新成员:

dynamicObj.Value = 100; //添加字段

dynamicObj.Increment = new Action(() => dynamicObj.Value++); //添加方法

这些动态添加的成员与普通的类成员用法一样:

for (int i = 0; i < 10; i++)

dynamicObj.Increment();//调用方法

Console.WriteLine(“dynamicObj.Value={0}”,dynamicObj.Value);//访问字段

ExpandoObject对象实现了IDictionary<string, object>接口,可看成是一个字典对象,所有动态添加的成员都是这个字典对象中的元素,这意味我们不仅可以添加新成员,还可以随时移除不再需要的成员:

//移除Increment方法

(dynamicObj as IDictionary<string, object>).Remove(“Increment”);

方法移除之后,再尝试访问此方法将引发RuntimeBinderException异常。

2)使用dynamic关键字简化与COM组件交互的代码

要在.NET这个“托管世界”里调用“非托管世界”中的COM组件,我们必须通过 “互操作程序集(Interop Assembly)”作为桥梁,“互操作程序集”定义了CLR类型与COM类型之间的对应关系。

只要给.NET项目添加对“互操作程序集”的引用,就可以在.NET应用程序中创建这一程序集所包容的各种类型的实例(即COM包装器对象),对这些对象的方法调用(或对其属性的存取)将会被转发给COM组件。

以调用Word为例,在C# 4.0之前您可能经常需要编写这样的代码:

Object wordapp = new Word.Application();   //创建Word对象

Object fileName = “MyDoc.docx” ;//指定Word文档

Object argu = System.Reflection.Missing.Value;

Word.Document doc = wordapp.Documents.Open(ref fileName, ref argu,

ref argu, ref argu, ref argu, ref argu, ref argu, ref argu,

ref argu, ref argu, ref argu, ref argu, ref argu, ref argu,

ref argu, ref argu);

上述对Open()方法的调用语句只能用“恐怖”一词来形容,其原因是Word组件中的Open()方法定义了太多的参数。

C#4使用dynamic关键字,配合从Visual Basic中学来的“命名参数与可选参数”这两个新语法特性,可以写出更简洁的代码:

dynamic wordapp = new Word.Application();

dynamic doc = wordapp.Documents.Open(FileName: “MyDoc.docx”);

上述代码中省去了用不着的参数,并且可以去掉参数前的ref关键字。

当上述代码运行时,DLR会使用反射技术将dynamic表达式“绑定(bind)”到COM互操作程序集中所包容的Word.Application代理对象。

3C# 4动态编程技术内幕

C#4中所定义的dynamic变量可以引用以下类型的对象:

l 传统的“静态”的CLR对象。

l COM包装器对象。前面已经介绍了这方面的内容。

l 实现了IDynamicMetaObjectProvider接口的“动态对象”,ExpandoObject就是这种类型对象的实例。

l 基于DLR实现的动态语言(比如IronRubyIronPython)所创建的对象。

C#程序员角度来看,所有这四种对象都是一样的,都可用一个dynamic变量引用之,而DLR在程序运行时动态地将方法调用和字段存取请求“绑定”到真正的对象上。

dynamic的功能是由DLR所支撑的,是C#编译器与DLR分工合作的成果。

请看以下示例代码:

dynamic d = 100;

d++;

C#编译器在处理上述代码时,它并不去检查变量d是否可以支持自增操作,而是为其创建了一个CallSite<T>对象(<>p__Site1):

private static class <Main>o__SiteContainer0 {

public static CallSite<Func<CallSite, object, object>> <>p__Site1;

}

中文MSDNCallSite<T>译为“动态(调用)站点”,它是DLR中的核心组件之一。

动态站点对象通过CallSite<T>.Create()方法创建, C#编译器会为其指定一个派生自CallSiteBinder的对象(称为“动态站点绑定对象”)作为其参数。

动态站点绑定对象是与具体语言相关的,比如IronPythonC#都有各自的动态站点绑定对象。

动态站点绑定对象的主要工作是将代码中的动态表达式(本例中为d++)转换为一棵“抽象语法树(ASTAbstract Syntax Tree)”,这棵语法树被称为“DLR Tree”,是在.NET 3.5所引入的LINQ表达式树的基础上扩充而来的,因此,有时又称其为“表达式树(Expression Tree)”

DLR在内部调用此表达式树的Compile()方法生成IL指令,得到一个可以被CLR所执行的委托(在本例中其类型就是Func<CallSite, object, object>)。

动态调用站点对象(本例中为<>p__Site1)有一个Target属性,它负责引用这一生成好的委托。

委托生成之后,动态表达式的执行就体现为委托的执行,其实参由C#编译器直接“写死”在IL代码中。

简化的代码示意如下(通过Reflector得到,为便于阅读,修改了变量名):

object d = 100;

object CS$0$0000 = d;

if (<>p__Site1 == null)

<>p__Site1 = CallSite<Func<CallSite, object, object>>.Create(……);

d = <>p__Site1.Target(<>p__Site1, CS$0$0000);

上述类型推断、方法绑定及IL代码生成的工作都是在程序运行时完成的。

4)动态代码很慢吗?

动态编程语言易学易用,代码紧凑,开发灵活,但性能则一直是它的“软肋”。为了提升性能,DLR设计了一个三级缓存策略。

动态站点绑定对象会为动态调用表达式转换而成的语法树加上相应的测试条件(称为“test”),构成一个“规则(Rule)”,这个规则可以用于判断某个语法树是否可用于特定的动态调用表达式。

举个例子,请看以下这个动态表达式:

d1 + d2

如果在程序运行时d1d2都是int类型的整数,则DLR生成的规则为:

if( d1 is int && d2 is int) //测试条件

return (int)d1+(int)d2; //语法树

DLR通过检查规则中的“测试条件”,就可以知道某个动态表达式是否可以使用此规则所包容的语法树。

“规则”是DLR缓存的主要对象。

前面介绍过的动态站点对象Target属性所引用的委托是第一级缓存,它实现的处理逻辑是这样的:

//当前处理规则,属于第1级缓存

if( d1 is int && d2 is int) //测试条件

return (int)d1+(int)d2; //满足测试条件,直接返回一个表达式树

//未命中,则在第2级、第3级缓存中查找,如果找到了,用找到的结果更新第1级缓存

return site.Update(site,d1,d2);

如果3级缓存中都没有命中的规则,则此动态站点所关联的调用站点绑定对象会尝试创建一个新的规则。如果创建新规则失败,则由当前编程语言(比如C#)所提供的默认调用站点绑定对象决定如何处理,通常的作法是抛出一个异常。

当前版本的DLR2级缓存了10条规则,第3级则缓存了100条规则。

由于DLR自身设计了一个“规则”缓存系统,又充分利用了CLR所提供的JIT缓存(因为所有动态调用代码最终都会转换为CLR可以执行的IL指令,而CLR可以缓存这些代码),使得动态代码仅仅在第一次执行时性能较差,后续的连续调用其性能可以逼近静态代码。

3 C# 4与动态语言的集成

由于几乎所有的编程语言都可以使用抽象语法树来表达,因此,在理论上DLR支持无限多种编程语言间的互操作,在当前版本中,可以实现C#/Visual BasicIronPythonIronRuby的互操作,相信很快会出现其他动态编程语言的DLR实现。

一个有趣的地方是当前基于DLR实现的动态编程语言都以“Iron”开头,比如IronRubyIronPythonIronPython的设计者、DLR架构设计Jim Hugunin曾经在微软PDC 2008大会上解释说主要是为了避免起一个“Python.NET”或“Python for .NET”之类“微软味十足”的名字,才有了“IronPython”。他强调:“Iron”系列动态语言将严格遵循动态语言自身的标准和规范,尊重这些动态语言已有的历史和积累,不会引入一些仅限于.NET平台的新语言特性,并且这些语言的.NET实现保持开源。与此同时,Jim Hugunin指出 Iron”系列语言能很好地与.NET现有类库、编程语言和工具集成,并且能“嵌入”到.NET宿主程序中。

1)动态对象通讯协议

由于各种动态编程语言之间的特性相差极大,实现各语言间的互操作是个难题。为此DLR采取了一个聪明的策略,它不去尝试设计一个“通用的类型系统”(CLR就是这么干的),而是设计了一个“通用的对象通讯协议”,规定所有需要互操作的动态对象必须实现IDynamicMetaObjectProvider接口,此接口定义了一个GetMetaObject()方法,接收一个语法树对象作为参数,向外界返回一个“动态元数据(DynamicMetaObject)”对象:

DynamicMetaObject GetMetaObject(Expression parameter);

DynamicMetaObject对象向外界提供了两个重要属性:Restrictions引用一组测试条件,Expression属性则引用一个语法树。这两个属性组合起来就是可供动态站点对象缓存的“规则(Rule)”。

DLR中的“动态站点绑定对象(CallSiteBinder)”获取了DynamicMetaObject对象之后,它调用此对象所提供的各个方法创建“规则”,让“动态站点对象(CallSite<T>)”的Target属性引用它,完成动态绑定的工作。

2)动态语言集成环境

为了方便地实现静态编程语言与各种动态编程语言间的相互集成,DLR提供了一整套称为“通用寄宿(Common Hosting)”的组件,其中包容ScriptRuntimeScriptScope等类型。

下面我们以IronPython为例,介绍如何在C# 4开发的程序中集成动态编程语言代码。

首先需要创建一个ScriptRuntime对象,它是一个最顶层的对象,用于在一个.NET应用程序域中“嵌入”一个特定动态语言的运行环境:

ScriptRuntime pythonRuntime = Python.CreateRuntime();

接着需要创建一个ScriptEngine对象,它是动态语言代码的执行引擎:

ScriptEngine engine = pythonRuntime.GetEngine(“py”);

ScriptScope对象类似于C#中的命名空间,其中可以通过定义一些变量向动态代码传入数据,比如下述代码将一个C# 创建的ExpandoObject对象传给Python代码:

ScriptScope scope = pythonRuntime.CreateScope();

//C#创建动态对象

dynamic expando = new ExpandoObject();

expando.Name = “JinXuLiang”; //动态添加一个字段

//IronPython接收C#创建的Expando对象

scope.SetVariable(“ExpandoObject”, expando);

string pythonCode = “print ExpandoObject.Name”;

//IronPython引擎执行Python语句

engine.CreateScriptSourceFromString(pythonCode).Execute(scope);

上述示例代码是直接执行Python代码。在实际开发中,更常见的是直接执行Python文件中的代码,假设有一个Calculator.py文件,其中定义了一个Add函数:

def Add(a,b):

return a+b

则以下C#代码可以直接执行之:

ScriptRuntime pythonRuntime = Python.CreateRuntime();

dynamic pythonFile = pythonRuntime.UseFile(“Calculator.py”);

Console.WriteLine(pythonFile.Add(100, 200));

上述示例说明在DLR的支持之下,可以让静态编程语言使用动态语言所开发的库,反过来,基于DLR实现的动态编程语言也能使用为静态语言所设计的库,比如标准的.NET基类库。

这意味着两点:

1)我们现在可以将“静态”和“动态”编程语言组合起来,开发出一些具有高度交互性的应用程序,使用静态编程语言搭建系统框架,使用动态编程语言实现交互性,这是一个很值得注意的应用领域。

2)将来会出现一些“静态”“动态”编程语言同时适用的库,向实现“无所不在的复用”目标又前进了一步。

Visual Studio 2010为新的.NET编程语言F#提供了专门的项目模板,但没有为IronPythonIronRuby之类动态语言的开发提供支持,相信随着动态语言在.NET平台之上的应用日趋广泛,后继版本的Visual Studio会直接支持动态语言的开发。

C# 1.0~4.0所走过的路,可以很清晰地看到它的发展轨迹,得到这样的一个结论:

未来的编程语言应该是多范式的,具有高度的可组合性,在一个项目或产品中组合多个编程语言、使用多种编程范式会变得越来越普遍。

我们可以推断C#的后继版本将会在此条道路上越走越远……


[1] http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html

[2] IronPython是动态语言Python基于.NET的一个实现