本章内容:
.Net Framework 2.0 包含大量功能的改进并添加了很多功能。本章研究其中的一些功能,并阐释如何使用它们以减少编码工作量,同时又可以提高应用程序的可用性、安全性以及可维护性。

本页内容
|  | 应用程序:基类库的增强功能 | 
|  | 应用程序:Console 增强功能 | 
|  | 应用程序:安全性增强功能 | 
应用程序:基类库的增强功能
此应用程序演示许多基类库的增强功能,例如类型改进、扩展的应用程序跟踪、类型化资源以及泛型集合。
新概念
.NET Framework 2.0 包含一些强大的、可在任何 .NET 应用程序中实现的新增功能。可使用这些新功能来改善应用程序任务,例如转换和验证数据,监视执行速度及捕获和记录应用程序事件。
类型增强功能
.NET Framework 的基本类型(如 System.String和 System.DateTime)始终提供许多使用这些特定类型数据的方法。但是,一些数据操作和类型转换的情况却未得到很好的解决。例如,String 类型的 Split 方法允许将分隔的字符串分析成一个数组。字符串中的每个元素都作为一项添加到数组中。如果字符串有空元素(即两个分隔符相邻),则向数组添加一个空项。对于大部分情况而言这已经足够了,但是在有些情况下,您可能希望忽略空元素而不是将它们添加到数组中。一个新的 Split 重载已经添加到 String 类型中以处理这些情况。现在可拆分一个字符串并提供第二个参数,指示是否想要忽略空元素。下面的代码拆分一个包含四个元素的字符串,其中一个元素是空元素。第二个参数值为 true,指示方法忽略空元素,因此结果数组只包含三项。
Dim colorArray() as String Dim colors as String = “Red,Blue,,Green" colorArray = colors.Split(separators, True)
另一个常见的任务是将字符串数据分析成另一种数据类型 — 例如,当用户输入日期时,您需要将该值分析成一个 DateTime变量。以前,Parse方法是这种情况下唯一可用的机制。遗憾的是,如果字符串不能分析成适当的类型,则 Parse方法就会引发异常。此行为要求您将任何对Parse方法的调用包装在 Try…Catch块中以处理异常,这样,如果应用程序经常遇到无效数据,就会引起性能问题。另外,还添加了一个名为 TryParse的新方法,它接受两个参数。第一个参数是要分析的字符串,第二个参数是分析成功时赋给分析值的变量。该方法返回一个 Boolean值,指示分析是否成功。这允许您检查返回值以确定赋值变量是否包含分析值。下面的代码将试图分析指定的字符串。如果分析成功,将显示一个带有分析值的消息框。否则显示一个失败消息。
Dim inputString as String Dim parsedValue as DateTime Dim tryResult as Boolean tryResult = DateTime.TryParse(inputString, parsedValue) If tryResult Then MessageBox.Show(“The parsed date is “& parsedValue.ToString() & “.”) Else MessageBox.Show(“The parse failed. “) End If
通常情况下,最先需要对字符串数据执行的一个操作是确定变量包含的是一个数值还是 null 或为空。这种需要非常常见,因此一个名为 IsNullOrEmpty的新方法添加到 String 类中。这是一个共享成员,它只返回一个 Boolean值,指示所提供的字符串是否等于 Nothing 或空字符串。调用 IsNullOrEmpty等同于使用 IsNothing函数并进行变量与 String.Empty 的比较。下面的代码在试图在分析之前使用 IsNullOrEmpty验证字符串变量是否包含数据:
If Not String.IsNullOrEmpty(inputString) Then DateTime.TryParse(inputString, parsedValue) End If
泛型集合
.NET Framework 通过 System.Collections和 System.Collections.Specialized命名空间提供许多强大的集合类。这些类(如 ArrayList和 Hashtable)允许您存储任何类型的对象,并轻松地添加、删除和搜索项。但是,这种灵活性是以类型安全为代价的。内置集合不是类型安全的,因为无法隐式保证集合的所有成员都属于某一特定类型。必须通过编写代码显式实现类型安全,在每次向集合添加项时,强制执行类型约束。另外,每当从集合中检索一项时,必须将所返回的对象手动转换为所需的类型。这些类型检查和类型转换例程通常都在自定义集合类中实现,这些自定义集合类是框架提供的一种集合的包装。下面的代码显示一个基本的自定义集合类,它执行这些任务以将 Product 对象存储在 ArrayList 中:
Public Class ProductCollection Private myProducts As New ArrayList Public Sub Add(ByVal prod As Product) myProducts.Add(prod) End Sub Public Function Item(ByVal index As Integer) As Product Return CType(myProducts(index), Product) End Function End Class
.NET Framework 中添加的泛型集合允许用很少的代码创建类型安全的集合。事实上,可创建一个完全实现版的产品集合类,而只需用一行代码来描述,如稍后所示。.NET Framework 通过 System.Collections.Generics 命名空间来提供泛型集合,并且包括一些泛型实现,例如 List、KeyValuePair、Stack、Queue 等。它们之所以称为泛型,是因为它们包含泛型集合实现。在对泛型实例化时告诉它想将其存储为什么类型。然后,编译器会创建一个特定的集合类,它为存储所指定的类型进行了优化,并拥有强类型成员。下面的代码创建 System.Collections.Generics.List类的一个实例,为存储 Product 对象而进行了类型化。List类本质上是 ArrayList 的泛型版本。
Dim products As New System.Collections.Generic.List(Of Product)
请注意,实例化泛型的语法与实例化非泛型类的语法稍有不同。使用 Of 子句来提供集合要存储的数据类型。一些泛型(如 KeyValuePair)允许您指定多个数据类型参数。KeyValuePair泛型集合需要两个数据类型参数,一个用于指定键所接受的类型,另一个用于指定值所接受的类型。所生成的实例拥有强类型成员,因此您不需要编写任何类型检查或转换代码。下面的代码显示如何使用前面为存储 Product 对象而创建的genericList的一些方法:
Dim prod as new Product(“Chai Tea”) Dim products As New System.Collections.Generic.List(Of Product) Products.Add(prod) Products.Item(0).Description = “A great tasting tea. “
类型化资源
资源文件一直为打包应用程序资源(如界面字符串和图像)提供便捷的机制。但是,在 .NET Framework 的早期版本中,从资源文件中检索资源始终会返回一个对象,之后必须手动将其转换为所需的类型。现在,Visual Studio .NET 2005 解决了这个问题,方法是每次编辑资源文件时自动创建强类型的资源包装。这些包装使您易于访问资源,而无需执行强制转换操作,或了解如何使用 System.Resources.ResourceManager类的细节。
异常和跟踪
.NET Framework 提供用于封装错误信息的 System.Exception类和 System.Diagnostics命名空间中用于跟踪应用程序事件信息的类。Exception 类始终提供公开错误状态的属性,例如 Message、StackTrace 和 HelpLink。一个名为 Data 的新成员已添加到异常类中,允许附加任何其他与异常相关的信息。例如,可能希望附加一些值来表示出现异常时应用程序的状态。
System.Diagnostics命名空间提供用于将应用程序事件信息输出到外部存储(如文件或事件日志)的类。.NET Framework 2.0 添加了 TraceSource类,在如何跟踪事件数据方面,该类为您提供了更大的灵活性。每个 TraceSource实例都有其自己的名称和 TraceListeners 集合。跟踪侦听器负责向外部存储发送跟踪消息。定义多个跟踪源的功能允许一个应用程序为其不同部分定义不同的源。例如,可以为用户界面和数据访问组件分别定义一个 TraceSource。
System.Diagnostics命名空间还包括一个名为 StopWatch的新类,以便轻松捕获运行时间间隔。可以通过调用共享的 StartNew方法来启动一个新计时器,然后通过读取 Elapsed 属性(该属性返回一个 TimeSpan实例)来检索运行时间间隔。
演练
EmployeeManager应用程序演示如何实现多种新框架功能。此应用程序将员工数据从 XML 文件加载到Employee对象的集合中。通过将此集合绑定到 DataGrid 来显示数据。新的类型改进用于确保 XML 文件中所包含的数据是有效的员工数据。通过实现所提供的一种泛型集合来创建员工集合。应用程序异常由一个中央异常管理器处理,它使用新的跟踪功能将错误信息输出到 XML 日志文件(使用新的 XmlWriterTraceListener)。最后,窗体的背景图像从一个由 Resgen 创建的强类型资源包装中加载。
验证和转换字符串数据
现在,通过使用新的 IsNullOrEmpty和 TryParse方法,可以很容易地检查字符串值是否存在,也可以很容易地将它分析为另一种数据类型。EmployeeFactory类的 CreateEmployee方法负责从 XML 文件中读取字符串的值,验证该数据,然后用它创建Employee实例。下面的代码显示一个员工 ID 的读取和验证:
buffer = empElement.GetAttribute(“id”) If String.IsNullOrEmpty(buffer) OrElse _ Not Integer.TryParse(buffer, tempID) Then . . . End If
首先,从XML 属性 id 中检索员工 ID 值并将该值分配给 buffer 变量。下一步是调用 String.IsNullOrEmpty 以确保 buffer 包含一个数值。如果 buffer 包含一个值,则可以调用 Integer.TryParse 尝试将该值分析成 Integer 类型。在本例中,我们将 buffer 作为源值传递,将其转换成整数类型,而将 tempID作为变量传递,对其分配转换后的整数值。如果其中有一步操作返回了 False,就会创建异常。此异常的细节和处理稍后再做探讨。验证操作的其余部分使用类似的方式检查员工的名称和雇用日期。
Employee类将已传递给其构造函数的名称分析成单独的姓名值以对数据执行进一步操作。XML 文件将名称以逗号分隔名称各部分(包括名字、中间名和姓氏)列表的形式进行存储。但是,中间名可选。ParseName函数使用新的 String.Split重载之一将分隔的名称部分拆分到一个名称组成部分的数组中,并指示 Split操作忽略任何空组成部分。然后通过检查最终数组的长度来确定是否提供了中间名,并将适当的值分配给 myFirstName和 myLastName变量,如下列代码所示:
Private Sub ParseName(ByVal fullName As String) Dim nameParts() As String Dim separators(1) As Char separators(0) = Char.Parse(“,”) nameParts = fullName.Split(separators, True) Select Case nameParts.Length Case 2 myLastName = nameParts(0) myFirstName = nameParts(1) Case 3 myLastName = nameParts(0) myFirstName = nameParts(2) End Select End Sub
使用泛型集合
EmployeeFactory 类的 LoadEmployeeCollection方法处理员工集合的创建和填充。通过使用下列代码,将该集合作为 System.Collections.Generics.List类的一个实例来实现:
Dim tempList As New List(Of Employee)
此代码定义List泛型的一个新实例,它只接受Employee实例。在稍后的步骤中,使用下列代码将新创建的Employee对象添加至员工集合中:
tempList.Add(tempEmp)
尽管此代码似乎与常规 ArrayList 中添加的对象并无差别,但其实这两者之间存在很大的不同,因为 ArrayList允许添加任何对象,而该集合只允许添加Employee对象。
监视执行速度
除了返回已填充的员工集合之外,LoadEmployeeCollection方法还将创建集合所花费的时间作为一个输出参数返回。该方法最先执行的操作是使用下列代码启动一个新的 stopwatch:
Dim watch As Stopwatch = Stopwatch.StartNew()
就在此过程完成之前,系统检索运行时间并使用下列代码将其分配给 loadTime参数:
loadTime = watch.Elapsed
以上代码返回一个 TimeSpan实例,该实例在用户界面的状态栏中显示。
跟踪应用程序事件
EmployeeManager应用程序使用名为 ExceptionManager 的中央异常处理程序。每当创建或捕获一个异常时,此应用程序就会将异常传递给 ExceptionManager 的 HandleException方法,该方法接着再将异常传递给 TraceException方法,然后该 TraceException方法基于 rethrow 参数值有选择地再次引发异常。ExceptionManager在它首次被访问时创建一个共享的 TraceSource实例:
Private Shared ts As New TraceSource(TRACE_SOURCE_NAME)
每个 TraceSource都有一个名称,它是用于标识事件来源的字符串。这里所配置的 TraceSource只能用于跟踪错误信息。EmployeeManager应用程序的其他部分可以定义其他 TraceSource以跟踪其他信息,例如性能度量。
此 TraceSource实例在 ExceptionManager 的共享构造函数中进一步配置:
Shared Sub New() Trace.AutoFlush = True ts.Switch.Level = SourceLevels.Error Dim outputPath As String outputPath = Path.Combine(Directory.GetCurrentDirectory, LOG_FILE_NAME) Dim traceListener As New XmlWriterTraceListener(outputPath) traceListener.TraceOutputOptions = TraceOptions.DateTime Or _ TraceOptions.ProcessId ts.Listeners.Add(traceListener) End Sub
上述代码首先设置 Trace 类的共享 AutoFlush属性。这就确保所有的跟踪侦听器会自动将其内容刷新到基础存储。下一行代码指定:跟踪源应该只能跟踪标记为 SourceLevels.Error 源级别的消息。通常,该值来自于配置文件,因此您可以轻松更改记录的跟踪消息的类型。事实上,此过程中的大部分配置也可以通过应用程序配置文件来定义。跟踪源定义跟踪什么消息,而跟踪侦听器定义如何跟踪消息。跟踪源必须至少有一个定义为可用的跟踪侦听器,但是如果您需要将跟踪信息输出至多个目标,它就可以有多个侦听器。例如,您可能希望将跟踪消息分别输出至事件日志和本地文本文件。
在定义一个文本文件以记录消息之后,上述代码就创建了一个 XmlWriterTraceListener 实例。此框架提供的跟踪侦听器将消息以 XML 格式输出至指定的文件。下一步配置是标识事件生成器应该输出跟踪信息的哪些可选部分。TraceListener类的 TraceOutputOptions属性允许您指定一些选项,例如 DateTime、ProcessID、ThreadID 和 CallStack。侦听器使用该属性确定应该将哪些信息包含在它们所构造的输出消息中。一旦跟踪侦听器配置完成,就将其添加到跟踪源的Listener集合中。
TraceException过程通过下列代码调用跟踪源的 TraceEvent方法:
Dim message As String = x.Message For Each key As String In x.Data.Keys message += vbCrLf & key & “:”& x.Data(key).ToString Next ts.TraceEvent(TraceEventType.Error, ERRORCODE_INVALID_EMPLOYEE, message)
在调用 TraceEvent方法之前,该过程通过从异常的 Data 属性中检索信息来生成自定义的错误消息。您将在该演练的后面部分中看到这些数据是如何填充的。接着,该过程调用一个 TraceEvent方法的重载,它允许您指定事件的类型、任意事件 id 以及消息字符串。跟踪源将此信息传递给它的每一个事件侦听器,然后这些侦听器构造事件字符串,并将事件信息发送到最终存储(在本例中为 log.txt 文本文件)。
在EmployeeFactory 类的 CreateEmployee方法中,您可以看到,当在源 XML 文件中发现无效数据时,调用 ExceptionHandler。例如,下列代码用于分析员工的雇用日期:
buffer = empElement.GetAttribute(“hireDate”) If String.IsNullOrEmpty(buffer) OrElse _ Not Date.TryParse(buffer, tempHireDate) Then Dim x As New ApplicationException( _ "Unable to create Employee object. Invalid ‘hireDate’attribute.”) x.Data.Add(“id", tempID) x.Data.Add(“fullName", tempName) x.Data.Add(“hireDate", buffer) ExceptionManager.HandleException(x, False) Return Nothing End If
如果XML 属性 hireData 中的数据不能转换成有效的 DateTime,就会创建一个新的 ApplicationException,并将 XML 文件中的值添加到异常的 Data 属性中。这就允许导致错误的相关业务数据携带异常。
使用类型化资源
EmployeeManager使用另一种框架增强功能。它使用强类型的资源包装以提供对嵌入应用程序资源的简单访问。如果您显示解决方案资源管理器中的所有文件,您就会在 My Project 下方发现一个名为 MyResources.resx 的资源文件。如果您双击此文件,Visual Studio 将在资源编辑器中打开它。一个名为 FormBackgroundImage的资源已经进行了定义,其类型为 System.Drawing.Bitmap。Visual Studio 为每个资源文件维护一个代码隐藏文件。该代码隐藏文件包含了所生成的强类型资源包装类。您可以通过展开解决方案资源管理器中的 MyResources.resx 并双击 MyResources.vb 来查看此包装类。My 命名空间自动为您提供对该类的访问。窗体的 LoadResources方法使用下列代码将窗体图像加载到 Panel 的 BackgroundImage属性。
Panel1.BackgroundImage = My.Resources.FormBackgroundImage
小结
此应用程序中所呈现的 .NET Framework 增强功能并不只局限于桌面环境。任何应用程序(不论是桌面应用程序、Web 应用程序,还是服务应用程序)都可以使用这些增强功能来改进字符串操作,加快集合生成,使跟踪更灵活,以及使实现资源文件更容易。
应用程序:Console 增强功能
此应用程序演示了许多 System.Console类的增强功能,这些增强功能允许您创建更强大的控制台应用程序。
新概念
System.Console类提供了主要的用户界面服务以通过控制台窗口与用户进行交互,例如显示文本消息和接受用户的键盘输入。
用户界面操作
控制台应用程序的可视化界面由三个主要组件组成:控制台窗口本身、窗口中显示的文本以及游标。在 .NET Framework 的早期版本中,您可以使用 Write 和 WriteLine方法向屏幕底部发送消息,以及使用 Read 和 ReadLine方法以每次一个字符或是一行的方式读取用户输入,但除此之外几乎不能对此界面进行其他控制。没有为文本设置格式的功能,也不能控制文本在屏幕的输出位置。文本始终出现在控制台窗口的底部。在 .NET Framework 2.0 中,现在可以通过属性(如 ForegroundColor和 BackgroundColor)来指定界面的颜色,这两个属性均接受新 ConsoleColor枚举中的值。也可以对文本的输出位置进行更多的控制,因为现在可以使用 SetCursorPosition方法显式设置光标的位置。颜色可用于强调重要的信息,而设置光标位置允许您轻松地创建更多使用其他布局(如列和表)的信息显示方式。现在,也可以使用 SetWindowSize方法以代码的方式来操作控制台窗口的大小。
文本捕获
现在,您可以通过三种不同的方法读取用户的输入:Read、ReadLine 和 ReadKey。Read 和 ReadLine仍然以一贯的方式(即分别以每次读取一个字符或者一行的方式)进行操作。这两种方法均简单地返回一个字符串,该字符串包含所输入的单个字符或多个字符。但是,新的 ReadKey方法返回一个 ConsoleKeyInfo对象,该对象包含更多有关用户输入的信息。通过 ConsoleKeyInfo,您可以确定修改键是否也是使用 Modifiers 属性按下的,该属性返回 ConsoleModifiers枚举的一个实例。Key 属性返回 ConsoleKey枚举的一个实例,KeyChar属性返回一个 Char 对象,该对象包含所按下的实际字符。通过这些属性的比较,您可以创建可读性很强并易于维护的用户输入处理代码,而且该代码同时为您的用户提供了更多输入选项。
缓冲区操作
控制台窗口实际上只是字符缓冲区中的视图。以前,此缓冲区不是直接可访问的,但是现在,可以通过 SetBufferSize方法来控制此缓冲区的大小。默认的缓冲区大小是 80 列 300 行,但可根据应用程序的要求将其设置成任意自定义的大小。例如,控制台用户普遍关心的一个问题是:一旦缓冲区已满,最原始的数据就会被推出缓冲区并丢失。如果用户试图向后滚动至旧数据,她将发现只能访问最近的 300 行信息。如果应用程序频繁显示大量用户需要能够再次访问的数据,可考虑增加缓冲区的大小。您也可以通过使用 SetIn和 SetOut方法重写默认的缓冲区输入流和输出流。使用这些方法允许您重定向命令(如 ReadLine)从何处获得数据,以及方法(如 WriteLine)将数据发送到何处。
演练
ConsoleEnhancements 应用程序显示如何使用 Console 类的这些新格式设置和用户输入功能创建一个使用控制台窗口的丰富的用户界面。此应用程序允许您在控制台窗口中创建 ASCII 艺术品(即通过标准 ASCII 字符的创造性排列创建的图片)。您可以使用箭头、Home、End、Page Up 以及 Page Down 键来移动输入光标。按任意的字符或数字键会将该字符输入到屏幕上当前光标所在位置。可以通过按 F1 键并在提示处输入一种颜色名称来更改当前的文本颜色。颜色名称必须是 System.ConsoleColor 枚举中的成员,并且必须区分大小写。忽略所有其他项。其中一些有效的颜色是:红、蓝、绿和黄。可以在任何时候按 Escape 键退出应用程序。此应用程序使用本章前面所述的大多数功能来提供光标定位、颜色输出、窗口和缓冲区操作,以及字符输入和求值。
操作 Console 窗口
应用程序启动时,它使用下列代码将字符缓冲区和控制台窗口的大小调整为 100 列 50 行。这是通过先使用 SetBufferSize方法设置缓冲区大小,再使用 SetWindowSize方法设置窗口大小来完成的。
Console.SetBufferSize(BUFFER_WIDTH, BUFFER_HEIGHT) Console.SetWindowSize(Console.BufferWidth, Console.BufferHeight)
如果您试图先设置窗口大小,则会出现异常,因为窗口大小不能设置为大于基础缓冲区。使用这些设置,您会注意到滚动栏是不可见的。这是因为窗口大得足可以显示缓冲区的全部内容。我们无法显式地显示或隐藏滚动条,也无法防止用户调整窗口的大小。
捕获键信息
应用程序通过使用 Console.ReadKey方法在其 Main 方法中捕获按键信息:
userKeyInfo = Console.ReadKey(True) While userKeyInfo.Key <> ConsoleKey.Escape
在识别出一个按键之前,阻止对ReadKey的调用。然后,它返回一个包含按键信息的 ConsoleKeyInfo对象。ReadKey包含一个称为 intercept 的可选参数,它确定是否将按键信息自动发送到控制台窗口。如果要完全控制用户输入处理,可以将该参数设置为 True,并手动处理字符的显示。接着,将所捕获的键信息传递给 ProcessKey方法,该方法通过询问 userKeyInfo参数的 Key 属性确定要采取什么操作。然后根据所按的键使用一些实用程序过程提供的帮助。
SetDrawLocation过程通过调用 Console.SetCursorPosition 将光标移动到窗口的某一特定位置。如果您想要跟踪光标的位置以便返回屏幕上的特定点,您就需要在位置变动之前对其进行手动存储。该应用程序调用 SetDrawLocation,光标每次在绘图图面移动时,SetDrawLocation 都更新 drawX和 drawY变量以包含当前的光标绘图位置值。
应用程序维护控制台窗口中的两个不同区域、绘图图面和提示区。提示区用于显示发送给用户的消息以及用户输入的请求。RequestInput方法通过设置光标位置将消息输出到提示区,并使用 Console.Write显示文本。
Private Function RequestInput(ByVal prompt As String) As String Console.ForegroundColor = promptColor Console.SetCursorPosition(0, promptLine) Console.Write(prompt) Dim input As String = Console.ReadLine() ClearInputLine() Console.ForegroundColor = drawColor Return input End Function
然后,它通过使用 Console.ReadLine检索用户的响应,在结束时,清除提示区并将控制台的 ForegroundColor属性返回到适当的绘图颜色。当用户按 F1 时,应用程序就会调用 PickColor方法,该方法使用 RequestInput 显示一个提示:
Private Sub PickColor() Dim colorInput As String = RequestInput(“Enter your color by name: “) Dim consoleColorType As Type = Console.ForegroundColor.GetType() If [Enum].IsDefined(consoleColorType, colorInput) Then Dim tempColor As ConsoleColor = _ CType([Enum].Parse(consoleColorType, colorInput), ConsoleColor) SetDrawColor(tempColor) End If End Sub
接着,将返回的用户响应字符串分析成一个 ConsoleColor实例,然后通过 SetDrawColor方法将其分配给 Console.ForegroundColor属性。
小结
对 Console 类进行的改进十分显著,它们允许您通过使用一些功能(如绝对定位、缓冲区操作以及文本颜色)来创建更具说明性的界面。例如,管理工具现在可以很容易地将数据以表格的形式显示在更大的窗口图面中,同时通过颜色编码表示不同程度的强调。
应用程序:安全性增强功能
此应用程序演示了对代码访问安全性的两个改进。
新概念
代码访问安全性允许代码实体(如程序集、类或方法)要求调用代码在调用执行之前传递一组权限。例如,对文件系统进行写操作的组件可以要求赋予调用方修改文件系统的权限。您可以通过将相应的权限属性应用于实体声明来要求代码实体上的权限。下列代码显示一个名为 SaveFile的方法,它要求调用方对 C 驱动器的根目录具有写权限如果调用方通过了权限检查,就会执行该方法,否则调用失败并引发安全性异常。
<FileIOPermission(SecurityAction.Demand, Write:="C:\”)> _ Public Sub SaveFile(ByVal data As String) . . . End Sub
框架在 System.Security.Permissions命名空间中提供了许多代码访问权限类,其中一些在表 7-1 中列出。
| 表 7-1 框架权限类 | |
| 名称 | 说明 | 
| EnvironmentPermission | 读或写环境变量 | 
| EventLogPermission | 对事件日志服务的读或写访问 | 
| FileIOPermission | 读、追加或写文件或目录 | 
| OdbcPermission | 访问 ODBC 数据源 | 
| PrintingPermission | 访问打印机 | 
| RegistryPermission | 读、写、创建或删除注册表项和注册表值 | 
| SocketPermission | 建立或接受传输地址的连接 | 
StrongNameIdentityPermission
在一些情况下,您可能希望基于调用代码的标识来限制对代码的访问。例如,您可能有一个实用工具程序集,只想让其他两个指定的程序集调用它。要做到这一点,实用工具程序集必须能够确认调用代码的标识,并确定其是否具有一个授权标识。如果程序集编译为强名称程序集,那么它就具有可验证的标识。强名称程序集在编译时使用公钥/私钥对建立标识,其他程序集在运行时使用该标识来对其进行识别。实用工具类将使用 StrongNameIdentityPermission来指定哪些标识可以访问它。在 .NET Framework 的早期版本中,您只能授权一个标识访问代码。但现在可允许多个标识进行访问,因为添加的新安全性操作允许将一个权限属性的多个实例应用于一个实体。表 7-2 列出一些安全性操作以及何时使用这些操作。
Secutil.exe 命令行工具
将 StrongNameIdentityPermissionAttribute应用于一个类型时,需要能够指定可以使用什么标识。这通过提供授权标识的公钥完成。可以使用 Secutil.exe 命令行工具从强名称程序集提取公钥。Secutil.exe 和 .NET Framework SDK 一起安装,并且可以在 Program Files\Microsoft Visual Studio .NET Whidbey\SDK\v2.0\Bin 中找到它。此工具允许您从已编译的程序集中检索强名称和证书信息。有许多选项可用于指定返回信息的格式。StrongNameIdentityPermissionAttribute类要求以十六进制字符串的格式提供公钥。下列命令行指令从一个名为 foo.dll 的强名称程序集中以十六进制字符串的格式检索公钥。
secutil -hex -s foo.dll
然后,可以将结果字符串复制到代码或配置文件中。
| 表 7-2 框架安全性操作 | |
| 名称 | 说明 | 
| Demand | 调用堆栈中的所有调用方都必须通过权限检查。 | 
| DemandChoice(新) | 可以应用权限的多个实例。调用堆栈中的所有调用方必须至少通过其中的一项权限检查。 | 
| LinkDemand | 直接调用方必须通过权限检查。 | 
| LinkDemandChoice(新) | 可以应用权限的多个实例。直接调用方必须至少通过其中的一项权限检查。 | 
GAC 标识权限
有时候您可能对调用方的特定标识不感兴趣,但是您希望确保调用代码来自全局程序集缓存 (GAC) 中的程序集。一个已添加的新权限 GACIdentityPermission 正好可以解决这一需求。GACIdentityPermission类可应用于程序集、类或成员,并验证调用是否源自全局程序集缓存。下列代码显示如何将此权限应用于类声明:
<GacIdentityPermission(SecurityAction.LinkDemand)> _ Public Class Foo End Class
该权限指定 LinkDemand安全性操作,该操作要求只有直接调用方才能安装在程序集缓存中。如果您希望调用堆栈中的所有代码都位于全局程序集缓存中,您就要使用 Demand 安全性操作。
演练
SecurityEnhancements 应用程序演示了如何使用 GACIdentityPermission-Attribute 和 StrongNameIdentityPermissionAttribute类,将其各自的权限应用于您的类型中。此应用程序由七个项目组成。表 7-3 列出了名称、类型、程序集是否具有强名称,以及程序集是否安装在全局程序集缓存中。
| 表 7-3 安全性增强功能项目 | |||
| 名称 | 类型 | 强名称 | 是否安装在 GAC 中? | 
| SecurityEnhancementsUI | EXE | 否 | 否 | 
| ProtectedCode | DLL | 是 | 是 | 
| LocalAssembly | DLL | 否 | 否 | 
| GACAssembly | DLL | 是 | 是 | 
| TrustedAssembly1 | DLL | 是 | 否 | 
| TrustedAssembly2 | DLL | 是 | 否 | 
| UntrustedAssembly | DLL | 是 | 否 | 
SecurityEnhancementsUI 项目提供的用户界面用来测试应用于 ProtectedCode程序集中代码的权限。此界面使用 LocalAssembly和 GACAssembly测试 GACIdentityPermission,并且使用 TrustedAssembly1,TrustedAssembly2 和 UntrustedAssembly测试 StrongNameIdentityPermission。接下来的演练将详细描述其中的每个测试,并对每个测试的成败作出解释。在可以运行应用程序之前,需要将 ProtectedCode.dll 和 GACAssembly.dll 程序集安装在全局程序集缓存中。实现该操作最简单的方法是打开 Windows 资源管理器的两个实例。其中一个实例定位到 SecurityEnhancements\SecurityEnhancementsUI\bin。另一个实例定位到 Windows\assembly。将 ProtectedAssembly.dll 和 GACAssembly.dll 从 SecurityEnhancementsUI\bin 复制到 Windows\assembly。现在这些程序集已安装在 GAC 中。此位置是测试 GACIdentityPermission 所必需的。
强制执行 GACIdentityPermission
在 SecurityEnhancementsUI 项目中,MainForm窗体包含两个用于测试 GACIdentityPermission 的按钮。btnLocalCall_Click和 btnGacCAll_Click事件处理程序启动调用,并最终导致对 ProtectedCode程序集中 GACIdentityTest类的调用。该类受一个 GACIdentityPermission属性实例的保护,该实例要求将直接调用方安装在全局程序集缓存中:
Imports System.Security.Permissions <GacIdentityPermission(SecurityAction.LinkDemand)> _ Public Class GACIdentityTest Public Function DoOperation() End Function End Class
btnLocalCall_Click事件处理程序使用一个 LocalAssembly.LocalClass的实例执行调用,而 btnGacCall_Click事件处理程序使用一个 GACAssembly.ClassInGAC 的实例执行调用。来自 LocalAssembly.LocalClass的调用会产生一个 LocalAssembly.LocalClass,因为该程序集没有安装在全局程序集缓存中。但是,来自 GACAssembly.ClassInGAC的调用是成功的,因为它安装在全局程序集缓存中。如果您将 GACAssembly从全局程序集缓存中移除,您会发现这次调用也将失败。
强制执行 StrongNameIdentityPermission
btnTrustedIdentity1_Click、btnTrustedIdentity2_Click 和 btnUntrustedIdentity_Click事件处理程序用于启动调用,并最终导致对 ProtectedCode程序集中 StrongNameIdentityTest类的调用。该类受 StrongNameIdentityPermission属性两个实例的保护。每个实例指定一个已授权的公钥标识,以执行对该类的调用。使用 Secutil.exe 工具从 TrustedAssembly1.dll 和 TrustedAssembly2.dll 程序集中提取这些密钥。UntrustedAssembly.dll 也具有强名称,但是它不受 StrongNameIdentityTest 类的信任。
<StrongNameIdentityPermission( _ SecurityAction.LinkDemandChoice, _ PublicKey:="002400000480000094000000060200000024000052534131000400000100010049AAFA961210D12A C3B569DD010A733B24A6D44C980585EE22608CB2D30379CBA61970ECBEAC7D84C25AF3BF8635A1994DE4DC2 BE4E4EEA1012EC514763C6C89FBB5A6F290B65B4E9CEFF94F3EECD6E9E9D429D2410301D0E18679AB0C03BF49 EA7E3B8392A5CEC7EAC139FF7E593C10FD4FE70CC2C3E51BA2B680CD”)> _ <StrongNameIdentityPermission( _ SecurityAction.LinkDemandChoice, _ PublicKey:="0024000004800000940000000602000000240000525341310004000001000100B78C8D572199C64E C19B8DD7D44C73F6248436F8D159F41B9D692565640D2BA0C3D354DE3FD2A41B4CDF07BEDC131D15C12965F3ECF8 AE8D1DDCCF85961BF7565CB339C80688244119C5E4160301F44383D71724CE0679E5CC9135D1C0C11F67BD6129 F30222706C4233089086036A130C412B6FABEEB94A6B778F039BFC1”)> _ Public Class StrongNameIdentityTest Public Sub DoOperation() End Sub End Class
值得注意的是,这两个属性实例使用的是 LinkDemandChoice操作,而不是 LinkDemand操作。使用 LinkDemandChoice允许您指定多个标识。btnTrustedIdentity1_Click和 btnTrustedIdentity2_Click事件处理程序使用 TrustedAssembly1和 TrustedAssembly2程序集中的类来调用受保护的类。这些调用都是成功的,因为它们的每个标识均与某个授权标识相匹配。但是,btnUntrustedAssembly_Click事件处理程序使用的是未授权的 UntrustedAssembly程序集中的类,因此调用失败。如果想要进行进一步测试,您可以使用 Secutil.exe 从 UntrustedAssembly.dll 提取公钥,并添加另一个 StrongNameIdentityPermission属性来授权此公钥访问 StrongNameIdentityTest类。
其他改进和添加功能
.NET Framework 2.0 中还有许多其他改进并添加了一些新功能,虽然它们超出了本章的讨论范围,但是的确值得一提。有一项新的支持可以通过文件传输协议 (FTP) 或计算机串行端口发送数据。现在,System.Net 命名空间的 WebRequest/WebResponse 框架通过 FtpWebRequest和 FtpWebResponse类为 FTP 通信提供支持。System.IO.Ports命名空间提供了 SerialPort类,该类支持与设备进行完全的 RS232 通信。此外,Visual Basic .NET 通过 MyServices类使用诸如 MyServices.MyPorts.OpenSerialPort 等方法公开已打开的端口。System.IO.Compression命名空间提供了两个新的流 — DeflateStream和 GZipStream — 来压缩和解压缩数据。在通过网络保持或发送数据之前,这些类允许您方便地对其进行压缩。最后,System.Xml命名空间进行了重要的重新设计,使它在安全性、性能、可用性以及标准兼容性方面有了极大的改进。
注有关对 System.Xml 命名空间进行更改的详细信息,请访问 http: //msdn.microsoft.com/xml/default.aspx?pull=/library/en-us/dnxml/html/sysxmlVS05.asp。
小结
.NET Framework 2.0 提供了许多改善开发人员和最终用户体验的新功能。尤其是泛型集合可以确保大大减少用于开发自定义集合类的时间,而新的类型改进使数据验证更简单、更有效。跟踪源和新跟踪侦听器的添加可以更好地对应用程序跟踪消息的内容和格式进行控制,从而提高了在开发和生产中解决疑难问题的能力。管理工具和其他控制台应用程序的最终用户将从具有更丰富信息的界面中获益,该界面包含了经过改进的定位和颜色格式。最后,通过使用基于标识的权限和分离请求,您可以拥有更灵活的选项来保护您的代码。
 
                    
                     
                    
                 
                    
                 

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号