[优化]ASP.NET中常用的26个优化性能方法

mikel阅读(813)

1. 数据库访问性能优化 
 
数据库的连接和关闭
访问数据库资源需要创建连接、打开连接和关闭连接几个操作。这些过程需要多次与数据库交换信息以通过身份验证,比较耗费服务器资源。ASP.NET中提供了连接池(Connection Pool)改善打开和关闭数据库对性能的影响。系统将用户的数据库连接放在连接池中,需要时取出,关闭时收回连接,等待下一次的连接请求。连接池的大小是有限的,如果在连接池达到最大限度后仍要求创建连接,必然大大影响性能。因此,在建立数据库连接后只有在真正需要操作时才打开连接,使用完毕后马上关闭,从而尽量减少数据库连接打开的时间,避免出现超出连接限制的情况。   
使用存储过程  
 
存储过程是存储在服务器上的一组预编译的SQL语句,类似于DOS系统中的批处理文件。存储过程具有对数据库立即访问的功能,信息处理极为迅速。使用存储过程可以避免对命令的多次编译,在执行一次后其执行规划就驻留在高速缓存中,以后需要时只需直接调用缓存中的二进制代码即可。另外,存储过程在服务器端运行,独立于ASP.NET程序,便于修改,最重要的是它可以减少数据库操作语句在网络中的传输。
优化查询语句
  
ASP.NET中ADO连接消耗的资源相当大,SQL语句运行的时间越长,占用系统资源的时间也越长。因此,尽量使用优化过的SQL语句以减少执行时间。比如,不在查询语句中包含子查询语句,充分利用索引等。   
2. 字符串操作性能优化 
 
使用值类型的ToString方法
  
在连接字符串时,经常使用”+”号直接将数字添加到字符串中。这种方法虽然简单,也可以得到正确结果,但是由于涉及到不同的数据类型,数字需要通过装箱操作转化为引用类型才可以添加到字符串中。但是装箱操作对性能影响较大,因为在进行这类处理时,将在托管堆中分配一个新的对象,原有的值复制到新创建的对象中。使用值类型的ToString方法可以避免装箱操作,从而提高应用程序性能。   
运用StringBuilder类   
String类对象是不可改变的,对于String对象的重新赋值在本质上是重新创建了一个String对象并将新值赋予该对象,其方法ToString 对性能的提高并非很显著。在处理字符串时,最好使用StringBuilder类,其.NET 命名空间是System.Text。该类并非创建新的对象,而是通过Append,Remove,Insert等方法直接对字符串进行操作,通过 ToString方法返回操作结果。   其定义及操作语句如下所示:
int num;   System.Text.StringBuilder str = new System.Text.StringBuilder(); //创建字符串   str.Append(num.ToString()); //添加数值num   Response.Write(str.ToString); //显示操作结果3. 优化 Web 服务器计算机和特定应用程序的配置文件以符合您的特定需要
默认情况下,ASP.NET 配置被设置成启用最广泛的功能并尽量适应最常见的方案。因此,应用程序开发人员可以根据应用程序所使用的功能,优化和更改其中的某些配置,以提高应用程序的性能。下面的列表是您应该考虑的一些选项。
仅对需要的应用程序启用身份验证。
默认情况下,身份验证模式为 Windows,或集成 NTLM。大多数情况下,对于需要身份验证的应用程序,最好在 Machine.config 文件中禁用身份验证,并在 Web.config 文件中启用身份验证。根据适当的请求和响应编码设置来配置应用程序。ASP.NET 默认编码格式为 UTF-8。如果您的应用程序为严格的 ASCII,请配置应用程序使用 ASCII 以获得稍许的性能提高。
  
考虑对应用程序禁用 AutoEventWireup。
在 Machine.config 文件中将 AutoEventWireup 属性设置为 false,意味着页面不将方法名与事件进行匹配和将两者挂钩(例如 Page_Load)。如果页面开发人员要使用这些事件,需要在基类中重写这些方法(例如,需要为页面加载事件重写 Page.OnLoad,而不是使用 Page_Load 方法)。如果禁用 AutoEventWireup,页面将通过将事件连接留给页面作者而不是自动执行它,获得稍许的性能提升。
从请求处理管线中移除不用的模块。
默认情况下,服务器计算机的 Machine.config 文件中节点的所有功能均保留为激活。根据应用程序所使用的功能,您可以从请求管线中移除不用的模块以获得稍许的性能提升。检查每个模块及其功能,并按您的需要自定义它。例如,如果您在应用程序中不使用会话状态和输出缓存,则可以从列表中移除它们,以便请求在不执行其他有意义的处理时,不必执行每个模块的进入和离开代码。
4. 一定要禁用调试模式  
在部署生产应用程序或进行任何性能测量之前,始终记住禁用调试模式。如果启用了调试模式,应用程序的性能可能受到非常大的影响。   
5. 对于广泛依赖外部资源的应用程序,请考虑在多处理器计算机上启用网络园艺  
ASP.NET 进程模型帮助启用多处理器计算机上的可缩放性,将工作分发给多个进程(每个CPU一个),并且每个进程都将处理器关系设置为其 CPU。此技术称为网络园艺。如果应用程序使用较慢的数据库服务器或调用具有外部依赖项的 COM 对象(这里只是提及两种可能性),则为您的应用程序启用网络园艺是有益的。但是,在决定启用网络园艺之前,您应该测试应用程序在网络园中的执行情况。  
6. 只要可能,就缓存数据和页输出  
ASP.NET 提供了一些简单的机制,它们会在不需要为每个页请求动态计算页输出或数据时缓存这些页输出或数据。另外,通过设计要进行缓存的页和数据请求(特别是在站点中预期将有较大通讯量的区域),可以优化这些页的性能。与 .NET Framework 的任何 Web 窗体功能相比,适当地使用缓存可以更好的提高站点的性能,有时这种提高是超数量级的。使用 ASP.NET 缓存机制有两点需要注意。首先,不要缓存太多项。缓存每个项均有开销,特别是在内存使用方面。不要缓存容易重新计算和很少使用的项。其次,给缓存的项分配的有效期不要太短。很快到期的项会导致缓存中不必要的周转,并且经常导致更多的代码清除和垃圾回收工作。若关心此问题,请监视与 ASP.NET Applications 性能对象关联的 Cache Total Turnover Rate 性能计数器。高周转率可能说明存在问题,特别是当项在到期前被移除时。这也称作内存压力。
7. 选择适合页面或应用程序的数据查看机制  
根据您选择在 Web 窗体页显示数据的方式,在便利和性能之间常常存在着重要的权衡。例如,DataGrid Web 服务器控件可能是一种显示数据的方便快捷的方法,但就性能而言它的开销常常是最大的。在某些简单的情况下,您通过生成适当的 HTML 自己呈现数据可能很有效,但是自定义和浏览器定向会很快抵销所获得的额外功效。Repeater Web 服务器控件是便利和性能的折衷。它高效、可自定义且可编程。   
8. 将 SqlDataReader 类用于快速只进数据游标  
SqlDataReader 类提供了一种读取从 SQL Server 数据库检索的只进数据流的方法。如果当创建 ASP.NET 应用程序时出现允许您使用它的情况,则 SqlDataReader 类提供比 DataSet 类更高的性能。情况之所以这样,是因为 SqlDataReader 使用 SQL Server 的本机网络数据传输格式从数据库连接直接读取数据。另外,SqlDataReader 类实现 IEnumerable 接口,该接口也允许您将数据绑定到服务器控件。有关更多信息,请参见 SqlDataReader 类。有关 ASP.NET 如何访问数据的信息,请参见通过 ASP.NET 访问数据。   
9. 将 SQL Server 存储过程用于数据访问  
在 .NET Framework 提供的所有数据访问方法中,基于 SQL Server 的数据访问是生成高性能、可缩放 Web 应用程序的推荐选择。使用托管 SQL Server 提供程序时,可通过使用编译的存储过程而不是特殊查询获得额外的性能提高。   
10. 避免单线程单元 (STA) COM 组件  
默认情况下,ASP.NET 不允许任何 STA COM 组件在页面内运行。若要运行它们,必须在 .aspx 文件内将 ASPCompat=true 属性包含在 @ Page 指令中。这样就将执行用的线程池切换到 STA 线程池,而且使 HttpContext 和其他内置对象可用于 COM 对象。前者也是一种性能优化,因为它避免了将多线程单元 (MTA) 封送到 STA 线程的任何调用。使用 STA COM 组件可能大大损害性能,应尽量避免。若必须使用 STA COM 组件,如在任何 interop 方案中,则应在执行期间进行大量调用并在每次调用期间发送尽可能多的信息。另外,小心不要在构造页面期间创建任何 STA COM 组件。例如下面的代码中,在页面构造时将实例化由某个线程创建的 MySTAComponent,而该线程并不是将运行页面的 STA 线程。这可能对性能有不利影响,因为要构造页面就必须完成 MTA 和 STA 线程之间的封送处理。
<%@ Page Language="VB" ASPCompat="true" %> Dim myComp as new MySTAComponent() Public Sub Page_Load() myComp.Name = “Bob” End Sub <% Response.Write(myComp.SayHello) %>
首选机制是推迟对象的创建,直到以后在 STA 线程下执行上述代码,如下面的例子所示。
<%@ Page Language="VB" ASPCompat="true" %> Dim myComp Public Sub Page_Load() myComp = new MySTAComponent() myComp.Name = “Bob” End Sub <% Response.Write(myComp.SayHello) %>
推荐的做法是在需要时或者在 Page_Load 方法中构造任何 COM 组件和外部资源。永远不要将任何 STA COM 组件存储在可以由构造它的线程以外的其他线程访问的共享资源里。这类资源包括像缓存和会话状态这样的资源。即使 STA 线程调用 STA COM 组件,也只有构造此 STA COM 组件的线程能够实际为该调用服务,而这要求封送处理对创建者线程的调用。此封送处理可能产生重大的性能损失和可伸缩性问题。在这种情况下,请研究一下使 COM 组件成为 MTA COM 组件的可能性,或者更好的办法是迁移代码以使对象成为托管对象。   
11. 将调用密集型的 COM 组件迁移到托管代码  
.NET Framework 提供了一个简单的方法与传统的 COM 组件进行交互。其优点是可以在保留现有投资的同时利用新的平台。但是在某些情况下,保留旧组件的性能开销使得将组件迁移到托管代码是值得的。每一情况都是不一样的,决定是否需要迁移组件的最好方法是对 Web 站点运行性能测量。建议您研究一下如何将需要大量调用以进行交互的任何COM 组件迁移到托管代码。许多情况下不可能将旧式组件迁移到托管代码,特别是在最初迁移 Web 应用程序时。在这种情况下,最大的性能障碍之一是将数据从非托管环境封送到托管环境。因此,在交互操作中,请在任何一端执行尽可能多的任务,然后进行一个大调用而不是一系列小调用。例如,公共语言运行库中的所有字符串都是 Unicode 的,所以应在调用托管代码之前将组件中的所有字符串转换成 Unicode 格式。另外,一处理完任何 COM 对象或本机资源就释放它们。这样,其他请求就能够使用它们,并且最大限度地减少了因稍后请求垃圾回收器释放它们所引起的性能问题。   
12. 在 Visual Basic .NET 或 JScript. 代码中使用早期绑定  
以往,开发人员喜欢使用 Visual Basic、VBScript. 和 JScript. 的原因之一就是它们所谓“无类型”的性质。变量不需要显式类型声明,并能够简单地通过使用来创建它们。当从一个类型到另一个类型进行分配时,转换将自动执行。不过,这种便利会大大损害应用程序的性能。Visual Basic 现在通过使用 Option Strict 编译器指令来支持类型安全编程。为了向后兼容,默认情况下,ASP.NET 不启用该选项。但是,为了得到最佳性能,强烈建议在页中启用该选项。若要启用 Option Strict,请将 Strict 属性包括在 @ Page 指令中,或者,对于用户控件,请将该属性包括在 @ Control 指令中。下面的示例演示了如何设置该属性,并进行了四个变量调用以显示使用该属性是如何导致编译器错误的。
<%@ Page Language="VB" Strict="true" %> <% Dim B Dim C As String ' This will cause a compiler error. A = "Hello" ' This will cause a compiler error. B = "World" ' This will not cause a compiler error. C = "!!!!!!" ' But this will cause a compiler error. C = 0 %> JScript. .NET 也支持无类型编程,但它不提供强制早期绑定的编译器指令。若发生下面任何一种情况,则变量是晚期绑定的:被显式声明为 Object,是无类型声明的类的字段,是无显式类型声明的专用函数或方法成员,并且无法从其使用推断出类型。   最后一个差别比较复杂,因为如果 JScript. .NET 编译器可以根据变量的使用情况推断出类型,它就会进行优化。在下面的示例中,变量 A 是早期绑定的,但变量 B 是晚期绑定的。
var A;   var B;   A = “Hello”;   B = “World”;   B = 0; 为了获得最佳的性能,当声明 JScript. .NET 变量时,请为其分配一个类型。例如,var A : String。
13. 使请求管线内的所有模块尽可能高效  
请求管线内的所有模块在每次请求中都有机会被运行。因此,当请求进入和离开模块时快速地触发代码至关重要,特别是在不使用模块功能的代码路径里。分别在使用及不使用模块和配置文件时执行吞吐量测试,对确定这些方法的执行速度非常有用。
14. 使用 HttpServerUtility.Transfer 方法在同一应用程序的页面间重定向  
采用 Server.Transfer 语法,在页面中使用该方法可避免不必要的客户端重定向。
  
15. 必要时调整应用程序每个辅助进程的线程数  
ASP.NET 的请求结构试图在执行请求的线程数和可用资源之间达到一种平衡。已知一个使用足够 CPU 功率的应用程序,该结构将根据可用于请求的 CPU 功率,来决定允许同时执行的请求数。这项技术称作线程门控。但是在某些条件下,线程门控算法不是很有效。通过使用与 ASP.NET Applications 性能对象关联的 Pipeline Instance Count 性能计数器,可以在 PerfMon 中监视线程门控。当页面调用外部资源,如数据库访问或 XML Web services 请求时,页面请求通常停止并释放 CPU。如果某个请求正在等待被处理,并且线程池中有一个线程是自由的,那么这个正在等待的请求将开始被处理。遗憾的是,有时这可能导致 Web 服务器上存在大量同时处理的请求和许多正在等待的线程,而它们对服务器性能有不利影响。通常,如果门控因子是外部资源的响应时间,则让过多请求等待资源,对 Web 服务器的吞吐量并无帮助。为缓和这种情况,可以通过更改 Machine.config 配置文件节点的 maxWorkerThreads 和 maxIOThreads 属性,手动设置进程中的线程数限制。   
注意:辅助线程是用来处理 ASP.NET 请求的,而 IO 线程则是用于为来自文件、数据库或 XML Web services 的数据提供服务的。分配给这些属性的值是进程中每个 CPU 每类线程的最大数目。对于双处理器计算机,最大数是设置值的两倍。对于四处理器计算机,最大值是设置值的四倍。无论如何,对于有四个或八个 CPU 的计算机,最好更改默认值。对于有一个或两个处理器的计算机,默认值就可以,但对于有更多处理器的计算机的性能,进程中有一百或两百个线程则弊大于利。注意进程中有太多线程往往会降低服务器的速度,因为额外的上下文交换导致操作系统将 CPU 周期花在维护线程而不是处理请求上。   
16. 适当地使用公共语言运行库的垃圾回收器和自动内存管理  
小心不要给每个请求分配过多内存,因为这样垃圾回收器将必须更频繁地进行更多的工作。另外,不要让不必要的指针指向对象,因为它们将使对象保持活动状态,并且应尽量避免含 Finalize 方法的对象,因为它们在后面会导致更多的工作。特别是在 Finalize 调用中永远不要释放资源,因为资源在被垃圾回收器回收之前可能一直消耗着内存。最后这个问题经常会对 Web 服务器环境的性能造成毁灭性的打击,因为在等待 Finalize 运行时,很容易耗尽某个特定的资源。   
17. 如果有大型 Web 应用程序,可考虑执行预批编译  
每当发生对目录的第一次请求时都会执行批编译。如果目录中的页面没有被分析并编译,此功能会成批分析并编译目录中的所有页面,以便更好地利用磁盘和内存。如果这需要很长时间,则将快速分析并编译单个页面,以便请求能被处理。此功能带给 ASP.NET 性能上的好处,因为它将许多页面编译为单个程序集。从已加载的程序集访问一页比每页加载新的程序集要快。批编译的缺点在于:如果服务器接收到许多对尚未编译的页面的请求,那么当 Web 服务器分析并编译它们时,性能可能较差。为解决这个问题,可以执行预批编译。为此,只需在应用程序激活之前向它请求一个页面,无论哪页均可。然后,当用户首次访问您的站点时,页面及其程序集将已被编译。没有简单的机制可以知道批编译何时发生。需一直等到 CPU 空闲或者没有更多的编译器进程(例如 csc.exe(C# 编译器)或 vbc.exe(Visual Basic 编译器))启动。还应尽量避免更改应用程序的 \bin 目录中的程序集。更改页面会导致重新分析和编译该页,而替换 \bin 目录中的程序集则会导致完全重新批编译该目录。在包含许多页面的大规模站点上,更好的办法可能是根据计划替换页面或程序集的频繁程度来设计不同的目录结构。不常更改的页面可以存储在同一目录中并在特定的时间进行预批编译。经常更改的页面应在它们自己的目录中(每个目录最多几百页)以便快速编译。Web 应用程序可以包含许多子目录。批编译发生在目录级,而不是应用程序级。
18. 不要依赖代码中的异常  
因为异常大大地降低性能,所以您不应该将它们用作控制正常程序流程的方式。如果有可能检测到代码中可能导致异常的状态,请执行这种操作。不要在处理该状态之前捕获异常本身。常见的方案包括:检查 null,分配给将分析为数字值的 String 一个值,或在应用数学运算前检查特定值。下面的示例演示可能导致异常的代码以及测试是否存在某种状态的代码。两者产生相同的结果。
  try   {   result = 100 / num;   }   catch (Exception e)   {   result = 0;   }   // …to this.   if (num != 0)   result = 100 / num;   else   result = 0;
19. 使用 HttpResponse.Write 方法进行字符串串联
该方法提供非常有效的缓冲和连接服务。但是,如果您正在执行广泛的连接,请使用多个 Response.Write 调用。下面示例中显示的技术比用对 Response.Write 方法的单个调用连接字符串更快。
Response.Write(“a”);   Response.Write(myString);   Response.Write(“b”);   Response.Write(myObj.ToString());   Response.Write(“c”);   Response.Write(myString2);   Response.Write(“d”); 20. 除非有特殊的原因要关闭缓冲,否则使其保持打开
禁用 Web 窗体页的缓冲会导致大量的性能开销。   
21. 只在必要时保存服务器控件视图状态  
自动视图状态管理是服务器控件的功能,该功能使服务器控件可以在往返过程上重新填充它们的属性值(您不需要编写任何代码)。但是,因为服务器控件的视图状态在隐藏的窗体字段中往返于服务器,所以该功能确实会对性能产生影响。您应该知道在哪些情况下视图状态会有所帮助,在哪些情况下它影响页的性能。例如,如果您将服务器控件绑定到每个往返过程上的数据,则将用从数据绑定操作获得的新值替换保存的视图状态。在这种情况下,禁用视图状态可以节省处理时间。默认情况下,为所有服务器控件启用视图状态。若要禁用视图状态,请将控件的EnableViewState 属性设置为 false,如下面的 DataGrid 服务器控件示例所示。
您还可以使用 @ Page 指令禁用整个页的视图状态。当您不从页回发到服务器时,这将十分有用:
<%@ Page EnableViewState="false" %>
注意:@ Control 指令中也支持 EnableViewState 属性,该指令允许您控制是否为用户控件启用视图状态。若要分析页上服务器控件使用的视图状态的数量,请(通过将 trace=”true” 属性包括在 @ Page 指令中)启用该页的跟踪并查看 Control Hierarchy 表的 Viewstate 列。有关跟踪和如何启用它的信息,请参见 ASP.NET 跟踪。
22. 避免到服务器的不必要的往返过程  
虽然您很可能希望尽量多地使用 Web 窗体页框架的那些节省时间和代码的功能,但在某些情况下却不宜使用 ASP.NET 服务器控件和回发事件处理。通常,只有在检索或存储数据时,您才需要启动到服务器的往返过程。多数数据操作可在这些往返过程间的客户端上进行。例如,从 HTML 窗体验证用户输入经常可在数据提交到服务器之前在客户端进行。通常,如果不需要将信息传递到服务器以将其存储在数据库中,那么您不应该编写导致往返过程的代码。如果您开发自定义服务器控件,请考虑让它们为支持 ECMAScript. 的浏览器呈现客户端代码。通过以这种方式使用服务器控件,您可以显著地减少信息被不必要的发送到 Web 服务器的次数。
使用 Page.IsPostBack 避免对往返过程执行不必要的处理
如果您编写处理服务器控件回发处理的代码,有时可能需要在首次请求页时执行其他代码,而不是当用户发送包含在该页中的 HTML 窗体时执行的代码。根据该页是否是响应服务器控件事件生成的。
使用 Page.IsPostBack 属性有条件地执行代码
例如,下面的代码演示如何创建数据库连接和命令,该命令在首次请求该页时将数据绑定到 DataGrid 服务器控件。
void Page_Load(Object sender, EventArgs e)   {   // Set up a connection and command here.   if (!Page.IsPostBack)   {   String query = “select * from Authors where FirstName like '%JUSTIN%'”;   myCommand.Fill(ds, “Authors”);   myDataGrid.DataBind();   }   }
由于每次请求时都执行 Page_Load 事件,上述代码检查 IsPostBack 属性是否设置为 false。如果是,则执行代码。如果该属性设置为 true,则不执行代码。注意 如果不运行这种检查,回发页的行为将不更改。Page_Load 事件的代码在执行服务器控件事件之前执行,但只有服务器控件事件的结果才可能在输出页上呈现。如果不运行该检查,仍将为 Page_Load 事件和该页上的任何服务器控件事件执行处理。   
23. 当不使用会话状态时禁用它  
并不是所有的应用程序或页都需要针对于具体用户的会话状态,您应该对任何不需要会话状态的应用程序或页禁用会话状态。   若要禁用页的会话状态,请将 @ Page 指令中的 EnableSessionState 属性设置为 false。例如:
<%@ Page EnableSessi %> 注意:如果页需要访问会话变量,但不打算创建或修改它们,则将@ Page 指令中的 EnableSessionState 属性设置为ReadOnly。还可以禁用 XML Web services 方法的会话状态。有关更多信息,请参见使用 ASP.NET 和 XML Web services 客户端创建的 XML Web services。若要禁用应用程序的会话状态,请在应用程序 Web.config 文件的 sessionstate 配置节中将 mode 属性设置为 off。例如:
24. 仔细选择会话状态提供程序  
ASP.NET 为存储应用程序的会话数据提供了三种不同的方法:进程内会话状态、作为 Windows 服务的进程外会话状态和 SQL Server 数据库中的进程外会话状态。每种方法都有自己的优点,但进程内会话状态是迄今为止速度最快的解决方案。如果只在会话状态中存储少量易失数据,则建议您使用进程内提供程序。进程外解决方案主要用于跨多个处理器或多个计算机缩放应用程序,或者用于服务器或进程重新启动时不能丢失数据的情况。有关更多信息,请参见 ASP.NET 状态管理。   
25. 不使用不必要的Server Control
ASP.net中,大量的服务器端控件方便了程序开发,但也可能带来性能的损失,因为用户每操作一次服务器端控件,就产生一次与服务器端的往返过程。因此,非必要,应当少使用Server Control。   
26. ASP.NET应用程序性能测试  
在对ASP.NET应用程序进行性能测试之前,应确保应用程序没有错误,而且功能正确。具体的性能测试可以采用以下工具进行:Web Application Strees Tool (WAS)是Microsoft发布的一个免费测试工具,可以从http://webtool.rte.microsoft.com/上下载。它可以模拟成百上千个用户同时对web应用程序进行访问请求,在服务器上形成流量负载,从而达到测试的目的,可以生成平均TTFB、平均TTLB等性能汇总报告。 Application Center Test (ACT) 是一个测试工具,附带于Visual Studio.NET的企业版中,是Microsoft正式支持的web应用程序测试工具。它能够直观地生成图表结果,功能比WAS多,但不具备多个客户机同时测试的能力。服务器操作系统”管理工具”中的”性能”计数器,可以对服务器进行监测以了解应用程序性能。   
结论:
对于网站开发人员来说,在编写ASP.NET应用程序时注意性能问题,养成良好的习惯,提高应用程序性能,至少可以推迟必需的硬件升级,降低网站的成本。

[文档]Null值判断

mikel阅读(906)

1) NULL
null 关键字是表示不引用任何对象的空引用的文字值。null 是引用类型变量的默认值。那么也只有引用型的变量可以为NULL,如果 int i=null,的话,是不可以的,因为Int是值类型的。
(2) DBNULL
DBNull在DotNet是单独的一个类型,该类只能存在唯一的实例,DBNULL.,DBNull唯一作用是可以表示数据库中的字符串,数字,或日期,为什么可以表示原因是DotNet储存这些数据的类(DataRow等)都是以 object 的形式来储存数据的。对于 DataRow , 它的 row[column] 返回的值永远不为 null , 要么就是具体的为column 的类型的值 。 要么就是 DBNull 。 所以 row[column].ToString() 这个写法永远不会在ToString那里发生NullReferenceException。DBNull 实现了 IConvertible 。但是,除了 ToString 是正常的外,其他的ToXXX都会抛出不能转换的错误。
(3) “”和String.Empty
这两个都是表示空字符串,其中有一个重点是string str1=”” 和 string str2=null 的区别,这样定义后,str1是一个空字符串,空字符串是一个特殊的字符串,只不过这个字符串的值为空,在内存中是有准确的指向的,string str2=null,这样定义后,只是定义了一个string 类的引用,str2并没有指向任何地方,在使用前如果不实例化的话,都将抱错。
(4) Convert.IsDBNull()
Convert.IsDBNull()返回有关指定对象是否为 DBNull 类型的指示,即是用来判断对象是否为DBNULL的。其返回值是True或Flase。
其实这些广义上的”空值”理解了后,还是有很大的区别的,甚至根本就没有关系。完全是两个概念。
摘自 http://blog.csdn.net/yumanqing/archive/2007/01/29/1497216.aspx

[文档]Dictionary与Hashtable

mikel阅读(992)

最近很多时候都在用C#的集合类型,发现个问题:什么时候用Dictionary和Hashtable?哪个一个性能上指的是查询的时候更高效呢,于是MSDN上搜了一下,找到了如下文档,大概意思是Hashtable利用一个缓存池来存储被唯一赋予按哈西算法生成的hashcode的值对象,类似与数据库中的索引,查询的时候读取每个对象的hashcode从缓存池中进行查询,最关键的是最后一段,Dictionary也是利用hash算法存储对象的,不过存储非简单类型对象的时候也就是自定的类型和Object类型的对象性能上要比hasttable类型高。
下面引自MSDN:
Hashtable and Dictionary Collection Types
The Hashtable class and the Dictionary<(Of <(TKey, TValue>)>) generic class implement the IDictionary interface. The Dictionary<(Of <(TKey, TValue>)>) generic class also implements the IDictionary<(Of <(TKey, TValue>)>) generic interface. Therefore, each element in these collections is a key-and-value pair.
A Hashtable object consists of buckets that contain the elements of the collection. A bucket is a virtual subgroup of elements within the Hashtable, which makes searching and retrieving easier and faster than in most collections. Each bucket is associated with a hash code, generated using a hash function and based on the key of the element.
A hash function is an algorithm that returns a numeric hash code based on a key. The key is the value of some property of the object being stored. A hash function must always return the same hash code for the same key. It is possible for a hash function to generate the same hash code for two different keys, but a hash function that generates a unique hash code for each unique key results in better performance when retrieving elements from the hash table.
Each object that is used as an element in a Hashtable must be able to generate a hash code for itself using an implementation of the GetHashCode method. However, you can also specify a hash function for all elements in a Hashtable by using a Hashtable constructor that accepts an IHashCodeProvider implementation as one of its parameters.
When an object is added to a Hashtable, it is stored in the bucket that is associated with the hash code that matches the object's hash code. When a value is being searched for in the Hashtable, the hash code is generated for that value, and the bucket associated with that hash code is searched.
For example, a hash function for a string might take the ASCII codes of each character in the string and add them together to generate a hash code. The string “picnic” would have a hash code that is different from the hash code for the string “basket”; therefore, the strings “picnic” and “basket” would be in different buckets. In contrast, “stressed” and “desserts” would have the same hash code and would be in the same bucket.
The Dictionary<(Of <(TKey, TValue>)>) class has the same functionality as the Hashtable class. A Dictionary<(Of <(TKey, TValue>)>) of a specific type (other than Object) has better performance than a Hashtable for value types because the elements of Hashtable are of type Object and, therefore, boxing and unboxing typically occur if storing or retrieving a value type.

[MVC]ASP.NET MVC之AJAX

mikel阅读(682)

本文的例子基于ASP.NET MVC Preview 3,并采用了JQuery完成客户端的JavaScript功能。

之前的两篇文章粗粗的对ASP.NET MVC做了介绍。这里强烈推荐一个网站,有兴趣的朋友可以看这里。这是Rob Conery的个人网站,他采用了ASP.NET MVC做了一个Demo, 不仅在codeplex上提供了这个项目的源代码,还提供了15个视频,这些视频的内容包括从项目的构思、到设计、再到实现和重构的一个完整的过程。

 

ASP.NET Web Form下的AJAX

在传统的Asp.Net下,如果不使用Asp.Net Ajax或者如Ajax.Net此类第三方的框架,就需要一个空白的页面,并且在此页面的Page_Load方法中完成所有的服务器端的操作,通过 Response将数据传回客户端,提交给JavaScript来处理。各人认为,这种方法的一个不好的地方就是一个页面只能完成一项功能,即使这个功能 再简单。

ASP.NET MVC下的AJAX

 

ASP.NET MVC中,每一个Request都被route到一个Controller下的Action来处理,即一个Controller Class的一个方法。因此,如果在Action方法中完成业务逻辑,并把需要回传的数据写回到Response中,在客户端再由JavaScript来 处理这些回传的数据,相信也能实现AJAX。基于这个想法,做了一个小小的Demo,实现了ASP.NET MVC下的ajax。

为了方便起见,客户端JavaScript的功能就通过JQuery来实现了。

页面文件:


 1<h2>Lunch Tracker List</h2>
 2<hr />
 3<!–<% using( Html.Form<LunchController>( lc => lc.Search(), FormMethod.Post )) { %>–>
 4<form id="UsersForm">
 5    Choose User:<%= Html.DropDownList("Users"new SelectList(ViewData.Model.Users, "ID""UserName"), new { id = "userName" })%> &nbsp;<input type="button" id="btnSearchLunch" value="Show All" />
 6</form>
 7<!–<%} %>–>
 8<br />
 9<id="userlunchlist">
10    
11</p>
12<script type="text/javascript">
13    $(document).ready(
14        function()
15        {
16            $("#btnSearchLunch").click(function()
17            {
18                var userName = $("#userName").val();
19                $.get("/Lunch/SearchUserAjax"{ name:userName }function(data)
20                    {
21                        $("p#userlunchlist").empty();
22                        $("p#userlunchlist").append(data);
23                        $("p#userlunchlist table").show("slow");
24                    }
);
25            }
);            
26        }

27    );
28
</script>

其中的$(document).ready(…..)是JQuery下的JavaScript实现,有兴趣的朋友可以看看jQuery官网中文社区

 

在来看一下Controller中的对应的Action方法:

 1         public void SearchUserAjax()
 2        {
 3            string uerid = Request["name"];
 4
 5            List<UserLunchList> lunchs = (
 6                                        from userlunch in repository.UserLunchLists.ToList()
 7                                        where userlunch.UserID == int.Parse(uerid)
 8                                        select userlunch
 9                                        ).ToList();
10            StringBuilder sb = new StringBuilder();
11            sb.Append("<table id='LunchList' style='display:none'><tr><th>User</th><th>Time</th><th>Price</th></tr>");
12            foreach (UserLunchList lunch in lunchs)
13            {
14                sb.Append("<tr><td>" + lunch.User.UserName + "</td><td>" + lunch.Time.ToShortDateString() + "</td><td>" + lunch.Cost + "</td></tr>");
15            }

16            sb.Append("</table>");
17            Response.ContentType = "text/html";
18            Response.Write(sb.ToString());
19        }

说穿了很简单,就是把想要的数据直接写到Response中就可以了,这里为了方便起见,就是写好了Table的格式。有一个地方需要注意的就是这个语句

Response.ContentType = "text/html" 很重要,它告知JavaScript以何种格式来处理Response中的数据。

 

核心内容就这么简单,呵呵。

如果觉得写JavaScript代码烦的话,可以使用extension方法,自定一个Html.Form或者其它的控件。
Sample Code

[MVC]推荐一个基于Microsoft ASP.NET MVC Preview 2 的应用示例

mikel阅读(673)

 园子里介绍Mircosoft MVC的文章已经有不少了,其中还有一些入门的系列示例很不错.
但是如果要使用这个东西开发应用的话,项目结构,代码分布上还有一些细节问题.当然每
个人可以会根据自己的实际开发经验进行分层设计.但我还是比较关注微软工程师们在使用
这个框架开发时的方式和习惯.所幸在MVC的官方链接上有这样一个应用,它就是采用MVC
框架进行架构的,当然还有一组视频从设计,集成测试等方面来介绍使用MVC框架.所以本
人就在这里借花献佛了:)

  它的源码下载链接:点击这里
      
  设计和测试视频:点击这里 
  

  当然如果这编译和运行这个应用,还需要Unity框架支持
  它的installer 下载链接:点击这里
  source code 链接: 点击这里   

  注意:示例应用的MVC版本为Microsoft ASP.NET MVC Preview 2,因为MVC框架更
新速度很快(基本上两三个月就一个版本),所以如果下载是Preview3的话,会在编译时报错

  下面是这个产品示例的物理文件截图:)

     

  
     其实就目前看来MVC还是一个试用性产品,离投入实际应用还待时日.所以园子中观望学
习的多,敢于吃螃蟹的少.不过本人希望到今年年底可以正式应用它来做一些软件.目前也只
能骑毛驴看唱本了:)

[Flex]整合Flex和Java—配置篇

mikel阅读(985)

uthor:yongtree

         废话就不说了,要想了解Flex的相关内容就请问一下Google,百度吧。切入正题,作为一个Java程序员学习Flex,关心的就是怎样将FlexJava进行结合交互。带着Java程序员的思维,一开始学习Flex并没有按部就班的学习Flex的基础知识,而是想搞清楚Flex到底怎样和Java交互的。经过了一个周末的研究,终于初见成果,下面就重要的讲解三种配置的两个。

         在分享这几种配置之前,先简单的介绍一下需要用到的一些资源。

1、  MyEclipse+Flex插件(官网下载)

2、  Tomcat6.0作为服务器(官网下载)

3、  BlazeDS(免费)代替LCDS(收费):没钱啊,只能先使用免费的了。从Adobe官方网站上下载下来,将blazeds.wards-console.warsamples.war三个文件放在tomcatwebapps目录下。

Flex+Java配置:

         第一种:Java工程和Flex工程独立,这种方式也是很多人使用的方式,Flex程序员和Java程序员相互独立的工作,这种方式网上有很多的资料,在这里就不再 赘述了。

         第二种:Flex工程加入Java元素

1、  切换到Flex视图,新建Flex project,如下图

因为我们是要javaflex结合,所以在服务器选择上我们选择J2EE

 

存放java类的源文件,我们的目的就是Flexjava在一个工程里,所以我们这里选择上

 

说明:Java source folder就是你自己java业务源码存放的根目录,在FB3里,LCDS项目旨在将Java J2ee项目和FlexLcds项目混合。

 

当然如果你不选择 combined 两个在一起,那么就麻烦些:要么你再单独新建一个Flex项目,而这个项目只写java代码。要么再建一个J2ee工程写java代码,而这个项目只写Flex代码,但最后要把Java编译后的class文件放到这个项目下的webroot\web-inf\classes目录中。即不管怎样,最后发布时,java编译后的class文件必须和lcds部署的项目在一起。

2、  点击Next,配置J2EE服务器,如下图

这里我们可以自定义输出路径,一般情况下设置成根目录WebRoot就可以了

因为我们采用的BlazeDS,所以这里要设置BlazeDS的路径

说明:Target runtime实际上没什么用(后来我删除了配置文件里的对应信息,也没问题),但是不指定就不能继续,如果这里显示的是<none>那么就新建一个Tomcatruntime,简单的只需要指定tomcat的安装目录即可。

 

Content folder实际上就是最终编译后的容器目录,因此,BlazeDSblazeds.war文件将会发布到 该目录 下的web-inf下的flex目录中。同时因为教程采用的是MyEclipse,他默认的就是发布WebRoot里的内容,为了自动化,因此这里改为了WebRoot(这也是java开发的习惯)

 

Flex WAR file 指的是安装了lcds后的flex.war文件的路径,但是在这里我们采用的是BlazeDS来取代lcds,所以这里设置的是blazeds.war的路径。

 

Compilation options指定了flex文件的编译方式,选择推荐的在FlexBuilder里编译吧,虽然开发时多耗点时间,但是在发布后不会占用服务器的编译处理时间,对用户来说是有好处的。

 

Output folder 指的是Flex编译后的swfhtml等文件存放的路径,这里改为了WebRoot,意思是发布到根目录就可以了。当然你可以根据你的需要和习惯自行设置其他的路径

 

3、  点击下一步,采用默认的配置就可以,点击完成,该工程就建立完成。下图为该工程的目录结构

Flex编译后发布的文件。我们发布在根目录下

自动生成的flex的配置文件

Java源文件夹

Flex文件的源文件夹

4、  让它变成web工程由MyEclipse发布吧

注意,不要点的太快了,要把这里的对号勾掉,这样就不会覆盖BlazeDS创建的web.xml

 

              点击Finish,现在我们的工程的图标变成了J2EE Web 工程了,这意味着,你可以用MyEclipse来发布它或者添加更多容器,比如hibernatespring

 

5、  工程建好以后,你可以通过右键—>属性来进行更多的设置。

6、  这样一个Flex+Java的工程就建立完成。

7、  编写例子,测试在介绍完第三种方式以后统一介绍。

 

第三种:由Web project反向加入Flex,也就是Java+Flex

1、  先建立一个web工程:flexweb。(略)

2、  flexweb工程手工添加Flex需要的元素。

1)首先将BlazeDS需要的jar文件拷到工程的lib目录下。可以将上面建的那个flex工程的lib下的jar文件拷到该工程下的lib目录下。

2)然后要加入Flex BlazeDS需要的配置文件。在WEB-INF下新建一个名为flex的文件夹,然后将我们上面建立的那个firstFlex该文件夹下的四个xml文件拷到该文件夹下。

3)最后,修改web.xml文件,加入Flex的配置。做法一个简单的把上面我们新建的那个flex工程的web.xml的部分代码拷过来。

<context-param>

       <param-name>flex.class.path</param-name>

       <param-value>/WEB-INF/flex/hotfixes,/WEB-INF/flex/jars</param-value>

    </context-param>

 

    <!– Http Flex Session attribute and binding listener support –>

    <listener>

        <listener-class>flex.messaging.HttpFlexSession</listener-class>

    </listener>

 

    <!– MessageBroker Servlet –>

    <servlet>

        <servlet-name>MessageBrokerServlet</servlet-name>

        <display-name>MessageBrokerServlet</display-name>

        <servlet-class>flex.messaging.MessageBrokerServlet</servlet-class>

        span

[Flex]FLEX资源---------手把手实现WebService服务与通讯

mikel阅读(801)

FLEX与后台交互的方式不外乎那三 种:HttpService,WebService,remoteObject.虽然从个人的角度我对WebService有些偏见(效率不高),可能因 为我是JAVA程序员,所以更喜爱RO这种专属于JAVA数据传输的高效,但WS带上了SOAP的帽子或许很多时候还是能起到关键作用,最近负责了一个 FLEX AIR项目就完全基于WS,所以也把WS拿出来说说.在FLEX里实现与后台的交互是相当方便的.语法瞧几眼大家都会,

 

我这里把关键的代码贴一下:

Java代码 复制代码
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">  
  3.     <mx:Script>  
  4.         <![CDATA[  
  5.             import mx.rpc.events.FaultEvent;  
  6.             import mx.rpc.events.ResultEvent;  
  7.             private function resultHanlder(e:ResultEvent):void{  
  8.                 msg.text=e.result.toString();  
  9.             }  
  10.             private function falutHanlder(e:FaultEvent):void{  
  11.                 msg.text=e.message.toString();  
  12.             }  
  13.               
  14.         ]]>  
  15.     </mx:Script>  
  16.     <mx:WebService wsdl="http://localhost:8080/axis2/services/AccountService?wsdl" id="testService" showBusyCursor="true"  
  17.      useProxy="false">  
  18.         <mx:operation name="withdraw" result="resultHanlder(event);">  
  19.             <mx:request xmlns="">  
  20.                 <money>  
  21.                     {parseInt(param.text)}  
  22.                 </money>  
  23.             </mx:request>  
  24.         </mx:operation>  
  25.     </mx:WebService>  
  26.     <mx:TextArea id="msg"/>  
  27.     <mx:TextInput id="param"/>  
  28.     <mx:Button label="sendMsg" click="testService.withdraw.send();"/>  
  29. </mx:Application>  

 

 

 

至于后端如何实现WEBService我有一个不错的文档,照着上面的操作流程图一步步来,相信不会出什么大问题.

[教程]理解.NET中的数据库连接池

mikel阅读(725)

理解.NET中的数据库连接池
作者 Joydip Kanjilal

摘要:

       连接池能在程度上提高数据库访问性能。本文讨论到底何为连接池,它如何提高数据库访问性能,以及如何在.NET中创建连接池并增加或移除连接。

 

导言

       连 接数据库是应用程序中耗费大量资源且相对较慢的操作,但它们又是至关紧要的。连接池是已打开的及可重用的数据库连接的一个容器。连接池在所有的数据库连接 都关闭时才从内存中释放。使用连接池最基本的好处是提高应用程序的性能及可伸缩性,而其主要缺点是会有一个或多个数据库连接将一直保持打开状态,即使当前 不在使用。ADO.NET的Data Providers将默认情况下将使用连接池,如果你不想使用连接池,必须在连接字符串中指定 ”Polling=false”。连接池中为你提供了空闲的打开的可重用的数据库连接,而不再需要每次在请求数据库数据时新打开一个数据库连接。当数据库 连接关闭或释放时,将返回到连接池中保持空闲状态直到新的连接请求到来。如果我们有效地使用连接池,打开和关闭数据库将不再很耗费资源。本文讨论连接池的 相关内容以及如何有效的使用连接池来提高应用程序的效率及可伸缩性。

 

连接池如何工作

       连 接池中包含打开的可重用的数据库连接。在同一时刻同一应用程序域中可以有多个连接池,但连接池不可以跨应用程序域共享。注意:一个连接池是通过一个唯一的 连接字符串来创建。连接池是根据第一次请求数据库连接的连接字符串来创建的,当另外一个不同的连接字符串请求数据库连接时,将创建另一个连接池。因此一个 连接字符中对应一个连接池而不是一个数据库对应一个连接池。如以下代码所示

代码1

// 新建一个连接池

SQLConnection SQLConnection = new SqlConnection();

sqlConnection.ConnectionString = 

"Server=localhost;Database=test;User ID=joydip;Password=joydip;Trusted_Connection=False";

sqlConnection.Open();      

代码2

// 因为连接字符串不同,新建另一个连接池

SqlConnection conn = new SqlConnection();

sqlConnection.ConnectionString = 

"Server=localhost;Database=test;User ID=test;Password=test;Trusted_Connection=False";

sqlConnection.Open();   

代码3

// 因为连接字符串与代码1相同,不再创建连接池.

SqlConnection conn = new SqlConnection();

sqlConnection.ConnectionString = 

"Server=localhost;Database=test;User ID=joydip;Password=joydip;Trusted_Connection=False";

sqlConnection.Open();      

 

当 有新的数据库连接请求到来时,连接池中连接进行了响应而不用创建一个新的数据库连接,也就是说数据库连接可以被重用,而不需要重新新建连接。因此这提高了 应用程序的效率和可伸缩性。当你在应用程序中关闭一个打开的数据库连接时,该连接返回到连接池中等待重新连接直到等待超时。在这个时间内等待同一数据库相 同连接信息的连接请求。如果这个时间内没有连接请求,这个数据库连接将被关闭,并从连接池中移除这个连接实例。

 

       当一个新的连接池创建后,数据库连接被添加到池中,连接池和池中的连接立即可被使用。连接池中将填满连接字个串中指定的最小连接数量的连接。连接池中连接在长时间不活动或超出指定的生存期时将被移除。

 

       连接池由连接池管理器维护。当后续的连接请求到来,连接池管理器在连接池中寻找可用的空闲的连接,如果存在就交给应用程序使用。以下描述了当一个新的连接请求到来时连接管理器如何工作

 

·         如果有未用连接可用,返回该连接

·         如果池中连接都已用完,创建一个新连接添加到池中

·        如果池中连接已达到最大连接数,请求进入等待队列直到有空闲连接可用

 

通过连接字符串中传递的参数可以控制连接池。基本的参数包括:

·         Connect Timeout

·         Min Pool Size

·        Max Pool Siz

·        Pooling

 

为了有效的使用连接池,记住数据库操作完成后马上关闭连接,这样连接才能返回连接池中。

 

提高连接池性能

       我 们应该在最晚时刻打开连接并在最早时刻释放连接,即在使用完成后立即释放。数据库连接应该在真正请求数据时才打开,而不应在使用之前就请求连接,这会减少 池中可用连接的数量,因此有害于连接池的操作及应用程序性能。数据库连接应使用完成后立即释放,这能促进连接池更好的使用,因为连接可以返回池中被重新使 用。以下代码展示如何在应用程序中有效地打开和关闭连接

 

代码4

SqlConnection sqlConnection = new SqlConnection(connectionString);

try

{

  sqlConnection.Open();

  //Some Code

}

 

finally

{

  sqlConnection.Close();

}

代码4可以使用”using”关键字进一步简化如以下代码所示

代码5

using(SqlConnection sqlConnection = new SqlConnection(connectionString))

{

  sqlConnection.Open();

  //Some Code

}

 

注:以上代码5中的”using”关键字将隐含地生成try-finally块

 

 

 

以下列举了更好地使用连接池的几个可参考要点

·         在需要使用时才打开连接,并在完成操作后马上关闭

·         在关闭连接时先关闭相关用户定义的事务

·        确保维持连接池中至少有一个打开的连接

·         在使用集成身份验证的情况下避免使用连接池

 

连接池可以通过以下途径进行监控

·         使用sp_who或sp_who2存储过程

·        使用SQL Server的Profiler

·        使用性能监视器的性能计数器

 

参考文献

Tuning Up ADO.NET Connection Pooling in ASP.NET Applications

Connection Pooling for the .NET Framework Data Provider for SQL Server

The .NET Connection Pool Lifeguard

ADO.NET Connection Pooling Explained

 

结语

       连 接池是数据库连接对象的容器,只要其中存在活动的或打开的连接它维持活动状态。当使用一个连接字符串来请求数据库连接时,将分配一个新的连接池。通过在应 用程序中使用相同的连接字符串我们可以提高应用程序的性能和可伸缩性。然而如果我们不正确地使用连接池可能给我们的应用程序带来负效果。MSDN中说“连 接是提高应用程序性能的有力工具,但如果使用不当连接池非但不是有益的而且是害的”。本文讨论了连接池的相关内容以及如何有效的使用连接池来提高应用程序 的效率及可伸缩性。

[教程][Serializable]在C#中的作用-NET 中的对象序列化

mikel阅读(924)

简介
序列化是指将对象实例的状态存储到存储媒体的过程。在此过程中,先将对象的公共字段和私有字段以及类的名称(包括类所在的程序集)转换为字节流,然后再把字节流写入数据流。在随后对对象进行反序列化时,将创建出与原对象完全相同的副本。

在面向对象的环境中实现序列化机制时,必须在易用性和灵活性之间进行一些权衡。只要您对此过程有足够的控制能力,就可以使该过程在很大程度上自动进 行。例如,简单的二进制序列化不能满足需要,或者,由于特定原因需要确定类中那些字段需要序列化。以下各部分将探讨 .NET 框架提供的可靠的序列化机制,并着重介绍使您可以根据需要自定义序列化过程的一些重要功能。

持久存储
我们经常需要将对象的字段值保存到磁盘中,并在以后检索此数据。尽管不使用序列化也能完成这项工作,但这种方法通常很繁琐而且容易 出错,并且在需要跟踪对象的层次结构时,会变得越来越复杂。可以想象一下编写包含大量对象的大型业务应用程序的情形,程序员不得不为每一个对象编写代码, 以便将字段和属性保存至磁盘以及从磁盘还原这些字段和属性。序列化提供了轻松实现这个目标的快捷方法。

公共语言运行时 (CLR) 管理对象在内存中的分布,.NET 框架则通过使用反射提供自动的序列化机制。对象序列化后,类的名称、程序集以及类实例的所有数据成员均被写入存储媒体中。对象通常用成员变量来存储对其他 实例的引用。类序列化后,序列化引擎将跟踪所有已序列化的引用对象,以确保同一对象不被序列化多次。.NET 框架所提供的序列化体系结构可以自动正确处理对象图表和循环引用。对对象图表的唯一要求是,由正在进行序列化的对象所引用的所有对象都必须标记为 Serializable(请参阅基本序列化)。否则,当序列化程序试图序列化未标记的对象时将会出现异常。

当反序列化已序列化的类时,将重新创建该类,并自动还原所有数据成员的值。

按值封送
对象仅在创建对象的应用程序域中有效。除非对象是从 MarshalByRefObject 派生得到或标记为 Serializable,否则,任何将对象作为参数传递或将其作为结果返回的尝试都将失败。如果对象标记为 Serializable,则该对象将被自动序列化,并从一个应用程序域传输至另一个应用程序域,然后进行反序列化,从而在第二个应用程序域中产生出该对 象的一个精确副本。此过程通常称为按值封送。

如果对象是从 MarshalByRefObject 派生得到,则从一个应用程序域传递至另一个应用程序域的是对象引用,而不是对象本身。也可以将从 MarshalByRefObject 派生得到的对象标记为 Serializable。远程使用此对象时,负责进行序列化并已预先配置为 SurrogateSelector 的格式化程序将控制序列化过程,并用一个代理替换所有从 MarshalByRefObject 派生得到的对象。如果没有预先配置为 SurrogateSelector,序列化体系结构将遵从下面的标准序列化规则(请参阅序列化过程的步骤)。

基本序列化
要使一个类可序列化,最简单的方法是使用 Serializable 属性对它进行标记,如下所示:

[Serializable]
public class MyObject {
   public int n1 = 0;
   public int n2 = 0;
   public String str = null;
}
以下代码片段说明了如何将此类的一个实例序列化为一个文件:

MyObject obj = new MyObject();
obj.n1 = 1;
obj.n2 = 24;
obj.str = "一些字符串";
IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream("MyFile.bin", FileMode.Create,
FileAccess.Write, FileShare.None);
formatter.Serialize(stream, obj);
stream.Close();
本 例使用二进制格式化程序进行序列化。您只需创建一个要使用的流和格式化程序的实例,然后调用格式化程序的 Serialize 方法。流和要序列化的对象实例作为参数提供给此调用。类中的所有成员变量(甚至标记为 private 的变量)都将被序列化,但这一点在本例中未明确体现出来。在这一点上,二进制序列化不同于只序列化公共字段的 XML 序列化程序。

将对象还原到它以前的状态也非常容易。首先,创建格式化程序和流以进行读取,然后让格式化程序对对象进行反序列化。以下代码片段说明了如何进行此操作。

IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream("MyFile.bin", FileMode.Open,
FileAccess.Read, FileShare.Read);
MyObject obj = (MyObject) formatter.Deserialize(fromStream);
stream.Close();

// 下面是证明
Console.WriteLine("n1: {0}", obj.n1);
Console.WriteLine("n2: {0}", obj.n2);
Console.WriteLine("str: {0}", obj.str);
上 面所使用的 BinaryFormatter 效率很高,能生成非常紧凑的字节流。所有使用此格式化程序序列化的对象也可使用它进行反序列化,对于序列化将在 .NET 平台上进行反序列化的对象,此格式化程序无疑是一个理想工具。需要注意的是,对对象进行反序列化时并不调用构造函数。对反序列化添加这项约束,是出于性能 方面的考虑。但是,这违反了对象编写者通常采用的一些运行时约定,因此,开发人员在将对象标记为可序列化时,应确保考虑了这一特殊约定。

如果要求具有可移植性,请使用 SoapFormatter。所要做的更改只是将以上代码中的格式化程序换成 SoapFormatter,而 Serialize 和 Deserialize 调用不变。对于上面使用的示例,该格式化程序将生成以下结果。

<SOAP-ENV:Envelope
   xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
   xmlns:xsd="http://www.w3.org/2001/XMLSchema"
   xmlns:SOAP- ENC=http://schemas.xmlsoap.org/soap/encoding/
   xmlns:SOAP- ENV=http://schemas.xmlsoap.org/soap/envelope/
   SOAP-ENV:encodingStyle=
   "http://schemas.microsoft.com/soap/encoding/clr/1.0
  http://schemas.xmlsoap.org/soap/encoding/"
   xmlns:a1="http://schemas.microsoft.com/clr/assem/ToFile">

   <SOAP-ENV:Body>
     <a1:MyObject id="ref-1">
       <n1>1</n1>
       <n2>24</n2>
       <str id="ref-3">一些字符串</str>
     </a1:MyObject>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>
需 要注意的是,无法继承 Serializable 属性。如果从 MyObject 派生出一个新的类,则这个新的类也必须使用该属性进行标记,否则将无法序列化。例如,如果试图序列化以下类实例,将会显示一个 SerializationException,说明 MyStuff 类型未标记为可序列化。

public class MyStuff : MyObject
{
   public int n3;
}
使用序列化属性非常方便,但是它存在上述的一些限制。有关何时标记类以进行序列化(因为类编译后就无法再序列化),请参考有关说明(请参阅下面的序列化规则)。

选择性序列化
类通常包含不应被序列化的字段。例如,假设某个类用一个成员变量来存储线程 ID。当此类被反序列化时,序列化此类时所存储的 ID 对应的线程可能不再运行,所以对这个值进行序列化没有意义。可以通过使用 NonSerialized 属性标记成员变量来防止它们被序列化,如下所示:

[Serializable]
public class MyObject
{
   public int n1;
   [NonSerialized] public int n2;
   public String str;
}
自定义序列化
可 以通过在对象上实现 ISerializable 接口来自定义序列化过程。这一功能在反序列化后成员变量的值失效时尤其有用,但是需要为变量提供值以重建对象的完整状态。要实现 ISerializable,需要实现 GetObjectData 方法以及一个特殊的构造函数,在反序列化对象时要用到此构造函数。以下代码示例说明了如何在前一部分中提到的 MyObject 类上实现 ISerializable。

[Serializable]
public class MyObject : ISerializable
{
   public int n1;
   public int n2;
   public String str;

   public MyObject()
   {
   }

   protected MyObject(SerializationInfo info, StreamingContext context)
   {
     n1 = info.GetInt32("i");
     n2 = info.GetInt32("j");
     str = info.GetString("k");
   }

   public virtual void GetObjectData(SerializationInfo info,
StreamingContext context)
   {
     info.AddValue("i", n1);
     info.AddValue("j", n2);
     info.AddValue("k", str);
   }
}
在 序列化过程中调用 GetObjectData 时,需要填充方法调用中提供的 SerializationInfo 对象。只需按名称/值对的形式添加将要序列化的变量。其名称可以是任何文本。只要已序列化的数据足以在反序列化过程中还原对象,便可以自由选择添加至 SerializationInfo 的成员变量。如果基对象实现了 ISerializable,则派生类应调用其基对象的 GetObjectData 方法。

需要强调的是,将 ISerializable 添加至某个类时,需要同时实现 GetObjectData 以及特殊的构造函数。如果缺少 GetObjectData,编译器将发出警告。但是,由于无法强制实现构造函数,所以,缺少构造函数时不会发出警告。如果在没有构造函数的情况下尝试反 序列化某个类,将会出现异常。在消除潜在安全性和版本控制问题等方面,当前设计优于 SetObjectData 方法。例如,如果将 SetObjectData 方法定义为某个接口的一部分,则此方法必须是公共方法,这使得用户不得不编写代码来防止多次调用 SetObjectData 方法。可以想象,如果某个对象正在执行某些操作,而某个恶意应用程序却调用此对象的 SetObjectData 方法,将会引起一些潜在的麻烦。

在反序列化过程中,使用出于此目的而提供的构造函数将 SerializationInfo 传递给类。对象反序列化时,对构造函数的任何可见性约束都将被忽略,因此,可以将类标记为 public、protected、internal 或 private。一个不错的办法是,在类未封装的情况下,将构造函数标记为 protect。如果类已封装,则应标记为 private。要还原对象的状态,只需使用序列化时采用的名称,从 SerializationInfo 中检索变量的值。如果基类实现了 ISerializable,则应调用基类的构造函数,以使基础对象可以还原其变量。

如果从实现了 ISerializable 的类派生出一个新的类,则只要新的类中含有任何需要序列化的变量,就必须同时实现构造函数以及 GetObjectData 方法。以下代码片段显示了如何使用上文所示的 MyObject 类来完成此操作。

[Serializable]
public class ObjectTwo : MyObject
{
   public int num;

   public ObjectTwo() : base()
   {
   }

   protected ObjectTwo(SerializationInfo si, StreamingContext context) :
base(si,context)
   {
     num = si.GetInt32("num");
   }

   public override void GetObjectData(SerializationInfo si,
StreamingContext context)
   {
     base.GetObjectData(si,context);
     si.AddValue("num", num);
   }
}
切记要在反序列化构造函数中调用基类,否则,将永远不会调用基类上的构造函数,并且在反序列化后也无法构建完整的对象。

对象被彻底重新构建,但是在反系列化过程中调用方法可能会带来不良的副作用,因为被调用的方法可能引用了在调用时尚未反序列化的对象引用。如果正在 进行反序列化的类实现了 IDeserializationCallback,则反序列化整个对象图表后,将自动调用 OnSerialization 方法。此时,引用的所有子对象均已完全还原。有些类不使用上述事件侦听器,很难对它们进行反序列化,散列表便是一个典型的例子。在反序列化过程中检索关键 字/值对非常容易,但是,由于无法保证从散列表派生出的类已反序列化,所以把这些对象添加回散列表时会出现一些问题。因此,建议目前不要在散列表上调用方 法。

序列化过程的步骤
在格式化程序上调用 Serialize 方法时,对象序列化按照以下规则进行:

检查格式化程序是否有代理选取器。如果有,检查代理选取器是否处理指定类型的对象。如果选取器处理此对象类型,将在代理选取器上调用 ISerializable.GetObjectData。
如果没有代理选取器或有却不处理此类型,将检查是否使用 Serializable 属性对对象进行标记。如果未标记,将会引发 SerializationException。
如果对象已被正确标记,将检查对象是否实现了 ISerializable。如果已实现,将在对象上调用 GetObjectData。
如果对象未实现 Serializable,将使用默认的序列化策略,对所有未标记为 NonSerialized 的字段都进行序列化。
版本控制
.NET 框架支持版本控制和并排执行,并且,如果类的接口保持一致,所有类均可跨版本工作。由于序列化涉及的是成员变量而非接口,所以,在向要跨版本序列化的类中 添加成员变量,或从中删除变量时,应谨慎行事。特别是对于未实现 ISerializable 的类更应如此。若当前版本的状态发生了任何变化(例如添加成员变量、更改变量类型或更改变量名称),都意味着如果同一类型的现有对象是使用早期版本进行序 列化的,则无法成功对它们进行反序列化。

如果对象的状态需要在不同版本间发生改变,类的作者可以有两种选择:

实现 ISerializable。这使您可以精确地控制序列化和反序列化过程,在反序列化过程中正确地添加和解释未来状态。
使用 NonSerialized 属性标记不重要的成员变量。仅当预计类在不同版本间的变化较小时,才可使用这个选项。例如,把一个新变量添加至类的较高版本后,可以将该变量标记为 NonSerialized,以确保该类与早期版本保持兼容。
序列化规则
由 于类编译后便无法序列化,所以在设计新类时应考虑序列化。需要考虑的问题有:是否必须跨应用程序域来发送此类?是否要远程使用此类?用户将如何使用此类? 也许他们会从我的类中派生出一个需要序列化的新类。只要有这种可能性,就应将类标记为可序列化。除下列情况以外,最好将所有类都标记为可序列化:

所有的类都永远也不会跨越应用程序域。如果某个类不要求序列化但需要跨越应用程序域,请从 MarshalByRefObject 派生此类。
类存储仅适用于其当前实例的特殊指针。例如,如果某个类包含非受控的内存或文件句柄,请确保将这些字段标记为 NonSerialized 或根本不序列化此类。
某些数据成员包含敏感信息。在这种情况下,建议实现 ISerializable 并仅序列化所要求的字段。

[教程]Membership扩展

mikel阅读(819)

转]用好VS2005之扩展membership服务
ASP.NET 2.0中新增的最佳功能之一是新的成员身份服务,它提供了用于创建和管理用户帐户的易于使用的API
   ASP.NET 2.0中新增的最佳功能之一是新的成员身份服务,它提供了用于创建和管理用户帐户的易于使用的API。ASP.NET 1.x大规模引入了窗体身份验证,但仍然要求您编写相当数量的代码来执行实际操作中的窗体身份验证。成员身份服务填补了ASP.NET 1.x窗体身份验证服务的不足,并且使实现窗体身份验证变得比以前简单得多。
  成员身份API 通过两个新的类公开:Membership和MembershipUser。前者包含了用于创建用户、验证用户以及完成其他工作的静态方法。 MembershipUser代表单个用户,它包含了用于检索和更改密码、获取上次登录日期以及完成类似工作的方法和属性。通过这两个新的类,我们可以不 用写一行代码,方便得完成对用户的管理。 但是在实际开发过程中,绝对不能满足我们日常开发的需要。经过日常项目的开发和网络上资料的搜索,现将其一一列出:
  一、默认的各类的数据库是使用SQL express的,而我们在实际开发中往往使用SQL sever 2000或者SQL server 2005,这时就需要我们修改数据库的类型。
  微软给我们提供了一个Aspnet_regsql的命令来修改默认数据库。打开 Visual Studio 2005 命令提示,输入aspnet_regsql,按照提示一步一步进行即可。
  此时打开数据库,可以发现多处来了一系列"aspnet_"开头的存储过程,这就是我们使用membership所必需的存储过程。
  此时打开IIS,[属性] →[ASP.NET] →[编辑配置]:
  [常规],连接参数LocalSQLServer按照普通的sql连接字符串格式。
  [身份验证],模式为Forms,管理提供程序的minRequiredNonalphanumericCharacters为0,这时就可以去掉默认变态的必需需要输入字母,数字等组合的密码安全了。此步也可修改密码最低长度和最大长度等等。
  经过此步骤,系统会自动在web.config中配置好了我们所需的规则。很方便,修改web.config以后都可以通过这种图形化工具来了。
   二、由于自带的login控件和membership类,只提供了简单的用户信息录入,不能满足我们项目的需要。例如:我们要用户注册的时候同时输入 QQ号码,电话号码,家庭地址。那么默认的是没有办法解决的。我这里给出两种解决方案。我分别用在了不同的项目中。优缺点大家自行判断。
  1、使用profile。此类方法网上教程已经很多。不在出重复叙述,免去赚稿费的嫌疑:)。这里只是给出网上没有的部分说明。
由于membership只能列出来指定组的用户名,而不能列出其他的详细信息,我们实际使用中,往往需要对组中的其它信息进行同时修改。我采用的是自行构造datatable的方法。见代码:
public static DataTable listuser(string userRoles)//列出指定组的用户信息
{
 string[] users = Roles.GetUsersInRole(userRoles);
 //列出指定组下的用户
 DataTable dt = new DataTable();
 dt.Columns.Add("username", System.Type.GetType("System.String"));
 dt.Columns.Add("QQ", System.Type.GetType("System.String"));
 dt.Columns.Add("phone", System.Type.GetType("System.String"));
 dt.Columns.Add("address", System.Type.GetType("System.String"));
 dt.Columns.Add("email", System.Type.GetType("System.String"));
 //以上构造一个数据表
 foreach (string i in users)
 {
  DataRow dr = dt.NewRow();
  MembershipUser mu = Membership.GetUser(i);
  得到用户基本信息
  ProfileCommon p = Profile.GetProfile(i); //得到用户的profile信息
  dr[0] = mu. username;
  dr[1] = p. QQ;
  //profile是强类型,可以很方便的通过感知来添加。
  dr[2] = p. phone;
  dr[3] = p. address;
  dr[4] = mu. email;
  dt.Rows.Add(dr);
  dt.AcceptChanges();
 }
 return dt;
}
public static void deleteuser(string username)/删除指定用户
{
 Membership.DeleteUser(username);
 //系统会自动删除profile下的指定用户的信息
}
public static void updateuser(string username)/更新指定用户
{
 ProfileCommon p = Profile.GetProfile(i);
 //得到用户的profile信息
 p. phone="电话";
 p. address="地址;
 p. QQ="QQ号码";
 p.Save();
 //保存所作修改。
}
  2、自定义一个membershipinfo表格,同membership系统标关联起来。自己编写SQL语句来进行查询,修改等功能。
列出指定组的用户
select * from aspnet_membership inner join aspnet_users on
aspnet_membership.userid=aspnet_users.userid left join memberinfo on aspnet_membership.userid=memberinfo.userid
where aspnet_membership.userid=(select userid from aspnet_usersinroles inner join
aspnet_roles on aspnet_usersinroles.roleid=aspnet_roles.roleid where rolename='admin')
  
  删除、修改等功能比较简单,这里就不作叙述。可以采用membership的createuser方法建立,然后再用sql语句写入memberinfo表。
  对于建立用户,我们可以采取扩展CreateUserWizard控件,或者自行写登陆界面。
   1、采取扩展CreateUserWizard控件,我们可以使用它的模版列,此时需要注意的是:用户名,密码,提示问题,提示问题答案,Email, 他们的ID一定要分别是username,Password,Question,Answer,Email否则会出错,而且此时验证控件均不能使用。怀疑 是IDE的一个Bug。
  如下所示,我们定义好的样式应当是:
<WizardSteps>
<asp:CreateUserWizardStep runat="server"> 自定义代码部分<ContentTemplate>
</ContentTemplate>
</asp:CreateUserWizardStep>
</WizardSteps>
代码部分:
protected void CreateUserWizard1_CreatedUser(object sender, EventArgs e)
{
 //由于系统会自动给们建立基本的信息表,所以我们只需要对profile或者membershipinfo标进行修改即可。   
 Roles.AddUserToRole(CreateUserWizard1.UserName, "shop"); 
 //添加用户到相应的组
 ProfileCommon p = (ProfileCommon)ProfileCommon.Create(CreateUserWizard1.UserName, true);
 p. QQ = ((TextBox)CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("QQ")).Text.Trim();
 p.address= ((TextBox)CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("address")).Text.Trim();   
  p.phone = ((TextBox)CreateUserWizard1.CreateUserStep.ContentTemplateContainer.FindControl("phone")).Text.Trim(); p.Save();//保存所作修改
}
  2、采取自己写UI,个人推荐使用这种方法。灵活性比较大,而且可以使用2005强大的验证控件。
  页面部分比较简单略过不再陈述。下面详细介绍一下代码部分。
   此时我们需要使用membership的CreateUser()方法。语法我们就不再介绍。他会根据建立用户的结果返回成一个 MembershipCreateStatus枚举类,它详细的包含了所有建立用户不成功的错误信息。我们只需要根据他的值,就可以返回给界面相应的提 示,如:用户名已经存在,电子邮件已经存在等等。
  对于自定义用户信息部分我们仍然可以采取profile或者自定义membershipinfo表的方法。
  列出profile建立用户的方法。SQL语句比较简单,略过不写。
protected void Button1_Click(object sender, EventArgs e)
{
 MembershipCreateStatus status;
  MembershipUser newUser = Membership.CreateUser(username.Text.Trim(), Password.Text.Trim(),  Email.Text.Trim(), Question.Text.Trim(), Answer.Text.Trim(), true, out status);
 //使用membership建立用户,并把建立结果返回给MembershipCreateStatus
  if (newUser == null)//没有新用户,则意味着出错。
  {
  GetErrorMessage(status);
  //调用GetErrorMessage函数,返回详细错误信息
  } else {
   Roles.AddUserToRole(newUser.UserName, "jiancai");
   //添加用户到相应组 ProfileCommon
   p = (ProfileCommon)ProfileCommon.Create(newUser.UserName, true);
   p.QQ = QQ.Text.Trim();
   p.address= address.Text.Trim();
   p.phone= phone.Text.Trim();
   p.Save();
  }
 }
public void GetErrorMessage(MembershipCreateStatus status)
{
 switch (status)
  {
   case MembershipCreateStatus.DuplicateUserName: DisplayAlert("当前用户已经存在,请重新选择");
    break;
    //其余各种错误信息,请查看MSDN的MembershipCreateStatus枚举类。
   default: DisplayAlert("注册00000000用户失败,请检查您的用户名,密码等信息");
    break;
  }
}
   个人见解:采取profile的方法,比较方便,由于profile 是强类型,可以通过智能感知功能减少代码的输入量。采取自定义数据表的方法,需要输入大量的sql语句,但是查询速度比较快,性能比较强,由于 Roles.GetUsersInRole()方法无法分页读取数据,只能一次性读出来所有数据,而自写SQL 语句可以很方便的根分页结合起来。随着用户量的增多,故不推荐profile方法。
  三、密码问题。
   个人觉得密码是一个比较头疼的问题。我们在实际开发中总是需要admin组有对用户进行密码修改的权限。Membership提供的修改密码方法只能在 已经知道密码提示答案的时候才能修改。而admin组根本不可能知道用户的密码提示答案的。这里有点好笑。难道是中西方文化差异?
  列出个人对membership密码研究的一些心得。
  大家都知道machine.config和web.config,如果两者发生冲突,那么以web.config优先。
  默认状态下,membership是采用SHA1的方法进行加密,然后采取一种机制,与passwordsalt进行再次加密,最后形成数据库中显示的密码。可见随着MD5加密的破解,微软对密码的安全也煞费苦心。
  有的朋友不喜欢默认的SHA1加密形式,我们只需要在web.config中设置以下代码来覆盖machine.config中对密码的设置就好了:
<machineKey
validationKey="AutoGenerate,IsolateApps"
decryptionKey="AutoGenerate,IsolateApps"
decryption="Auto"
validation="MD5/SHA1/Clear" />
  其中:
  Clear – 密码以明文形式存储。用户密码可与此值直接比较,而不需要进行进一步的转换。
  MD5 -使用消息摘要 5 (MD5) 哈希摘要存储密码。为了验证凭据,将使用 MD5 算法对用户密码进行哈希运算并将计算出来的值与存储的值进行比较。使用此值时,从不存储或比较明文密码。此算法的性能比 SHA1 好。
  SHA1 -使用 SHA1 哈希摘要存储密码。为了验证凭据,将使用 SHA1 算法对用户密码进行哈希运算并将计算出来的值与存储的值进行比较。从不存储明文密码。使用该算法可以获得比 MD5 算法高的安全性。
   然而虽然MembershipUser提供了GetPassword 方法,但是这是后只有在加密形式设置成Clear,即密码在数据库中以明码的形式存在,才能得到密码。而ChangePassword必须要提供旧密码或 者密码提示答案才可以修改。对用户管理造成了很大的不便。找了许多资料无解。顺便提带一下:微软的membership机制起源于csblog,并进行了 一定的修改。
  Csblog对密码的策略:首先对用户输入的密码进行加密(SHA1或者MD5),然后根据系统自动生成的密匙再次对加密后的密码进行DES加密。不过我按照csblog 的方法进行加密的时候,形式相同,密码却不同,目前正在演就中。也欢迎大家共同讨论。
  如果passwordsalt和password固定,那么用户密码肯定一定。所以目前我采取的一个方法就是给用户重新设置成固定密码的功能。
  首先取得已知用户密码的passwordsalt和password,然后替换相应用户的passwordsalt和password字段。这时,用户的密码就已经恢复成已知的密码了。
   不可否认,membership给我们开发中创造了很大的便利,其方便的Roles功能,对于我们进行权限管理的时候提供了很好的解决方案。2005许 多功能均进化自csblog这优秀的开源项目,有兴趣者可以研究csblog,以进一步的了解membership的运作机制。限于篇幅,MSDN上边有 做介绍的我就不再重复叙述。只介绍MSDN上没有列出的技巧跟我在项目开发中的一些心得。 ASP.NET 2.0为使用窗体身份验证的Web站点提供了重要的安全性优势。通过提供用户配置文件储备库以及对角色的支持,窗体身份验证将走出ASP.NET内行的视 野,而得以更广泛地实现。