[IIS]用ISAPI_Rewrite实现反向代理(ReverseProxy)

mikel阅读(651)

ISAPI_Rewrite是一个强大的基于正则表达式的URL处理引擎。它非常类似于Apache's mod_Rewrite,但它是专为IIS设计的。
ISAPI_Rewrite有两个版本:ISAPI_Rewrite Full与ISAPI_Rewrite Lite。
ISAPI_Rewrite Lite是免费版本,但不支持反向代理功能。
ISAPI_Rewrite Full只能下载到30天的试用版本。
这篇文章介绍的是基于ISAPI_Rewrite Full实现反向代理。配置实际上很简单。写出来的目的主要是希望给初次使用ISAPI_Rewrite的朋友提供参考。
下面就是配置步骤:
1、下载ISAPI_Rewrite Full: http://www.helicontech.com/download/#isapi_rewrite
2、安装ISAPI_Rewrite Full
3、修改配置文件httpd.ini,默认位置在C:\Program Files\Helicon\ISAPI_Rewrite。假如我们现在有两台Web服务器,一台是www.cnblogs.com, 另一台是 www2.cnblogs.com,  www2作为一台反向代理服务器,客户端浏览器访问www2服务器,www2服务器向www服务器请求内容并返回给客户端。具体在httpd.ini的配置如下:
在httpd.ini中增加下面的内容:
RewriteCond Host: www2\.cnblogs\.com
RewriteProxy (.*) http\://www.cnblogs.com$1 [I,F,U]
参考配置文档:http://www.isapirewrite.com/docs/

[IIS]配置 IIS Rewrite 模块 并修改 httpd.ini文件

mikel阅读(689)

配置 IIS Rewrite 模块 并修改 httpd.ini文件

下载IIS Rewrite模块:http://download.discuz.net/4.1.0/discuz_iis_rewrite.zip
2、 将压缩包解压到任意目录,(如:C:\Rewrite)。然后打开“控制面板”-“管理工具”-“IIS信息服务管理器”-“网站”-“您的站点”-“属 性”。在“ISAPI筛选器”项点击 “添加”,筛选器名称填入Rewrite,可执行文件为C:\Rewrite\Rewrite.dll ;
3、然后修改httpd.ini 文件,添加相应的过滤规则,例如:
RewriteRule ^(.*)/([a-z0-9\-\_]+)$ $1/Default\.aspx\?id=$2 [I,O]
http://localhost/Text 就会等同于 http://localhost/Default.aspx?id=Text
RewriteRule ^(.*)/id_([a-z0-9\-]+)\.html$ $1/Default\.aspx\?id=$2 [I,O]
http://localhost/id_Text.html 就会等同于 http://localhost/Default.aspx?id=Text
^(.*)/html/([0-9]+)/([0-9]+)\.html$ $1/Default\.aspx\?id=$2&name=$3
http://localhost/html/001/007.html 就等同于 http://localhost/Default.aspx?id=001&name=007
RewriteRule 语句我的理解:http://hi.baidu.com/liyuehui1987/blog/item/612fb044d1dbad83b3b7dc80.html
4、重新启动IIS就可以生效了(这一步很重要,每次修改httpd.ini后都要重启 IIS 以加载配置。)。
实际作中发现应该为:
RewriteRule ^(.*)-htm-(.*)$ $1.php\?$2

^(.*)/([a-z0-9\-\_]+)$ $1/Default\.aspx\?id=$2 [I,O]

^(.*) 这个是必须的

/ 这个应该是目录的开始

([a-z0-9\-\_]+) 这个是正则表达式,是用来匹配参数的

$ 这个是必须的就像最前面的 ^(.*)

$1 好像也是必须的

/ 也是一个目录的开始

Default 文件的真实名称(不包括扩展名)

\. 这是个转意字符 转意后面的点

Aspx 文件的扩展名

\? 又是一个转意字符 转意后面的问号

id= 参数的名称

$2 好像代表了参数的值(第一个参数的值)

[I,O] 好像是必须的

http://localhost/Text 就会等同于 http://localhost/Default.aspx?id=Text 这就成了目录式了

^(.*)/id_([a-z0-9\-]+)\.html$ $1/Default\.aspx\?id=$2 [I,O]

^(.*)这个是必须的

/ 这个应该是目录的开始

id_ 这是最终生成的文件名的一部分(最终文件名:id_ 参数值)

([a-z0-9\-\_]+) 这个是正则表达式,是用来匹配参数的

\. 这个是最终文件的名称和后缀名之间的点,\ 为转意字符

Html 为最终文件名的后缀名

$ 这个是必须的就像最前面的 ^(.*)  

$1好像也是必须的

/ 也是一个目录的开始

Default文件的真实名称(不包括扩展名)

\. 这是个转意字符 转意后面的点

Aspx 文件扩展名

\? 又是一个转意字符 转意后面的问号

id= 参数的名称

$2好像代表了参数的值(第一个参数的值)

[I,O] 也是必须的

localhost/id_Text.html 就会等同于 localhost/Default.aspx?id=Text 成静态得了

[MVC]ASP.NET MVC V2预览版发布

mikel阅读(807)

ASP.NET开发团队刚发布了ASP.NET MVC第二版的第一个公开的预览版。你可以 在这里下载。

今 天的预览版是在.NET 3.5 SP1和VS 2008下工作的,可与ASP.NET MVC 1.0并行安装在同一个机器上(即,它们不相冲突,如果你安装2.0的话,你现有的ASP.NET MVC 1.0项目不会受影响)。如果你同时安装ASP.NET MVC 1.0 和 ASP.NET MVC 2.0的话,你会在Visual Studio 2008的 “新项目” 对话框中看到2个ASP.NET MVC项目模板:

ASP.NET MVC 2预览版的发布说明详述了如何将现有的ASP.NET MVC 1.0项目升级到使用V2,如果你想将它们移植来利用V2的新特性的话。

新特性

ASP.NET MVC V2将包括一堆新功能和特性(其中一些已经在 ASP.NET MVC产品路线图网页上列出了)。今天的“第一个预览版”包括的一些新特性还是第一次露面,更多的特性将在将来的预览版中出现。第一个预览版的代码还属于早期版本,开发团队今天发布这个版本,意在开始征询大家的反馈,并将它们融入将来的版本。

下面简短地说明一下其中一些新功能:

区域支持

ASP.NET MVC 2将支持一个叫“区域(areas)”的新特性,允许你轻松地将MVC应用的功能进行分割和组合。

“区域”提供了一个将控制器和视图进行分组的方式,允许你把一个大应用的各个子部分相对独立地进行构建。每个区域可以一个单独的ASP.NET MVC项目的形式来实现,并且这些项目可进而为主应用所引用。这在建造大应用时有助于管理其复杂性,并方便多个团队合作开发应用。

下面这个屏幕截图展示一个单一解决方案中有三个项目。其中一个项目叫“CompanySite”(公司网站),包括了核心的网站内容,布局,相关的控制器 和视图。还有2个单独的“区域”项目: “Blogs”(博客) 和 “Forums” (论坛)。这2个项目实现了网站的/Blogs 和 /Forums URL下的功能,封装了所有的路径规则,以及实现这2个部分的控制器和视图:

第一个预览版只包括了区域特性实现的第一个部分,还不包括任何工具支持(目前,你只能手工添加编译任务来创建并配置区域项目)。将来的预览版本将包括工具支持,同时进一步将功能集扩大和完善。

DataAnnotation(数据标记)验证支持

ASP.NET MVC 2现在包含了对最先在.NET 3.5 SP1中发布的DataAnnotation验证的内置支持,这个东西是在ASP.NET动态数据和.NET RIA服务中使用的。DataAnnotation提供了一个简易的方式,可以声明的方式在应用中的Model(模型)和ViewModel(视图模型) 类中添加验证规则,在ASP.NET MVC中有自动的绑定和UI辅助方法验证支持。

要看该特性如何在实际中使用,我们可以象下面这样创建一个新的“Customer” 视图模型类,内含5个属性(是用了 C#特性之一 — 自动属性来实现的)。

然后,我们可以使用System.ComponentModel.DataAnnotations命 名空间下的DataAnnotation特性,在这些属性上饰于适当的验证规则。下面的代码使用了4种不同的内置验证规则: Required], [StringLength], [Range], 和 [RegularExpression]。该命名空间下还包括一个基类,ValidationAttribute, 你可以继承来创建你自己的定制验证特性。

然后,我们可以创建一个CustomersController类,其中有2个 Create action方法。第一个 Create action方法处理对应于“/Customers/Create” URL的HTTP GET请求,基于一个空白的Customer对象显示一个视图模板。第二个 Create action方法则处理同个URL的HTTP POST请求,并接受一个Customer对象作为方法参数。它会检查提交的输入数据是否造成了任何模型绑定错误,如果造成了错误的话,它会使用已经输入 的数据重新显示视图模板,如果没错误的话,它会给用户显示一个提交成功了的视图:

最 后,我们可以在上面任意一个Create action方法中右击,选择 “添加视图”上下文菜单命令,自动地生成(scaffold)一个基于Customer对象的“Create”视图模板的框架。这么做的话,生成的框架视 图模板会包含下面这样一个HTML <form>:

这样,当我们在浏览器中请求“/Customers/Create” URL时,我们会得到象下面这样起始的空白表单:

如果我们输入不合法的数据,提交到服务器时,ASP.NET MVC 2模型绑定器会检测到我们的Customer类上有DataAnnotations特性,会自动地使用它们对提交的表单输入数据进行验证。如果有错误的 话,我们的控制器action方法会重新显示表单,并且给用户显示适当的验证错误消息,象下面这样。注意我们使用DataAnnotation特性指定的 验证属性错误消息字符串是如何通过Html.Validation辅助方法显示给用户的,不用编写额外的代码就可以实现。

The above form will redisplay with error messages each time the user enters invalid input and attempts to perform a form post.

在将来的ASP.NET MVC 2预览版中,我们计划将 jQuery验证插件作 为默认项目模板的一部分来发布,同时添加DataAnnotation验证规则在客户端JavaScript中自动强制执行的支持。这将允许开发人员轻松 地在一个地方,Model或ViewModel对象上,添加验证规则,然后无论对象用在应用中的什么地方,这些规则都会同时在客户端和服务器端强制执行。

如果你不希望直接标记你的模型或视图模型类,你还可以创建一个伴随你的模型类的“伙伴类(buddy class)”,另外封装DataAnnotaton规则。这个功能还在这样一些场景下有用: VS直接生成/更新类的属性代码,你无法简易地在生成的代码上饰以特性(例如,由LINQ to SQL 或 LINQ to Entities设计器生成的类)。

除了提供对DataAnnotations的内置支持外,ASP.NET MVC V2中的DefaultModelBinder类现在还有新的虚拟方法,可以在子类中覆盖,来轻松地集成其他的验证框架( 例如Castle Validator, EntLib Validation等等)。ASP.NET MVC中的验证UI辅助方法是设计来支持任何类型的验证框架的(它们不用知道DataAnnotations)。

强类型UI辅助方法

ASP.NET MVC V2包含了新的HTML UI辅助方法,这些辅助方法允许你在引用视图模板的模型对象时使用强类型的lambda表达式。这可以促成更好的 视图编译时检查(这样缺陷是在编译时发现而不是在运行时发现),还能促成视图模板中更好的代码intellisense支持。

你可以在下面看到一个示范更好intellisense的例子,注意我在使用新的Html.EditorFor()辅助方法时是如何得到Customer模型对象属性的完整列表的:

第 一个预览版提供对新的Html.EditorFor(), Html.LabelFor(), 和 Html.DisplayFor() 辅助方法的内置支持。将在这个星期发布的更新过的MVC Futures程序集还将包含对另外的Html.TextBoxFor(), Html.TextAreaFor(), Html.DropDownListFor(), Html.HiddenFor(), 和 Html.ValidationMessageFor()辅助方法的支持(随着时间的推移,这些方法也会移到核心ASP.NET MVC 2程序集中去)。

在下面,你可以看到Customer创建场景下“Create”视图模板的一个更新了的版本。注意,在UI辅助方法中,我们不是使用字符串表达式来引用 Customer对象,而是使用强类型的lambda表达式。我们在所有这些方法中都可以得到完整的 intellisense 和 编译时检查:

上面的Html.LabelFor()辅助方法生成<label for="Name">Name:</label> HTML 标识。

Html.EditorFor()辅助方法可以用于任何数据类型值。在默认情形下,它很聪明,会根据要编辑的类型输出合适的HTML <input/>元素。譬如,它会为上面的前4个属性(是字符串和整数类型)生成<input type=”text”/>元素,会为最后的那个“IsActive” 属性生成<input type=”checkbox”/>元素,因为这个属性是布尔值类型。

除了支持简单的数据类型外,Html.EditorFor()辅助方法还允许你传递给它拥有多个属性的比较复杂的对象。在默认情形下,它会对对象的所有公 开属性进行循环,输出<label>, <input/> 元素,以及它能找到的每个属性的任何合适的验证消息。例如,我们可以重写上面的视图,对Customer对象只做单个Html.EditorFor()调 用,从概念上来说输出跟上面一样的标识:

强类型的辅助方法允许你在Customer视图类的属性上饰以[DisplayName]特性,来控制为每个属性输出的标签字符串(例如:不是用 “IsActive”作为标签文字,我们可以加一个[DisplayName(“Is Active Customer:”)]特性)。

你也可以加一个[ScaffoldColumn(false)]特性,来表示,在象上面把一个复杂的对象传给Html.EditorFor()那样的场景下,某个特定的属性完全不应该显示出来。

UI 辅助方法模板化支持

Html.EditorFor() 和 Html.DisplayFor() 辅助方法对显示标准的数据类型以及含有多个属性的复杂对象有内置的支持。就象上面说的,它们还支持通过在视图模型上施加象[DisplayName]和 [ScaffoldColumn]特性这样的基本的定制机制。

但经常地,开发人员想要能够进一步定制UI辅助方法的输出,对生成的东西要有完全的控制。Html.EditorFor() 和 Html.DisplayFor()辅助方法通过一个模板化机制支持这个要求,这个机制允许你定义外部的模板,替换原先的,完全控制显示的输出。更棒的 是,你还可以在每个数据类型/类的基础上定制要显示的内容。

在第一个预览版中,你可以在"Views"[控制器名称]目录下(如果你想要定制某个特定的控制器所用视图的显示的话)或在 "Views"Shared目录下(如果你想要定制一个应用中所有视图和控制器的显示的话)加一个“EditorTemplates” 或者 “DisplayTemplates” 子目录。

然后你可以往这些目录中加分模板(partial template)文件,针对个别数据类型或者类来定制显示输出。例如,在下面,我在 "Views"Shared目录下加了一个EditorTemplates子目录,在其中加了三个定制的模板文件:

上面的“Customer.ascx”模板表示我想要定制在调用Html.EditorFor()时其参数为Customer对象时的输出(例如,我可以 定制Customer属性的精确顺序和布局)。上面的“DateTime.ascx” 模板表示我想要定制在调用Html.EditorFor()时其参数为DateTime属性时的输出(例如,我也许想要使用JavaScript的日历控 件,而不是普通的文本框)。我也可以在目录中加一个“Object.ascx” 模板,如果我想要替代所有对象的默认显示的。

除了在每个类的基础上定制输出外,你还可以在目录中加“具名模板(named templates)”。一个常见的场景也许是 “CountryDropDown”模板,它处理字符串数据类型,但不是提供标准的文本框,而是显示一个用户可以选择的列出了国家名称值 的<select>下拉框。下面是这个编辑器模板的一个例子:

然后,我们可以在调用Html.EditorFor()辅助方法时,把上面这个模板的名称作为参数传给它,明确地表示我们想要使用这个模板。例如,在下面,除了指定Country属性的lambda表达式外,我们还指定了在显示时要使用的编辑器模板的名称:

或者,你也可以在你的ViewModel属性和类型上指定“UIHint”特性。这允许你在单一一个地方指定要使用的默认编辑器或者显示器模板,然后在整个应用的所有视图中使用指定的模板(而不必显式地将这个名称作为参数传给Html.EditorFor)。

下面是一个如何使用UIHint特性来表示Customer.Country属性(字符串类型)应该在默认情形下显示时使用CountryDropDown模板的例子:

一旦在我们的视图模型上设置上述特性后,在使用Html.EditorFor()显示那个属性时,我们就不再需要显式指定模板名称了。现在,在/Customers/Create URL上点击刷新时,我们的Country属性就会显示为一个下拉框,而不是一个标准的文本框:

其他酷特性

ASP.NET MVC 2第一个预览版还包含了若干个虽小但是很妙的特性。我最喜爱的几个包括:

新的[HttpPost]特性

在 ASP.NET MVC中,把一个URL的处理分成2个action方法的做法是非常常见的,其中一个处理GET请求,另一个处理POST请求。

在ASP.NET MVC 1中,你使用[AcceptVerbs(HttpVerbs.Post)]特性来表示action方法的“Post”版本:

在ASP.NET MVC 2中这依然工作,但你现在也可以利用更简洁的[HttpPost]特性来做同样的事情:

默认参数值

处理可省参数在web场景中是司空见惯的事。在ASP.NET MVC 1中,处理可省参数一般有2个做法,通过注册定制的路径规则,在其中指定默认的值,或者将某个action方法的参数标记为nullable,然后在 action方法中添加代码处理该参数是否是null(如果是null就提供默认值)。

ASP.NET MVC 2第一个预览版现在支持在action方法的参数上饰以 System.ComponentModel命名空间下的DefaultValueAttribute。 这允许你在某个参数不在请求值中时指定ASP.NET MVC应该传给action方法的参数值。例如,下面是一个我们可以如何处理 /Products/Browse/Beverages 和 /Products/Browse/Beverages?page=2 URLs的例子,如果“page”参数不是查询字符串的一部分时,其值为“1”:

VB今天就允许你直接在语言中指定默认的参数值(而不必象上面那样显式指定DefaultValue特性),VS2010中的C#语言也将支持可省参数的默认值,将允许我们把上面的代码简化成:

这应该会使处理默认/可省场景变得非常地干净利落。

绑定二进制数据

ASP.NET MVC 2的第一个预览版还加了支持绑定base64编码的字符串值到类型为byte[]和System.Data.Linq.Binary的属性。现在还有2个 可以接受这些数据类型的重载的Html.Hidden()版本。在你想要在应用中启用并发性控制,在表单中来回传送数据库行记录的时间戳 (timestamp)值的场景下,这会非常有用。

结语

点击这里下载包含一个ASP.NET MVC 2项目的.zip文件,该项目实现了我在上面示范的样例。

今天的ASP.NET MVC 2版本还只是第一个预览版,将来的预览版中将包括更多的特性,开发团队期待在如何改进和增强功能方面得到许许多多的反馈。

有规律地发布这些预览版的目的是想帮助确保这个反馈过程是开放的,任何想参与的人都可以轻易地参与。请在 www.asp.net ASP.NET MVC论坛上发贴提反馈,建议或者贴出你遇到的问题等。 你也可以从 Phil Haack的MVC2贴子 Phil和 Scott Hanselman在Channel9录制的关于第一个预览版的录像中了解这个预览版的详情。

希望本文对你有所帮助,

Scott

[SCSF]Smart Client Software Factory 简介、安装

mikel阅读(806)

SCSF 系列:Smart Client Software Factory 简介、安装及通过模板新建项目

一、Smart Client Software Factory 简介

Smart Client Software Factory 是微软 patterns & practice 项目组的 Software Factory 系列软件框架,用于帮助开发者建立基于 MVP 模式的桌面程序(Winform),当然这种桌面程序是微软一直大力推广的所谓智能客户端程序(Smart Client)。

智能客户端(Smart Client)可以最大可能的整合瘦客户端(例如:基于 Browser 的 B\S Web 应用)和胖客户端(例如:传统的C\S结构的客户端应用)的优势,避免 B\S 用户体验差和 C\S 部署维护成本大的问题。

智能客户端与传统胖客户端的最大不同是 Smart Client 允许离线运行,一般通过 Web Service (或者 WCF) 与服务器交互。既可以充分利用客户端本地计算机的计算能力又可降低对服务器的强烈依赖。

Smart Client Software Factory 首先是为 Smart Client 应用设计的编程框架(就像 Java 中的 Struts,WebWork,是一个程序的半成品),通过合理的使用架构模式、设计模式和最佳实践为我们提供了建立 Smart Client 的指导原则和框架基础。同时 Smart Client Software Factory 也是一个软件工厂,它通过 Visual Studio 的 GuidanceAutomation 扩展了 VS 的功能,利用 RecipesTemplates (guidance package )帮助我们生成基础框架和代码,后面我们会看到。

二、Smart Client Software Factory 安装

这里介绍微软  2008-04-25 最新发布的 SCSF (April 2008 Release)。
安装必须环境:

安装可选组件: 

三、利用 Visual Studio 的模板新建项目

新建基于 Guidance Packages 的 Smart Client Development 项目

新建基于模板的项目

 

弹出创建向导:

Step1

 

点完成后,自动生成的解决方案文件夹

自动生成的基础解决方案

 

在自动生成的项目框架基础上创建解决方案文件夹,自定义项目,也可以通过在解决方案文件夹上点右键,选择 Smart Client Factory ,创建 Module 。

addModule

 

在 Smart Client 项目或者项目中的文件夹上点右键,Smart Client Factory 可以添加带有 Presenter 的 View (MVP)。

addView

 

如何具体使用,以后介绍。

[OSGi]什么是OSGi

mikel阅读(791)

OSGi(Open Service Gateway Initiative)有双重含义。一方面它指OSGi Alliance组织;另一方面指该组织制定的一个基于Java语言的服务(业务)规范——OSGi服务平台(Service Platform)。

OSGi Alliance是一个由Sun MicrosystemsIBM爱立信等于1999年3月成立的开放的标准化组织, 最初名为Connected Alliance。该组织及其标准原本主要目的在于使服务提供商通过住宅网关,为各种家庭智能设备提供各种服务。目前该平台逐渐成为一个为室内、交通工 具、移动电话和其他环境下的所有类型的网络设备的应用程序和服务进行传递和远程管理的开放式服务平台。

该规范和核心部分是一个框架 ,其中定义了应用程序的生命周期模式和服务注册。基于这个框架定义了大量的OSGi服务: 日志配置管理、偏好,HTTP(运行servlet)、XML分析、设备访问、软件包管理、许可管理、星级、用户管理、IO连接、连线管理、JiniUPnP

这个框架实现了一个优雅、完整和动态的组件模型。应用程序(称为bundle)无需重新引导可以被远程安装、启动、升级和卸载(其中Java包/类的管理被详细定义)。API中还定义了运行远程下载管理政策的生命周期管理服务注册允许bundles去检测新服务和取消的服务,然后相应配合。

OSGi原先关注于服务网关,其实可用于多个方面。现在OSGi规范已经用于从移动电话到开源的Eclipse(其中包括了与IBM的OSGi框架SMF兼容的开源版本)。 OSGi服务平台的应用包括:服务网关汽车移动电话工业自动化建筑物自动化PDA 网格计算娱乐(如iPronto)、和 IDE

OSGi规范是由成员通过公开的程序开发,对公众免费而且没有许可证限制。但是OSGi Alliance的兼容性程序只对成员开放,目前有12个兼容的实现

2003年Eclipse选择OSGi作为其插件的底层运行时架构。Equinox project对该理念进行了实验,2004年6月在Eclipse3 R3中发布。ProSyst是面向OSGi开发者的Eclipse插件。

2003年10月, 诺基亚摩托罗拉ProSyst 和其他OSGi成员组建了Mobile Expert Group (MEG)为下一代智能手机规范业务平台,做为对 MIDPCDC的补充。

[SQL]SQL Server 2008 Sp1

mikel阅读(940)

SQL Server 2008 Sp1 可以下载,只需要下载更新包即可能跟新。

 地址

以下是 Service Pack 1 中的新增功能:

  • 集成是一种安装方法,它将程序的基础安装文件与其 Service Pack 集成到一起,这样,只执行一个步骤即可安装它们。您可以将 SQL Server 2008 更新与原始安装媒体集成在一起,从而可以同时安装原始媒体和更新。可从“下载中心”下载的更新安装文档中有关于集成过程的最新说明。MSDN 也提供了更新安装文档。
  • 可以使用控制面板中的“程序和功能”卸载 SQL Server 2008 累积更新或 Service Pack。有关详细信息,请参阅更新帮助文件中的“Overview of SQL Server 服务安装概述”。
  • SQL Server 2008 Service Pack 1 提供了报表生成器 2.0 的 ClickOnce 版本。报表生成器可用于以本机模式或 SharePoint 集成模式安装的 Reporting Services (SSRS)。如果 SSRS 是以 SharePoint 集成模式安装的,那么您需要安装适用于 SharePoint 的 Microsoft SQL Server 2008 Report Builder 2.0 ClickOnce 外接程序,才能在 SharePoint 网站上使用 Report Builder 2.0 ClickOnce。有关详细信息,请参阅适用于 SharePoint 的 Report Builder 2.0 ClickOnce 自述文件

[C#]打造自己的LINQ Provider(上):Expression Tree揭秘

mikel阅读(880)

版权声明:原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://terrylee.blog.51cto.com/342737/90559

概述

在.NET Framework 3.5中提供了LINQ 支持后,LINQ就以其强大而优雅的编程方式赢得了开发人员的喜爱,而各种LINQ Provider更是满天飞,如LINQ to NHibernate、LINQ to Google等,大有“一切皆LINQ”的趋势。LINQ本身也提供了很好的扩展性,使得我们可以轻松的编写属于自己的LINQ Provider。 本文为打造自己的LINQ Provider系列文章第一篇,主要介绍表达式目录树(Expression Tree)的相关知识。

认识表达式目录树

究竟什么是表达式目录树(Expression Tree),它是一种抽象语法树或者说它是一种数据结构,通过解析表达式目录树,可以实现我们一些特定的功能(后面会说到),我们首先来看看如何构造出一 个表达式目录树,最简单的方法莫过于使用Lambda表达式,看下面的代码:

Expression<Func<int, int, int>> expression = (a, b) => a * b + 2;

在我们将Lambda表达式指定给Expression<TDelegate>类型的变量(参数)时,编译器将会发出生成表达式目 录树的指令,如上面这段代码中的Lambda表达式(a, b) => a * b + 2将创建一个表达式目录树,它表示的是一种数据结构,即我们把一行代码用数据结构的形式表示了出来,具体来说最终构造出来的表达式目录树形状如下图所示: TerryLee_0160 这里每一个节点都表示一个表达式,可能是一个二元运算,也可能是一个常量或者参数等,如上图中的ParameterExpression就是一 个参数表达式,ConstantExpression是一个常量表达式,BinaryExpression是一个二元表达式。我们也可以在Visual Studio中使用Expression Tree Visualizer来查看该表达式目录树: TerryLee_0166 查看结果如下图所示: TerryLee_0162 这里说一句,Expression Tree Visualizer可以从MSDN Code Gallery上的LINQ Sample中得到。现在我们知道了表达式目录树的组成,来看看.NET Framework到底提供了哪些表达式?如下图所示: TerryLee_0161 它们都继承于抽象的基类Expression,而泛型的Expression<TDelegate>则继承于 LambdaExpression。在Expression类中提供了大量的工厂方法,这些方法负责创建以上各种表达式对象,如调用Add()方法将创建 一个表示不进行溢出检查的算术加法运算的BinaryExpression对象,调用Lambda方法将创建一个表示lambda 表达式的LambdaExpression对象,具体提供的方法大家可以查阅MSDN。上面构造表达式目录树时我们使用了Lambda表达式,现在我们看 一下如何通过这些表达式对象手工构造出一个表达式目录树,如下代码所示:

static void Main(string[] args)
{
ParameterExpression paraLeft = Expression.Parameter(typeof(int), "a");
ParameterExpression paraRight = Expression.Parameter(typeof(int), "b");
BinaryExpression binaryLeft = Expression.Multiply(paraLeft, paraRight);
ConstantExpression conRight = Expression.Constant(2, typeof(int));
BinaryExpression binaryBody = Expression.Add(binaryLeft, conRight);
LambdaExpression lambda =
Expression.Lambda<Func<int, int, int>>(binaryBody, paraLeft, paraRight);
Console.WriteLine(lambda.ToString());
Console.Read();
}

这里构造的表达式目录树,仍然如下图所示: TerryLee_0160 运行这段代码,看看输出了什么: TerryLee_0158   可以看到,通过手工构造的方式,我们确实构造出了同前面一样的Lambda表达式。对于一个表达式目录树来说,它有几个比较重要的属性: Body:指表达式的主体部分; Parameters:指表达式的参数; NodeType:指表达式的节点类型,如在上面的例子中,它的节点类型是Lambda; Type:指表达式的静态类型,在上面的例子中,Type为Fun<int,int,int>。 在Expression Tree Visualizer中,我们可以看到表达式目录树的相关属性,如下图所示: TerryLee_0163 

表达式目录树与委托

大家可能经常看到如下这样的语言,其中第一句是直接用Lambda表达式来初始化了Func委托,而第二句则使用Lambda表达式来构造了一个表达式目录树,它们之间的区别是什么呢?

static void Main(string[] args)
{
Func<int, int, int> lambda = (a, b) => a + b * 2;
Expression<Func<int, int, int>> expression = (a, b) => a + b * 2;
} 

其实看一下IL就很明显,其中第一句直接将Lambda表达式直接编译成了IL,如下代码所示:

.method private hidebysig static void  Main(string[] args) cil managed
{
.entrypoint
.maxstack  3
.locals init ([0] class [System.Core]System.Func`3<int32,int32,int32> lambda)
IL_0000:  nop
IL_0001:  ldsfld     class [System.Core]System.Func`3<int32,int32,int32>
TerryLee.LinqToLiveSearch.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
IL_0006:  brtrue.s   IL_001b
IL_0008:  ldnull
IL_0009:  ldftn      int32 TerryLee.LinqToLiveSearch.Program::'<Main>b__0'(int32,
int32)
IL_000f:  newobj     instance void class [System.Core]System.Func`3<int32,int32,int32>::.ctor(object,
native int)
IL_0014:  stsfld     class [System.Core]System.Func`3<int32,int32,int32>
TerryLee.LinqToLiveSearch.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
IL_0019:  br.s       IL_001b
IL_001b:  ldsfld     class [System.Core]System.Func`3<int32,int32,int32>
TerryLee.LinqToLiveSearch.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
IL_0020:  stloc.0
IL_0021:  ret
}

而第二句,由于告诉编译器是一个表达式目录树,所以编译器会分析该Lambda表达式,并生成表示该Lambda表达式的表达式目录树,即它与我们手工创建表达式目录树所生成的IL是一致的,如下代码所示,此处为了节省空间省略掉了部分代码:

.method private hidebysig static void  Main(string[] args) cil managed
{
.entrypoint
.maxstack  4
.locals init ([0] class [System.Core]System.Linq.Expressions.Expression`1<
class [System.Core]System.Func`3<int32,int32,int32>> expression,
[1] class [System.Core]System.Linq.Expressions.ParameterExpression CS$0$0000,
[2] class [System.Core]System.Linq.Expressions.ParameterExpression CS$0$0001,
[3] class [System.Core]System.Linq.Expressions.ParameterExpression[] CS$0$0002)
IL_0000:  nop
IL_0001:  ldtoken    [mscorlib]System.Int32
IL_0006:  call       class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(...)
IL_000b:  ldstr      "a"
IL_0010:  call       class [System.Core]System.Linq.Expressions.ParameterExpression
[System.Core]System.Linq.Expressions.Expression::Parameter(
class [mscorlib]System.Type,
IL_0038:  call    class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle()
IL_003d:  call    class [System.Core]System.Linq.Expressions.ConstantExpression
[System.Core]System.Linq.Expressions.Expression::Constant(object,
class [mscorlib]System.Type)
IL_0042:  call    class [System.Core]System.Linq.Expressions.BinaryExpression
[System.Core]System.Linq.Expressions.Expression::Multiply(class [System.Core]System.Linq.Expressions.Expression,
class [System.Core]System.Linq.Expressions.Expression)
IL_0047:  call    class [System.Core]System.Linq.Expressions.BinaryExpression
[System.Core]System.Linq.Expressions.Expression::Add(class [System.Core]System.Linq.Expressions.Expression,
class [System.Core]System.Linq.Expressions.Expression)
IL_004c:  ldc.i4.2
IL_004d:  newarr     [System.Core]System.Linq.Expressions.ParameterExpression
}

现在相信大家都看明白了,这里讲解它们的区别主要是为了加深大家对于表达式目录树的区别。

执行表达式目录树

前面已经可以构造出一个表达式目录树了,现在看看如何去执行表达式目录树。我们需要调用Compile方法来创建一个可执行委托,并且调用该委托,如下面的代码:

static void Main(string[] args)
{
ParameterExpression paraLeft = Expression.Parameter(typeof(int), "a");
ParameterExpression paraRight = Expression.Parameter(typeof(int), "b");
BinaryExpression binaryLeft = Expression.Multiply(paraLeft, paraRight);
ConstantExpression conRight = Expression.Constant(2, typeof(int));
BinaryExpression binaryBody = Expression.Add(binaryLeft, conRight);
Expression<Func<int, int, int>> lambda =
Expression.Lambda<Func<int, int, int>>(binaryBody, paraLeft, paraRight);
Func<int, int, int> myLambda = lambda.Compile();
int result = myLambda(2, 3);
Console.WriteLine("result:" + result.ToString());
Console.Read();
}
运行后输出的结果:

TerryLee_0159 这里我们只要简单的调用Compile方法就可以了,事实上在.NET Framework中是调用了一个名为ExpressionCompiler的内部类来做表达式目录树的执行(注意此处的Compiler不等同于编译器 的编译)。另外,只能执行表示Lambda表达式的表达式目录树,即LambdaExpression或者 Expression<TDelegate>类型。如果表达式目录树不是表示Lambda表达式,需要调用Lambda方法创建一个新的表达 式。如下面的代码:

static void Main(string[] args)
{
BinaryExpression body = Expression.Add(
Expression.Constant(2),
Expression.Constant(3));
Expression<Func<int>> expression =
Expression.Lambda<Func<int>>(body, null);
Func<int> lambda = expression.Compile();
Console.WriteLine(lambda());
}

访问与修改表达式目录树

在本文一开始我就说过, 通过解析表达式目录树,我们可以实现一些特定功能,既然要解析表达式目录树,对于表达式目录树的访问自然是不可避免的。在.NET Framework中,提供了一个抽象的表达式目录树访问类ExpressionVisitor,但它是一个internal的,我们不能直接访问。幸运 的是,在MSDN中微软给出了ExpressionVisitor类的实现,我们可以直接拿来使用。该类是一个抽象类,微软旨在让我们在集成ExpressionVisitor的基础上,实现自己的表达式目录树访问类。现在我们来看简单的表达式目录树:
static void Main(string[] args)
{
Expression<Func<int, int, int>> lambda = (a, b) => a + b * 2;
Console.WriteLine(lambda.ToString());
} 
输出后为:
TerryLee_0164
现在我们想要修改表达式目录树,让它表示的Lambda表达式为(a,b)=>(a – (b * 2)),这时就需要编写自己的表达式目录树访问器,如下代码所示:
public class OperationsVisitor : ExpressionVisitor
{
public Expression Modify(Expression expression)
{
return Visit(expression);
}
protected override Expression VisitBinary(BinaryExpression b)
{
if (b.NodeType == ExpressionType.Add)
{
Expression left = this.Visit(b.Left);
Expression right = this.Visit(b.Right);
return Expression.Subtract(left,right);
}
return base.VisitBinary(b);
}
}

使用表达式目录树访问器来修改表达式目录树,如下代码所示:

static void Main(string[] args)
{
Expression<Func<int, int, int>> lambda = (a, b) => a + b * 2;
var operationsVisitor = new OperationsVisitor();
Expression modifyExpression = operationsVisitor.Modify(lambda);
Console.WriteLine(modifyExpression.ToString());
}
运行后可以看到输出:
TerryLee_0165
似乎我们是修改表达式目录树,其实也不全对,我们只是修改表达式目录树的一个副本而已,因为表达式目录树是不可变的,我们不能直接修改表达式目录树,看看上面的OperationsVisitor类的实现大家就知道了,在修改过程中复制了表达式目录树的节点。

为什么需要表达式目录树

通过前面的介绍,相信大家对于表达式目录树已经有些了解了,还有一个很重要的问题,就是为什么需要表达式目录树?在本文开始时,就说过通过解析表达式目录树,可以实现我们一些特定的功能,就拿LINQ to SQL为例,看下面这幅图:
TerryLee_0167
当我们在C#语言中编写一个查询表达式时,它将返回一个IQueryable类型的值,在该类型中包含了两个很重要的属性Expression和Provider,如下面的代码:
TerryLee_0168
我们编写的查询表达式,将封装为一种抽象的数据结构,这个数据结构就是表达式目录树,当我们在使用上面返回的值时,编译器将会以该值所期望的方 式进行翻译,这种方式就是由Expression和Provider来决定。可以看到,这样将会非常的灵活且具有良好的可扩展性,有了表达式目录树,可以 自由的编写自己的Provider,去查询我们希望的数据源。经常说LINQ为访问各种不同的数据源提供了一种统一的编程方式,其奥秘就在这里。然而需要 注意的是LINQ to Objects并不需要任何特定的LINQ Provider,因为它并不翻译为表达式目录树,后面会说到这一点。

总结

本文详细介绍了表达式目录树的相关知识,为我们编写自己的LINQ Provider打下一个基础,希望对于大家有所帮助。查看目前网上的各种lINQ Provider,请访问万般皆LINQ

本文出自 “TerryLee技术专栏” 博客,请务必保留此出处http://terrylee.blog.51cto.com/342737/90559

[C#]Expression Tree上手指南 (一)

mikel阅读(1095)

大家可能都知道Expression Tree是.NET 3.5引入的新增功能。不少朋友们已经听说过这一特性,但还没来得及了解。看看博客园里的老赵等诸多牛人,将Expression Tree玩得眼花缭乱,是否常常觉得有点落伍了呢?其实Expression Tree是一个一点就透的特性,只要对其基本概念有了一定的了解,就可以自己发挥出无数的用法。特别是之前对Reflection,泛型等知识有过一些了 解的话,就会发现Expression Tree的加入绝对是你工作中的得力助手。如果你是Expression Tree的新手,那么从本文开始,你就可以领略这一工具,之后再看老赵的文章就从容不迫了~

从表达式说起

Expression Tree从名称上看就是“表达式树”的意思。许多人一看到它,就会想起Lambda表达式,委托,Linq等等一堆名词。但其实最基本的概念就是“表达式”。现在让我们把那些名词全都给忘了,来重新了解一下表达式。

表达式是当今编程语言中最重要的组成成分。简单的说,表达式就是变量、数值、运算符、函数组合起来,表示一定意义的式子。例如下面这些都是(C#)的表达式:

3 //常数表达式
a //变量或参数表达式
!a //一元逻辑非表达式
a + b //二元加法表达式
Math.Sin(a) //方法调用表达式
new StringBuilder() //new 表达式

此外还有取地址表达式,新建数组表达式,赋值表达式等许多种。如你所见,表达式常常能够表示一个值或对象,因此在C#这类强类型语言里,表达式常常 有一个相应的类型。例如“3”这个表达式就是int类型的。不过有时表达式也没有值,例如方法调用表达式,如果方法没有返回值的话这个表达式也就没有值。 这种情况我们也说表达式的类型是void。

表达式的一个重要的特点就是它可以无限地组合,只要符合正确的类型和语义。例如+可以用于各类数值类型的加法,那么加号的左右就可以是任何类型为相 应数值的表达式。可以是函数调用和常数:Math.Sin(a) + 3;也可以是同样的加法表达式a + 2 + 3。想必大家在实践中早就用上这个特性了。那么a + 2 + 3是如何计算出正确的值来的呢?应该首先计算(a + 2)的结果b,然后计算b + 3的值。如果我们用一个图来表示这个过程,它就像这样:

 image

同理,表达式Math.Sin(a) + 3也可以表示成这样:

 image

如你所见,所有表达式都可以表示成这种树一样的结构。每个节点和它所有的后裔都构成一个独立的表达式。如果我们将表达式表示成这种结构,就可以轻易地明白它的运算规则和步骤。因此我们可以用一种树状的数据结构来表示每一个表达式。这个数据结构就是表达式树。

表达式树

刚才提到了,表达式树就是一种表示表达式的数据结构。System.Linq.Expression命名空间下的Expression类和它的诸多 子类就是这一数据结构的实现。每个表达式都可以表示成Expression某个子类的实例。每个Expression子类都按照相应表达式的特点储存自己 的子节点。例如BinaryExpression就表示各种二元运算符的表达式。它的Left和Right属性就是参与二元运算的两个运算数。下面开始我 们将每种表达式的内部特定结构称作表达式的“成分”。比如二元运算表达式的成分就是左运算数表达式、右运算符表达式和一个运算符。

Expression各个子类的构造函数都是不公开的,要创建表达式树只能使用Expression类提供的静态方法。(这同时也说明表达式树体系是不能自己扩展的)如果我们要创建1 + 2 + 3这个表达式的表达式树,可以这样写:

ConstantExpression exp1 = Expression.Constant(1);
ConstantExpression exp2 = Expression.Constant(2);
BinaryExpression exp12 = Expression.Add(exp1, exp2);
ConstantExpression exp3 = Expression.Constant(3);
BinaryExpression exp123 = Expression.Add(exp12, exp3);

这个应该非常好理解。下面如果我们想写出Math.Sin(a)这个表达式的表达式树怎么办呢?这时问题就来了,这里面的“a”不知道该用什么表示。为了解决这个问题,下面Lambda表达式该登场了。

Lambda表达式

Lambda也是C#3.0/VB9新引入的表达式。我们都知道它和以前的匿名函数和委托有关。不过现在还是把这些暂时都忘掉,完全把Lambda 表达式当成一种新的表达式来看到。刚才我们看到了各种各样的表达式,有的表示一个常数;有的表示一个变量;有的表示加法;有的表示函数调用等等。 Lambda表达式作为一个表达式,它表达的是一个函数。Lambda表达式的成分就是一系列的参数加上一个表示函数逻辑的表达式组成。

(parameters) => expression

Lambda表达式最重要的特色是它可以引入一批参数,这批参数可以在函数体表达式中使用。基于这种特色,我们就可以创建出带自定义变量的表达式树,而这些自定义变量就表示成Lambda表达式的参数:

ParameterExpression expA = Expression.Parameter(typeof(double), "a"); //参数a
MethodCallExpression expCall = Expression.Call(null,
typeof(Math).GetMethod("Sin", BindingFlags.Static | BindingFlags.Public),
expA); //Math.Sin(a)
LambdaExpression exp = Expression.Lambda(expCall, expA); // a => Math.Sin(a)

我们这里使用了一个新的Expression树节点——MethodCallExpression。它可以表示一次方法调用。方法是使用MethodInfo实例来表示的。如果画成图的话,Lambda表达式可以画成这样:

image

 

由此可见,用Lambda表达式表示函数是一个非常直观的过程。有时候我们真的觉得没有名字的函数才是真正的函数。因为函数只需要参数和函数体两个成分即可,名称只是为了在别处引用它才需要的。

到此为止,我们已经理解了表达式树的基本概念。但是我们还只能用最原始的方法一步一步地构建表达式树。前面我们用到的 LambdaExpression是适用于各种类型函数的类,.NET还提供了一种适用于特定委托类型的 LambdaExpression<TDelegate>类型。我们用它来表示强类型的LambdaExpression。现在我们就要引入 C#、VB真正表达式和表达式树之间的桥梁——表达式树字面量(Expression Tree Literals),可以自动从Lambda表达式生成它的表达式树

Expression<Func<double, double>> exp = a => Math.Sin(a);

注意这个赋值语句,左侧是一个强类型的LambdaExpression:Expression<Func<double, double>>,右侧是一个真正的C#语法的Lambda表达式。C#的编译器在这种情况下就能自动为你生成右侧Lambda表达式的表达 式树。也就是说,这个exp和我们刚才手工生成Lambda表达式树基本是一样的。注意,这种特殊的语法仅能从Lambda表达式获得表达式树。别的表达 式是不能自动生成表达式树的。但是一旦我们获得了Lambda表达式,就可以直接从它的子节点获得内部表达式了。这是一个非常有用的语法,要深刻理解它的 作用。

需要注意的是,这里的委托类型Func<double, double>有双重作用,首先它限定生成的表达式树是一个接受double,并返回double的一元Lambda函数;其次这个委托可以直接用 在Lambda表达式树的编译当中,可在C#作强类型处理。我们后面谈到表达式树的编译时再详细的讨论这个问题。

表达式树的意义:数据化的表达式

我们现在已经能够用两种方式创建表达式树——用Expression的节点组合或者直接从C#、VB的Lambda表达式生成。不管使用的是那种方 法,最后我们得到的是一个内存中树状结构的数据。如果我们愿意,可以将它还原成文本源代码的表达式或者序列化到字符串里。注意,如果是C#的表达式本身, 我们是没法对它进行输出或者序列化的,它只存在于编译之前的源文件里。现在的表达式树已经成为了一种数据,而不在是语言中的表达式。我们可以在运行的时候 处理这一数据,精确了解其内在逻辑;将它传递给其他的程序或者再次编译成为可以执行的代码。这就可以总结为表达式树的三大基本用途:

  • 运行时分析表达式的逻辑
  • 序列化或者传输表达式
  • 重新编译成可执行的代码

在下一篇中,我们将着重介绍这三者在实际开发中的用途。

习题

还有习题?别担心,你可以将下列问题当做上机实践的素材,以便很快地理解本次学到的内容。

第一题:画出下列表达式的表达式树。一开始,您很可能不知道某些操作其实也是表达式(比如取数组的运算符a[2]),不过没有关系,后面的习题将帮你验证这一点。

-a

a + b * 2

Math.Sin(x) + Math.Cos(y)

new StringBuilder(“Hello”)

new int[] { a, b, a + b}

a[i – 1] * i

a.Length > b | b >= 0

(高难度)new System.Windows.Point() { X = Math.Sin(a), Y = Math.Cos(a) }

提示:注意运算符的优先级。倒数第二题的a是String类型,其余变量你可以用任意合适的简单类型。如果想知道以上表达式分别是什么表达式,可以查MSDN。

第二题:将上述表达式中的变量提取成参数,表示成Lambda表达式的形式。然后用Expression静态方法逐渐组合的方式将他们构建出来。

例如a + b * 2写成Lambda表达式就成了(int a, int b)=> a + b * 2。按照前面Math.Sin(a)例子的做法用Expression的方法组合出这一逻辑。

第三题:验证您第二题的结果。请将生成Expression实例ToString(),它就会显示出它的表达式原型。看看您构建的表达式ToString()出来是不是正确。

如果您发现生成的Expression不是你想要构建的,又不知道该怎么做的话,可以用表达式树字面量的语法让C#编译器帮您生成。然后用Reflector反编译它就能看到正确的表达式树。不过C#编译器有时会使用一些作弊手法,聪明的你应该能找到绕过的手段……

 

(待续)

[ASP.NET]简说Session

mikel阅读(638)

*       Session大家都知道也都用过(ASP.NET我遇到过没用过Cookies的,还没遇到过没用过Session)Session的保存方式默认是在内存中,更确切的说是在
ASP.NET
的进程中,这种方式是默认的保存方式即InProc方式,在web.config里面可以进行配置。这种方式由于和应用程序在同一进程中,所以有时会发生丢失的
情况。有没有解决办法呢?答案是有的。
       一般常用的Session保存方式还有StateServerSQLServer,StateServer是一个单独的进行,较之InProc要稳定的多。而SQLServer则是可以持久的保存Session
       还有一个平时大家都说关闭浏览器Session便会丢失,其实不是这样的。Session都是有个超时时间的(TimeOut),因为Web本身就是无连接的。服务器怎么可能知道客户关闭了浏览器?即而怎么可能随之关闭Session,下面讲一下为什么表面上浏览器关闭了Session就会丢失Session在服务器上是以散列表结构保存的,并且每个会话Session服务哭都会生成一个唯一ID,又名SessionID。而自定义的Session 键值是以哈希表形式存储在相应的SessionID下面的。比如

 

用户A

SessionIDgoly4y550qfsmy554cm3k155  

KeyLoginOK Valuetrue

KeyUserName Value:春哥

 

用户B

SessionIDv4h40355mnhk3k451nfibj55  

KeyLoginOK Valuetrue

KeyUserName Value:曾哥

 

从上面的例子就可以看出为什么用户A登录后的 Session[“UserName”]是春哥,而用户B登后的Session[“UserName”]是曾哥了。

 

话接上题,因为每次会话请求都会生成一个新的唯一的SessionID,所以当关闭浏览器的时候,当前的SessionID还是存在于服务器上的,Session并没有丢,但是重新又建立请求时又生成了新的SessionID所以以前的Session当然是取不到的了。(IE8打开页面A后再打开页面B是同一个SESSIONid,所以会发生双开覆盖用户信息的问题。可以通过文件新会话页面方式开启新的会话页面。而IE7以下不会这样情况)。

 

最后说一下基本上Session不能共享也是因为这样的原因即每个请求都会是一个新的会话SessionID

[JQuery]一步一步实现跨浏览器的可编辑表格

mikel阅读(743)

      在修改数据时,有时候为了方便,我们希望能够直接在表格里面对数据进行直接修改。
      要实现可编辑的表格功能,我们要解决以下问题:
      
      1.明确要修改的数据在表格中是哪些列(如何找到这些单元格);
      
      2.如何让单元格变成可以编辑的;
            3.如何处理单元格的一些按键事件;
            4.解决跨浏览器问题。

     我们通过JQuery可以一步一步解决上述问题。
     

 一、    绘制表格

 首先我们先画好一个表格。

     Code1:

Code

   画好表格以后显示的如图:

       editTable01.jpg
      

 

       很明显它看起来不像一个表格,既没有边框,而且很丑。那么我们先给这个表格设置一些样式。

       Code2:

Code

       现在效果好多了:

       editTable02.jpg

       

      
      
但是单元格和单元格之间还是有重叠的边框,只需要在标签选择符table中加上这样一个属性就能去除重复边框:

      border-collapse: collapse;       

table{
    color
: #4F6B72; 
    border
: 1px solid #C1DAD7;
    border-collapse
: collapse;
    width
: 400px;
}

      

editTable03.jpg

      

二、    让表格的单元格变成可编辑的列

 

绘制好表格以后,我们选取表格中的编号列作为可编辑的列。要让这一列的单元格能够被编辑,就需要在这些列中插入文本框,我们通过这一列单元格的onclick事件来插入文本框。

       Code3:


$(document).ready(function(){
    
//找到学号这一列的所有单元格
    //因为学号这一列的单元格在所有td中的位置是偶数(0,2,4,6),所以通过even就可以筛选到td中偶数位的单元格
    var numTd = $("tbody  td:even");
    
    
//单击这些td时,创建文本框
    numTd.click(function(){
        
//创建文本框对象
        var inputobj = $("<input type='text'>");
        
//获取当前点击的单元格对象
        var tdobj = $(this);
        
        
//去除文本框的border
        inputobj.css("border","0");
        
//让文本框和单元格的宽度保持一致
        inputobj.width(tdobj.width());
        
//让文本框的字体和单元格的字体大小一样
        inputobj.css("font-size",tdobj.css("font-size"));
        
//让文本框和单元格的字体保持一致
        inputobj.css("font-family",tdobj.css("font-family"));
        
//让文本框和单元格的背景保持一致
        inputobj.css("background-color",tdobj.css("background-color"));
        
        
//appendTo方法把文本框添加到td中
        inputobj.appendTo(tdobj);
    }
);
}
);

      

       现在已经把文本框插入到单元格中了。既然要编辑文本框,文本框就应该有值,文本框的值来源于单元格中的数据,并且我们要清空单元格中原有的数据。

       Code4:

       


$(document).ready(function(){
    
//找到学号这一列的所有单元格
    //因为学号这一列的单元格在所有td中的位置是偶数(0,2,4,6),所以通过even就可以筛选到td中偶数位的单元格
    var numTd = $("tbody  td:even");
    
    
//单击这些td时,创建文本框
    numTd.click(function(){
        
//创建文本框对象
        var inputobj = $("<input type='text'>");
        
//获取当前点击的单元格对象
        var tdobj = $(this);
        
//获取单元格中的文本
        var text = tdobj.html();
        
        
//清空单元格的文本
        tdobj.html("");
        
        
//去除文本框的border
        inputobj.css("border","0");
        
//让文本框和单元格的宽度保持一致
        inputobj.width(tdobj.width());
        
//让文本框的字体和单元格的字体大小一样
        inputobj.css("font-size",tdobj.css("font-size"));
        
//让文本框和单元格的字体保持一致
        inputobj.css("font-family",tdobj.css("font-family"));
        
//让文本框和单元格的背景保持一致
        inputobj.css("background-color",tdobj.css("background-color"));
        inputobj.css(
"color","#C75F3E");
        
        
//给文本框赋值
        inputobj.val(text);
        
        
//appendTo方法把文本框添加到td中
        inputobj.appendTo(tdobj);
    }
);
}
);


       但是以上代码看起来非常的繁琐,jQuery有一个非常好的优点,就是它的代码连缀。上面的代码可以通过连缀进行简化:

       Code5:


$(document).ready(function(){
    
//找到学号这一列的所有单元格
    //因为学号这一列的单元格在所有td中的位置是偶数(0,2,4,6),所以通过even就可以筛选到td中偶数位的单元格
    var numTd = $("tbody  td:even");
    
    
//单击这些td时,创建文本框
    numTd.click(function(){
        
//创建文本框对象
        var inputobj = $("<input type='text'>");
        
//获取当前点击的单元格对象
        var tdobj = $(this);
        
//获取单元格中的文本
        var text = tdobj.html();
        
        
//清空单元格的文本
        tdobj.html("");
        
        inputobj.css(
"border","0")
                .css(
"font-size",tdobj.css("font-size"))
                .css(
"font-family",tdobj.css("font-family"))
                .css(
"background-color",tdobj.css("background-color"))
                .css(
"color","#C75F3E")
                .width(tdobj.width())
                .val(text)
                .appendTo(tdobj);
    }
);
}
);


       现在表格中已经成功的插入了文本框,可以对单元格进行编辑了。

       editTable04.jpg 


       
    但是有个明显的bug,当你再次点击同一个单元格时,会出现如下效果:

        editTable05.jpg

      
 

       是什么原因造成上面这个bug呢?因为在文本框中插入单元格之后,文本框是属于单元格的,我们点击文本框时,同样会触发单元格的click事件。

       我们需要阻止文本框的点击行为(阻止事件冒泡)。

      Code6:

inputobj.click(function(){
            
return false;
        }
);


       但是点击单元格的边框时,还是会出现上述的bug,那我们做如下判断:如果单元格中已经插入了文本框,就跳出click事件。

       Code7:

      

Code

      

       上面的bug解决了,但是我发现,点击单元格时,虽然从表面上看文字是变了色,但没有让我觉得它是能被编辑的。那么我就做一点点的改动,插入文本框的同时,选中文本框的文本。

       Code 8:
      

inputobj.get(0).select();

 

       但是问题又来了,在Safari浏览器中,要让文本框处于选中状态,必须显得让文本框获得焦点。而我们这里只是在点击单元格时,插入文本框并给文本框赋值,文本框并没有获得焦点。解决的方法:通过jQuerytrigger方法来触发某个事件。

       Code9: 

inputobj.trigger("focus").trigger("select");

 

   三、文本框按键事件处理

      

以上的这些问题解决了,那我们就再来给文本框添加一些按键事件。我们知道不同的浏览器中获取按键的keyCode是不同的,但是jQuery帮我们解决了这个问题。

       只需要在事件的function中加入event参数,然后在方法体中,通过event对象的which属性就能获得keyCode,event.which属性同化了不同浏览器获取keyCode的方法。

       获得keyCode之后,我主要做两个按键事件:ESC(键值:27)Enter(键值:13)

       Code10:

       

Code

      

       下面是完整的js代码:

       Code11:

      

Code

      

       下面是源文件下载:/Files/psunny/EditTable.rar