针对 .NET 框架的安全编码指南.net

时间:2023年08月26日

/

来源:宇宙最甜可琦安

/

编辑:本站小编

收藏本文

下载本文

以下是小编整理的针对 .NET 框架的安全编码指南.net,本文共7篇,欢迎阅读分享,希望对大家有帮助。本文原稿由网友“宇宙最甜可琦安”提供。

篇1:针对 .NET 框架的安全编码指南.net

前提条件:读者应当熟悉公共语言运行库和 Microsoft(R) .NET 框架,以及基于证据的安全性和代码访问安全性的基本知识, 基于证据的安全性和代码访问安全性 结合使用两项单独的技术来保护托管代码: #8226; 基于证据的安全性决定将什么权限授予代码。 #8226;

前提条件:读者应当熟悉公共语言运行库和 Microsoft(R) .NET 框架,以及基于证据的安全性和代码访问安全性的基本知识。

基于证据的安全性和代码访问安全性

结合使用两项单独的技术来保护托管代码:

• 基于证据的安全性决定将什么权限授予代码。

• 代码访问安全性负责检查堆栈上的所有代码是否拥有执行某项操作的所需权限。

权限将这两个技术绑定在一起:权限是执行某个特定受保护操作的权利。例如,“读取 c:\\temp”是一个文件权限;“连接到 www.msn.com”是一个网络权限。

基于证据的安全性决定授予代码的权限。证据是有关用作安全策略机制输入的任何程序集(授予权限的单位)的已知信息。假如将证据作为输入,系统将评估由管理员设置的安全策略,以决定可以将什么权限授予代码。代码本身可以使用权限请求来影响被授予的权限。权限请求被表示为使用自定义属性语法的程序集级别的声明性安全性。但是,代码不能以任何方式获取多于或少于策略系统所允许的权限。权限授予只发生一次,指定程序集中所有代码的权利。要查看或编辑安全策略,请使用 .NET 框架配置工具 (Mscorcfg.msc)。

下表列出了策略系统用来向代码授予权限的某些常见证据类型。除了这里列出的标准证据类型(它们是由安全系统提供的)以外,还可以使用用户定义的新类型来扩展证据集合。

证据 说明

哈希值

程序集的哈希值

出版商

AuthentiCode(R) 签名者

强名称

公钥+名称+版本

站点

代码来源的 Web 站点

Url

代码来源的 URL

区域

代码来源的 Internet Explorer 区域

代码访问安全性负责处理执行所授予权限的安全检查。这些安全检查的独特方面是,它们不仅会检查试图执行受保护操作的代码,而且会沿堆栈检查它的所有调用方。要让检查获得成功,所有被检查的代码都必须具有所需权限(可以重写)。

安全检查是有好处的,因为它们可以防止引诱攻击。引诱攻击是指未经授权的代码调用您的代码,并引诱您的代码代替未经授权的代码执行某些操作。假设您有一个读取文件的应用程序,并且安全策略将读取文件的权限授予了您的代码。因为您的所有应用程序代码都拥有权限,所以会通过代码访问安全性检查。但是,如果无权访问文件的恶意代码以某种方式调用了您的代码,那么安全检查将失败,这是因为不受信任的代码调用了您的代码,从而在堆栈上可见。

需要注意的是,该安全性的所有方面都是基于允许代码执行什么操作这一机制的。基于登录信息对用户进行授权是基础操作系统完全独立的安全功能。请将这两个安全系统看作多层防御:例如,要访问一个文件,必须要通过代码授权和用户授权。虽然在许多依赖用户登录信息或其他凭据来控制某些用户可以和不可以执行某项操作的应用程序中,用户授权很重要,但这种类型的安全不是本文讨论的重点。

安全编码的目标

我们假设安全策略是正确的,并且潜在的恶意代码不具有授予信任代码的权限,该权限允许受信任代码安全地执行功能更强大的操作。(如果进行其他假设,将使一种类型的代码无法与其他类型的代码区分开,从而使问题不会发生。)使用强制 .NET 框架的权限和代码中实施的其他措施时,您必须建立障碍来防止恶意代码获得您不希望它得到的信息,或防止它执行恶意的操作。此外,在受信任代码的所有预期情况中,必须在代码的安全性和可用性之间找到一种平衡。

基于证据的安全策略和代码访问安全性为实现安全性提供了非常强大的显式机制。大多数应用程序代码只需要使用 .NET 框架实现的基础结构。在某些情况下,还需要其他特定于应用程序的安全性,该安全性是通过扩展安全系统或使用新的特殊方法生成的。

安全编码的方法

这些安全技术的一个优点是,您通常可以忘记它们的存在。如果将代码执行任务所需的权限授予代码,那么一切将正常工作(同时,您将能够抵御潜在的攻击,例如,前面描述的引诱攻击)。但是,在某些特定情况下,您必须显式地处理安全问题。下面的几个部分描述了这些方法。即使这些部分并不直接适用于您,但了解这些安全问题总是有用的。

安全中立代码

安全中立代码不对安全系统执行任何显式操作。它只使用获得的权限来运行。尽管无法捕获受保护操作(例如,使用文件、网络等)的安全异常会导致不良的用户体验(这是指包含许多细节的异常,但对大多数用户来说这些细节是完全模糊的),但这种方式利用了安全技术,因为即使是高度受信任的代码也无法降低安全保护的程度。可能发生的最坏情况是,调用方将需要许多权限,否则会被安全机制禁止运行。

安全中立库具有您应当了解的特殊特征。假设此库提供的 API 元素需要使用文件或调用非托管代码;如果您的代码没有相应的权限,将无法按描述运行。但是,即使该代码拥有权限,调用它的任何应用程序代码也必须拥有相同的权限才能运行。如果呼叫代码没有正确的权限,那么安全异常将作为代码访问安全性堆栈审核的结果出现。如果可以要求调用方对库所执行的所有操作都拥有权限,那么这将是实现安全性的简单且安全的方式,因为它不涉及危险的安全重写。但是,如果想让调用库的应用程序代码不受权限要求的影响,并减少对非常强大的权限的需要,您必须了解与受保护资源配合工作的库模型,这部分内容将在本文的“公开受保护资源的库代码”部分中进行描述。

不属于可重用组件的应用程序代码

如果您的代码是不会被其他代码调用的应用程序的一部分,那么安全性很简单,并且可能不需要特殊的编码。但请记住,恶意代码可以调用您的代码。虽然代码访问安全性机制可以阻止恶意代码访问资源,但此类恶意代码仍然可以读取可能包含敏感信息的字段或属性的值。

此外,如果您的代码可以从 Internet 或其他不可靠的来源接受用户输入,则必须小心恶意输入。

有关详细信息,请参阅本文的确保状态数据的安全和用户输入。

本机代码实现的托管包装程序

通常在此情况下,某些有用的功能是在本机代码中实现的,并且您想在不改写它的情况下将其用于托管代码。托管包装程序很容易编写为平台调用或使用 COM 互操作。但是,如果您这样做,包装程序的调用方必须拥有非托管代码的权利,调用才能成功。在默认策略下,这意味着从 Intranet 和 Internet下载的代码将不会与包装程序配合工作。

更好的方法是将这些非托管代码权利只授予包装程序代码,而不要授予使用这些包装程序的所有应用程序。如果基础功能很安全(不公开任何资源)并且实现也很安全,则包装程序只需要断言它的权利,这将使任何代码都能够通过它进行调用。如果涉及到资源,则安全编码应与下一部分中描述的库代码情况相同。因为包装程序向调用方潜在地公开了这些问题,所以需要对本机代码的安全性进行仔细验证,这是包装程序的责任。

有关详细信息,请参阅本文的非托管代码和评估权限部分。

公开受保护资源的库代码

这是功能最强大因此也是潜在最危险(如果方法不正确)的安全编码方式:您的库充当了其他代码用来访问特定资源(这些资源以其他方式是不可用的)的接口,正如 .NET 框架类对它们所使用的资源施加权限一样。无论在哪里公开资源,代码都必须先请求适用于资源的权限(即,执行安全检查),然后通常需要断言它执行实际操作的权利。

有关详细信息,请参阅本文的非托管代码和评估权限部分。

安全编码的最佳做法

注 除非另行指定,代码示例都是用 C# 编写的。

权限请求是使代码获得安全性的好方法。这些请求可让您做两件事:

• 请求代码运行所必需的最低权限。

• 确保代码所接收的权限不会超过它实际需要的权限。

例如:

[assembly:FileIOPermissionAttribute

(SecurityAction.RequestMinimum, Write=“C:\\\\test.tmp”)]

[assembly:PermissionSet

(SecurityAction.RequestOptional, Unrestricted=false)]

…SecurityAction.RequestRefused_

该示例告诉系统:除非代码收到写入 C:\\test.tmp 的权限,否则不应当运行。如果代码遇到没有授予该权限的安全策略,那么将引发 PolicyException,并且代码不会运行。您可以确保代码将被授予该权限,并且不必担心由于权限太少而导致的错误。

该示例还告诉系统:不需要其他权限。除此之外,代码将被授予策略选择要授予它的任何权限。虽然额外的权限不会导致损害,但如果某处有安全问题,则拥有较少的权限可以很好地堵住漏洞。带有代码不需要的权限会导致安全问题。

将代码所接收的权限限制为最少特权的另一个方式是列出要拒绝的特定权限。如果您要求所有权限都是可选的,并从该请求中排除特定权限,那么权限通常会被拒绝。

确保状态数据的安全

处理敏感数据或作出任何安全决定的应用程序需要使该数据处于自己的控制下,并且不能让其他的潜在恶意代码直接访问该数据。使数据安全地保留在内存中的最佳方式是将其定义为私有或内部(限制在同一程序集的范围内)变量。但是,此数据也服从于您应当知道的访问权:

• 在反射时,引用了对象的高度受信任代码可以获得并设置私有成员。

• 使用序列化时,如果高度受信任的代码可以通过对象的序列化形式访问相应数据,那么它就可以有效地获得和设置私有成员。

• 在调试时,可以读取该数据。

确保自己的任何方法或属性都没有无意地公开这些值。

在某些情况下,数据可以使用“protected”加以保护,这时,只能访问该类及其派生类。但是,由于存在其他公开的可能性,您还应当采取下面的预防措施:

• 通过将类的派生限制在同一个程序集内,或使用声明性安全来要求某些标识或权限以便从您的类中派生,从而控制允许哪些代码从您的类中派生(请参阅本文的确保方法访问的安全部分)。

• 确保所有派生类都实现了相似的保护或者被密封。

装箱的值类型

如果您认为已经分发了无法修改原始定义的类型的副本,有时还可以修改装箱的值类型。返回装箱的值类型时,您所返回的是对值类型的引用,而不是对值类型副本的引用,因而允许调用您代码的代码修改您的变量值。

以下 C# 代码示例显示了如何使用引用来修改装箱的值类型。

using System;

using System.Reflection;

using System.Reflection.Emit;

using System.Threading;

using System.Collections;

class bug {

// Suppose you have an API element that exposes a

// field through a property with only a get aclearcase/“ target=”_blank“ >ccessor.

public object m_Property;

public Object Property {

get { return m_Property;}

set {m_Property = value;} // (if applicable)

}

// You can modify the value of this by doing

// the byref method with this signature.

public static void m1( ref int j ) {

j = Int32.MaxValue;

}

public static void m2( ref ArrayList j )

{

j = new ArrayList;

}

public static void Main(String[] args)

{

Console.WriteLine( ”////// doing this with value type“ );

{

bug b = new bug();

b.m_Property = 4;

Object[] bjArr = new Object[]{b.Property};

Console.WriteLine( b.m_Property );

typeof(bug).GetMethod( ”m1“ ).Invoke( null, objArr );

// Note that the property changed.

Console.WriteLine( b.m_Property );

Console.WriteLine( objArr[0] );

}

Console.WriteLine( ”////// doing this with a normal type“ );

{

bug b = new bug();

ArrayList al = new ArrayList();

al.Add(”elem“);

b.m_Property = al;

Object[] bjArr = new Object[]{b.Property};

Console.WriteLine( ((ArrayList)(b.m_Property)).Count );

typeof(bug).GetMethod( ”m2“ ).Invoke( null, objArr );

// Note that the property does not change.

Console.WriteLine( ((ArrayList)(b.m_Property)).Count );

Console.WriteLine( ((ArrayList)(objArr[0])).Count );

}

}

}

确保方法访问的安全

某些方法可能不适合由不受信任的任意代码调用它们。此类方法会导致几个风险:方法可能会提供某些受限制信息;可能会相信传递给它的任何信息;可能不会对参数进行错误检查;或者,如果参数错误,可能会出现故障或执行某些有害操作。您应当注意这些情况,并采取适当的操作来确保方法的安全。

在某些情况下,您可能需要限制不打算公开使用、但仍必须是公共的方法。例如,您可能有一个需要在自己的 DLL 之间进行调用的接口,因此它必须是公共的,但您不想公开它,以防止用户使用它或防止恶意代码利用它作为入口点进入到您的组件中。对不打算公共使用(但仍必须是公共)的方法进行限制的另一个常见理由是,避免用文档记录和支持非常内部的接口。

托管代码为限制方法访问提供了几个方式:

• 将可访问性的作用域限制到类、程序集或派生类(如果它们是可信任的)。这是限制方法访问的最简单方式。请注意,通常派生类的可信赖度比它们派生自的类更低,但在某些情况下,它们可以共享超类标识。特别是,不要从关键字 protected 推断信任情况,因为在安全上下文中,该关键字不是必须使用的。

• 将方法访问限制到指定标识(实质上,是您选择的任何特殊证据)的调用方。

• 将方法访问限制到拥有您所选权限的调用方。

同样,声明性安全也允许您控制类的继承。您可以使用 InheritanceDemand 执行以下操作:

• 要求派生类拥有指定的标识或权限。

• 要求重写特定方法的派生类拥有指定的标识或权限。

示例:保护对类或方法的访问

以下示例显示了如何确保公共方法的安全,以限制访问。

• sn -k 命令用于创建新的私钥/公钥对。私钥部分需要使用强名称签署代码,并由代码发行者安全地保留。(如果泄露,任何人都可以在他们的代码上假冒您的签名,从而使保护措施失效。)

通过强名称标识来确保方法的安全

sn -k keypair.dat

csc/r:App1.dll /a.keyfile:keypair.dat App1.cs

sn -p keypair.dat public.dat

sn -tp public.dat >publichex.txt

[StrongNameIdentityPermissionAttribute

(SecurityAction.LinkDemand,

PublicKey=”_“,hex_”,Name=“App1”,

Version=“0.0.0.0”)]

public class Class1

• csc 命令用于编译和签署 App1,以授于它访问受保护方法的权限。

• 后面的两个 sn 命令用于从密钥对中提取公钥部分,并将它格式化为十六进制。

• 示例的下半部分是摘录的受保护方法的源代码。自定义属性可定义强名称,指定密钥对中的公钥,并插入从 sn 得到的十六进制格式的数据作为 PublicKey 属性。

• 在运行时,由于 App1 拥有必需的强名称签名,所以被允许使用 Class1。

该示例使用 LinkDemand 来保护 API 元素;有关使用 LinkDemand 的限制的重要信息,请参阅本文后面的部分。

防止不受信任的代码使用类和方法

使用以下声明可以防止部分信任的代码使用类和方法(包括属性和事件)。通过将这些声明应用于类,可以对该类的所有方法、属性和事件应用保护;但请注意,字段访问不会受到声明性安全的影响。请注意,链接请求只保护直接调用方,并且仍可能会受到引诱攻击(本文的基于证据的安全性和代码访问安全性部分对此进行了描述)。

具有强名称的程序集将声明性安全应用于所有可公开访问的方法、属性和事件,因此只有完全受信任的调用方才能使用它们,除非程序集通过应用 AllowPartiallyTrustedCallers 属性显式地决定参与使用。因此,通过显式地标记类来排除不受信任的调用方,只对未签名的程序集或具有该属性的程序集、以及原本就不打算用于不受信任调用方的类型的子集才是必需的。有关全部详细信息,请参阅 Microsoft .NET 框架的第 1 版安全更改文档。

• 对于公共的非密封类:

[System.Security.Permissions.PermissionSetAttribute(System.Security.

Permissions.SecurityAction.InheritanceDemand, Name=“FullTrust”)]

[System.Security.Permissions.PermissionSetAttribute

(System.Security.Permissions.SecurityAction.LinkDemand, Name=“FullTrust”)]

public class CanDeriveFromMe

• 对于公共的密封类:

[System.Security.Permissions.PermissionSetAttribute

(System.Security.Permissions.SecurityAction.LinkDemand, Name=“FullTrust”)]

public sealed class CannotDeriveFromMe

• 对于公共的抽象类:

[System.Security.Permissions.PermissionSetAttribute

(System.Security.Permissions.SecurityAction.InheritanceDemand, Name=“FullTrust”)]

[System.Security.Permissions.PermissionSetAttribute

(System.Security.Permissions.SecurityAction.LinkDemand, Name=“FullTrust”)]

public abstract class CannotCreateInstanceOfMe_CanCastToMe

• 对于公共的虚拟函数:

class Base {

[System.Security.Permissions.PermissionSetAttribute

(System.Security.Permissions.SecurityAction.InheritanceDemand,

Name=“FullTrust”)]

[System.Security.Permissions.PermissionSetAttribute(

System.Security.Permissions.SecurityAction.LinkDemand,

Name=“FullTrust”)]

public override void CanOverrideOrCallMe() { ... }

• 对于公共的抽象函数:

class Base {

[System.Security.Permissions.PermissionSetAttribute

(System.Security.Permissions.SecurityAction.InheritanceDemand, Name=“FullTrust”)]

[System.Security.Permissions.PermissionSetAttribute

(System.Security.Permissions.SecurityAction.LinkDemand,

Name=“FullTrust”)]

public override void CanOverrideMe() { ... }

• 对于基函数不需要完全信任的公共重写函数:

class Derived {

[System.Security.Permissions.PermissionSetAttribute

(System.Security.Permissions.SecurityAction.Demand, Name=“FullTrust”)]

public override void CanOverrideOrCallMe() { ... }

• 对于基函数需要完全信任的公共重写函数:

class Derived {

[System.Security.Permissions.PermissionSetAttribute

(System.Security.Permissions.SecurityAction.LinkDemand,

Name=“FullTrust”)]

public override void CanOverrideOrCallMe() { ... }

• 对于公共接口:

[System.Security.Permissions.PermissionSetAttribute

(System.Security.Permissions.SecurityAction.InheritanceDemand,

Name=“FullTrust”)]

[System.Security.Permissions.PermissionSetAttribute

(System.Security.Permissions.SecurityAction.LinkDemand,

Name=“FullTrust”)]

public interface CanCastToMe

Demand 与 LinkDemand

声明性安全提供了两种类似的、但检查方式大不相同的安全检查,

花些时间了解这两种形式是值得的,因为错误的选择会导致脆弱的安全性或性能损失。本部分并不打算完整地说明这些功能;有关完整的详细信息,请参阅产品文档。

声明性安全可提供以下安全检查:

• Demand 指定代码访问安全性的堆栈审核:堆栈上的所有调用方都必须拥有权限或标识才能通过。Demand 会发生在每个调用上,这是因为堆栈可能包含不同的调用方。如果您重复调用某个方法,则每次都会进行该安全检查。Demand 对引诱攻击具有强大的抵御能力;它可捕获试图通过它的未经授权的代码。

• LinkDemand 发生在实时 (JIT) 编译时(在前面的示例中,当引用 Class1 的 App1 代码将要执行时),并且它只检查直接调用方。该安全检查不检查调用方的调用方。一旦通过该检查,无论它被调用多少次,都不会有其他安全开销。但是,它无法防御引诱攻击。如果使用 LinkDemand,则您的接口是安全的,但通过测试并且可以引用您的代码的任何代码都可以潜在地破坏安全性,因为它们允许使用授权代码调用恶意代码。因此,除非可以完全避免所有可能的弱点,否则不要使用 LinkDemand。

使用 LinkDemand 时所需的额外预防措施必须是“手工制订的”(安全系统可以帮助实施)。任何错误都会导致安全漏洞。使用您代码的所有经授权代码都必须通过执行以下操作,来负责实现其他安全性:

• 将呼叫代码的访问权限制到类或程序集。

• 为该代码设置相同的安全检查,并强制它的调用方这样做。例如,如果您编写的代码调用了某个方法,而该方法通过对 SecurityPermission.UnmanagedCode 权限使用 LinkDemand 获得保护,那么您的方法也应当对该权限使用 LinkDemand(或 Demand,这是更强的手段)。例外情况是,假如代码中有其他安全保护机制(例如,Demand),则您的代码将以一种总是安全的或您决定是安全的受限方式使用 LinkDemand 所保护的方法。在调用方负责削弱对基础代码的安全保护情况下,会出现此例外情况。

• 确保它的调用方无法进行欺骗,以代表它去调用受保护的代码(也就是说,调用方不能强迫经授权的代码将特定参数传递给受保护的代码,或从中获得结果)。

接口和 LinkDemands

如果具有 LinkDemand 的虚拟方法、属性或事件重写了某个基类方法,则此基类方法也必须具有相同的 LinkDemand,以便重写方法也是安全的。恶意代码可能会强制转换回基础类型,并调用基类方法。还要注意,可以将 LinkDemands 隐式地添加到不具有 AllowPartiallyTrustedCallersAttribute 程序集级属性的程序集中。

好的做法是,当接口方法也有 LinkDemands 时,使用 LinkDemands 对方法实现进行保护。

关于对接口使用 LinkDemands,请注意以下事项:

• AllowPartiallyTrustedCallers 属性会影响接口。

• 可以将 LinkDemands 放在接口上,以有选择地挑出某些接口,使其不能由部分信任的代码使用(例如,在使用 AllowPartiallyTrustedCallers 属性时)。

• 如果在不包含 AllowPartiallyTrustedCallers 属性的程序集中定义接口,则可以在部分信任的类上实现该接口。

• 如果将 LinkDemand 放在一个实现接口方法的类的公共方法上,在随后强制转换到该接口并调用该方法时,将不会执行 LinkDemand。在这种情况下,因为是对接口进行链接,所以只考虑接口上的 LinkDemand。

应当审阅以下各项是否有安全问题:

• 接口方法上的显式链接请求。确保这些链接请求提供了预期的保护。确定恶意代码是否可以使用强制转换来避开前面描述的链接请求。

• 具有链接请求的虚拟方法。

• 它们实现的类型和接口应当一致地使用 LinkDemands。

虚拟内部重写

在确认代码对其他程序集不可用时,需要了解类型系统可访问性的细微差别。声明 virtual 和 internal 的方法可以重写超类的 vtable 条目,并且只能在同一个程序集的内部使用,因为它是内部的。但是,重写的可访问性是由 virtual 关键字决定的,并且只要代码能够访问类本身,就可以从另一个程序集对该可访问性进行重写。如果重写的可能性比较小,请使用声明性安全解决它,或者删除 virtual 关键字(如果它不是必需的)。

包装程序代码

包装程序代码(特别是在包装程序比使用它的代码具有更高可信度时)可以显露一组独特的安全漏洞。如果没有将调用方的受限制权限包括在适当的安全检查中,则代表调用方所执行的任何操作都是可能被利用的潜在漏洞。

不要通过包装程序来启用调用方本身无法执行的某些操作。在执行某些涉及受限制安全检查的操作时(与完整的堆栈审核请求相反),会有特殊的危险性。涉及到单一级别的检查时,在实际调用方与可疑 API 元素之间插入包装程序代码,可以很容易地使安全检查在不应当成功时成功通过,从而降低了安全性。

委托

无论何时,如果您的代码从可能调用它、但信任度较低的代码那里取得委托权,请确保您不会让信任度较低的代码提升它的权限。如果您取得委托权并随后使用它,那么,如果委托中或委托下面的代码试图执行受保护的操作,则创建委托的代码将不会在调用堆栈中,并且不会测试它的权限。如果您的代码和委托代码具有比调用方更高的特权,这将使调用方能够在不成为调用堆栈一部分的情况下改变调用路径。

要解决该问题,可以限制调用方(例如,要求它具有某个权限)或对执行委托的权限加以限制(例如,通过使用 Deny 或 PermitOnly 堆栈重写)。

LinkDemands 和包装程序

在安全基础结构中,已经加强了对链接请求的特殊保护措施,但它仍然是代码中可能的漏洞来源。

如果完全受信任的代码调用某个由 LinkDemand 保护的属性、事件或方法,那么,如果对调用方的 LinkDemand 权限检查获得通过,则调用将成功。此外,如果完全受信任的代码所公开的类使用了某个属性的名称,并使用反射来调用该属性的 get 访问器,那么,即使用户代码无权访问该属性,对 get 访问器的调用也会成功。这是因为 LinkDemand 将只检查直接调用方,而直接调用方是完全受信任的代码。其实,完全受信任的代码在代表用户代码执行经授权的调用时,不需要确保用户代码有权进行该调用。如果您要包装反射功能,请参阅 Microsoft .NET 框架的第 1 版安全更改一文,以获得详细信息。

为了防止因疏忽造成的安全漏洞(例如,上面描述的情况),运行时将对任何使用调用的操作(实例创建、方法调用、属性设置或获得)所进行的完整堆栈审核请求检查扩展到由链接请求保护的方法、构造函数、属性或事件。该保护会导致一些性能成本(单级 LinkDemand 速度更快),并且会更改安全检查的语义 ― 即使单级检查已通过,完整堆栈审核请求也可能会失败。

程序集加载包装程序

用来加载托管代码的几个方法(包括 Assembly.Load(byte[])),可使用调用方的证据加载程序集。具体来说,如果要包装这些方法中的任意一个,安全系统可以使用您代码被授予的权限(而不是调用方对包装程序的权限)来加载程序集。显然,您并不想允许信任度较低的代码指使您代表它加载其所获权限比调用方对包装程序的权限更高的代码。

拥有完全信任或信任度比潜在调用方(包括 Internet 权限级调用方)高得多的任何代码很容易通过这种方式降低安全性。如果您代码包含的公共方法可以取得字节数组并将其传递给 Assembly.Load(byte[]),从而代表调用方创建程序集,则可能会破坏安全性。

该问题会发生在以下 API 元素上:

• System.AppDomain.DefineDynamicAssembly

• System.Reflection.Assembly.LoadFrom

• System.Reflection.Assembly.Load

异常处理

在运行任何 finally 语句之前,将运行堆栈上面的一个筛选表达式。在运行 finally 语句之后,将运行与该筛选器关联的 catch 代码块。请考虑以下伪代码:

void Main() {

try {

Sub();

} except (Filter()) {

Console.WriteLine(“catch”);

}

}

bool Filter () {

Console.WriteLine(“filter”);

return true;

}

void Sub() {

try {

Console.WriteLine(“throw”);

throw new Exception();

} finally {

Console.WriteLine(“finally”);

}

}

该代码将打印以下内容:

Throw

Filter

Finally

Catch

筛选器将在 finally 语句之前运行,这样,如果可以执行其他代码,则造成状态更改的任何操作都可以带来安全问题。例如:

try {

Alter_Security_State();

// This means changing anything (state variables,

// switching unmanaged context, impersonation, and so on)

// that could be exploitable if malicious code ran before state is restored.

Do_some_work();

}

finally {

Restore_Security_State();

// This simply restores the state change above.

}

该伪代码允许筛选器备份堆栈以运行任意代码。具有类似效果的其他操作示例是对另一个标识的临时模拟,这样会设置一个避开某个安全检查的内部标志,并更改与线程关联的区域性等等。

建议的解决方案是引入异常处理程序,将代码的线程状态更改与调用方的筛选器块隔离开。但重要的是,要正确引入异常处理程序,否则将无法解决该问题。下面的 Microsoft Visual Basic(R) 示例将切换 UI 区域性,但可以类似地公开任何线程状态更改。

YourObject.YourMethod()

{

CultureInfo saveCulture = Thread.CurrentThread.CurrentUICulture;

try {

Thread.CurrentThread.CurrentUICulture = new CultureInfo(“de-DE”);

// Do something that throws an exception.

}

finally {

Thread.CurrentThread.CurrentUICulture = saveCulture;

}

}

Public Class UserCode

Public Shared Sub Main()

Try

Dim obj As YourObject = new YourObject

obj.YourMethod()

Catch e As Exception When FilterFunc

Console.WriteLine(“An error occurred: '{0}'”, e)

Console.WriteLine(“Current Culture: {0}”,

Thread.CurrentThread.CurrentUICulture)

End Try

End Sub

Public Function FilterFunc As Boolean

Console.WriteLine(“Current Culture: {0}”, Thread.CurrentThread.CurrentUICulture)

Return True

End Sub

End Class

在此例中,正确的修复措施是在 try/catch 块中包装现有的 try/finally 块。只将 catch-throw 子句引入现有的 try/finally 块中将无法解决问题:

YourObject.YourMethod()

{

CultureInfo saveCulture = Thread.CurrentThread.CurrentUICulture;

try {

Thread.CurrentThread.CurrentUICulture = new CultureInfo(“de-DE”);

// Do something that throws an exception.

}

catch { throw; }

finally {

Thread.CurrentThread.CurrentUICulture = saveCulture;

}

}

这不会解决问题,因为在 FilterFunc 获得控制权之前尚未运行 finally 语句。

以下代码通过确保在根据调用方的异常筛选块提供异常之前执行 finally 子句,来解决该问题。

YourObject.YourMethod()

{

CultureInfo saveCulture = Thread.CurrentThread.CurrentUICulture;

try {

try {

Thread.CurrentThread.CurrentUICulture = new CultureInfo(“de-DE”);

// Do something that throws an exception.

}

finally {

Thread.CurrentThread.CurrentUICulture = saveCulture;

}

}

catch { throw; }

}

共2页: 1 [2] 下一页

原文转自:www.ltesting.net

篇2:STL实践指南.net

译者注 这是一篇指导您如何在Microsoft Visual Studio下学习STL并进行实践的文章,这篇文章从STL的基础知识讲起,循序渐进,逐步深入,涉及到了STL编写代码的方法、STL代码的编译和调试、命名空间(namespace)、STL中的ANSI / ISO字符串、各种不同类型的容

译者注

这是一篇指导您如何在Microsoft Visual Studio下学习STL并进行实践的文章。这篇文章从STL的基础知识讲起,循序渐进,逐步深入,涉及到了STL编写代码的方法、STL代码的编译和调试、命名空间(namespace)、STL中的ANSI / ISO字符串、各种不同类型的容器(container)、模板(template)、游标(Iterator)、算法(Algorithms)、分配器(Allocator)、容器的嵌套等方面的问题,作者在这篇文章中对读者提出了一些建议,并指出了使用STL时应该注意的问题。这篇文章覆盖面广,视角全面。不仅仅适合初学者学习STL,更是广大读者使用STL编程的实践指南。

STL简介

STL (标准模版库,Standard Template Library)是当今每个从事C++编程的人需要掌握的一项不错的技术。我觉得每一个初学STL的人应该花费一段时间来熟悉它,比如,学习STL时会有急剧升降的学习曲线,并且有一些命名是不太容易凭直觉就能够记住的(也许是好记的名字已经被用光了),然而如果一旦你掌握了STL,你就不会觉得头痛了。和MFC相比,STL更加复杂和强大。

STL有以下的一些优点:

可以方便容易地实现搜索数据或对数据排序等一系列的算法;

调试程序时更加安全和方便;

即使是人们用STL在UNIX平台下写的代码你也可以很容易地理解(因为STL是跨平台的)。

背景知识

写这一部分是让一些初学计算机的读者在富有挑战性的计算机科学领域有一个良好的开端,而不必费力地了解那无穷无尽的行话术语和沉闷的规则,在这里仅仅把那些行话和规则当作STLer们用于自娱的创造品吧。

使用代码

本文使用的代码在STL实践中主要具有指导意义。

一些基础概念的定义

模板(Template)――类(以及结构等各种数据类型和函数)的宏(macro)。有时叫做甜饼切割机(cookie cutter),正规的名称应叫做范型(generic)――一个类的模板叫做范型类(generic class),而一个函数的模板也自然而然地被叫做范型函数(generic function)。

STL――标准模板库,一些聪明人写的一些模板,现在已成为每个人所使用的标准C++语言中的一部分。

容器(Container)――可容纳一些数据的模板类。STL中有vector,set,map,multimap和deque等容器。

向量(Vector)――基本数组模板,这是一个容器。

游标(Iterator)――这是一个奇特的东西,它是一个指针,用来指向STL容器中的元素,也可以指向其它的元素。

Hello World程序

我愿意在我的黄金时间在这里写下我的程序:一个hello world程序。这个程序将一个字符串传送到一个字符向量中,然后每次显示向量中的一个字符。向量就像是盛放变长数组的花园,大约所有STL容器中有一半是基于向量的,如果你掌握了这个程序,你便差不多掌握了整个STL的一半了。

//程序:vector演示一

//目的:理解STL中的向量

// #include “stdafx.h” -如果你使用预编译的头文件就包含这个头文件

#include // STL向量的头文件。这里没有“.h”。

#include // 包含cout对象的头文件。

using namespace std; //保证在程序中可以使用std命名空间中的成员。

char* szHW = “Hello World”;

//这是一个字符数组,以”\\0”结束。

int main(int argc, char* argv[])

{

vector vec; //声明一个字符向量vector (STL中的数组)

//为字符数组定义一个游标iterator。

vector ::iterator vi;

//初始化字符向量,对整个字符串进行循环,

//用来把数据填放到字符向量中,直到遇到”\\0”时结束。

char* cptr = szHW; // 将一个指针指向“Hello World”字符串

while (*cptr != '\\0')

{ vec.push_back(*cptr); cptr++; }

// push_back函数将数据放在向量的尾部。

// 将向量中的字符一个个地显示在控制台

for (vi=vec.begin(); vi!=vec.end(); vi++)

// 这是STL循环的规范化的开始――通常是 “!=” , 而不是 “<”

// 因为“<” 在一些容器中没有定义。

// begin()返回向量起始元素的游标(iterator),end()返回向量末尾元素的游标(iterator)。

{ cout << *vi; } // 使用运算符 “*” 将数据从游标指针中提取出来。

cout << endl; // 换行

return 0;

}

push_back是将数据放入vector(向量)或deque(双端队列)的标准函数,

Insert是一个与之类似的函数,然而它在所有容器中都可以使用,但是用法更加复杂。end()实际上是取末尾加一(取容器中末尾的前一个元素),以便让循环正确运行――它返回的指针指向最靠近数组界限的数据。就像普通循环中的数组,比如for (i=0; i<6; i++) {ar[i] = i;} ――ar[6]是不存在的,在循环中不会达到这个元素,所以在循环中不会出现问题。

STL的烦恼之一――初始化

STL令人烦恼的地方是在它初始化的时候。STL中容器的初始化比C/C++数组初始化要麻烦的多。你只能一个元素一个元素地来,或者先初始化一个普通数组再通过转化填放到容器中。我认为人们通常可以这样做:

//程序:初始化演示

//目的:为了说明STL中的向量是怎样初始化的。

#include // 相同

#include

using namespace std;

int ar[10] = { 12, 45, 234, 64, 12, 35, 63, 23, 12, 55 };

char* str = “Hello World”;

int main(int argc, char* argv[])

{

vector vec1(ar, ar+10);

vector vec2(str, str+strlen(str));

return 0;

}

在编程中,有很多种方法来完成同样的工作。另一种填充向量的方法是用更加熟悉的方括号,比如下面的程序:

//程序:vector演示二

//目的:理解带有数组下标和方括号的STL向量

#include

#include

#include

using namespace std;

char* szHW = “Hello World”;

int main(int argc, char* argv[])

{

vector vec(strlen(sHW)); //为向量分配内存空间

int i, k = 0;

char* cptr = szHW;

while (*cptr != '\\0')

{ vec[k] = *cptr; cptr++; k++; }

for (i=0; i

{ cout << vec[i]; }

cout << endl;

return 0;

}

这个例子更加清晰,但是对游标(iterator)的操作少了,并且定义了额外的整形数作为下标,而且,你必须清楚地在程序中说明为向量分配多少内存空间。

命名空间(Namespace)

与STL相关的概念是命名空间(namespace)。STL定义在std命名空间中。有3种方法声明使用的命名空间:

1.用using关键字使用这个命名空间,在文件的顶部,但在声明的头文件下面加入:

using namespace std;

这对单个工程来说是最简单也是最好的方法,这个方法可以把你的代码限定在std命名空间中。

2.使用每一个模板前对每一个要使用的对象进行声明(就像原形化):

using std::cout;

using std::endl;

using std::flush;

using std::set;

using std::inserter;

尽管这样写有些冗长,但可以对记忆使用的函数比较有利,并且你可以容易地声明并使用其他命名空间中的成员。

3.在每一次使用std命名空间中的模版时,使用std域标识符。比如:

typedef std::vector VEC_STR;

这种方法虽然写起来比较冗长,但是是在混合使用多个命名空间时的最好方法。一些STL的狂热者一直使用这种方法,并且把不使用这种方法的人视为异类。一些人会通过这种方法建立一些宏来简化问题。

除此之外,你可以把using namespace std加入到任何域中,比如可以加入到函数的头部或一个控制循环体中。

一些建议

为了避免在调试模式(debugmode)出现恼人的警告,使用下面的编译器命令:

#pragma warning(disable: 4786)

另一条需要注意的是,你必须确保在两个尖括号之间或尖括号和名字之间用空格隔开,因为是为了避免同“>>”移位运算符混淆。比如

vector >veclis;

这样写会报错,而这样写:

vector >veclis;

就可以避免错误。

原文转自:www.ltesting.net

篇3:GSM规范中的部分编码转换.net

在做跟手机短信相关的东东时候常遇到各种编码格式数据的转换,特写了几个函数,供参考, function EncodeEnglish(var s:string):string; var i,j,len:Integer; cur:Integer; t:String; begin Result:=''; len:=Length(s); //j 用于移位计数 i:=1;j:=0; while

在做跟手机短信相关的东东时候常遇到各种编码格式数据的转换,特写了几个函数,供参考。

function EncodeEnglish(var s:string):string;

var

i,j,len:Integer;

cur:Integer;

t:String;

begin

Result:='';

len:=Length(s);

//j 用于移位计数

i:=1;j:=0;

while i<=len do

begin

if i

//数据变换

cur:=(ord(s[i]) shr j) or ((ord(s[i+1]) shl (7-j)) and $ff)

else

cur:=(ord(s[i]) shr j) and $7f;

FmtStr(t,'%2.2X',[cur]);

Result:=Result+t;

inc(i);

//移位计数达到7位的特别处理

j:=(j+1) mod 7;

if j=0 then inc(i);

end;

end;

//end;

function BinaryUniCode2Gb2312(ABinaryString:PChar;APosStart,APosEnd:integer):string;

var

i,iLen:integer;

AscHexText,TmpHexStr:string;

AsciiInt:integer ;

AscLen,AscUniLen:integer;

UniHexstr,GB2312:string;

begin

iLen:=Length(ABinaryString);

for i:= APosStart-1 to APosEnd-1 do

begin

AsciiInt:=ord(ABinaryString[i]);

TmpHexStr:=Format('%x',[AsciiInt]);

if length(TmpHexStr)=1 then

TmpHexStr:='0'+TmpHexStr;

AscHexText:=AscHexText+TmpHexStr;

end; //for

AscLen:=Length(AscHexText);

AscUniLen:=AscLen div 4;

for i:=0 to AscUniLen-1 do

begin

UniHexstr:=Copy(AscHexText,i*4+1,4);

// Gb2312 := Gb2312 + UnicodeToGb2312(HexAscii2DecimalInt(uniHexstr));

end; // for

result := Gb2312 ;

end;

function WideStringToUnicode_Ex(s:WideString;ADestStr:PChar;Limit:integer):integer;

var sLen:integer;

buffer:array[1..1024] of char;

tmpchar:char;

i:integer;

begin

sLen:=Length(s);

if sLen>0 then

begin

CopyMemory(@buffer,Pointer(s),2*SLen);

for i:=0 to SLen-1 do

begin

tmpchar:=buffer[2*i+1];

buffer[2*i+1]:=buffer[2*i+2];

buffer[2*i+2]:=tmpchar;

end;

if SLen>Limit then

SLen:=Limit;

CopyMemory(ADestStr,@buffer,2*SLen);

result:=SLen*2;

result:=2*SLen;

end

else

begin

result:=0;

end;

end;

原文转自:www.ltesting.net

篇4:通过禁止使用xpcmdShell提高安全.net

一般的 攻击 SQL Server时,首先采用的方法是执行xp_cmdshell命令来破坏 数据库 ,为了数据库 安全 起见,最好禁止使用xp_cmdShell, xp_cmdshell可以让系统管理员以操作系统命令行解释器的方式执行给定的命令字符串,并以文本行方式返回任何输出,是一个

一般的 攻击SQLServer时,首先采用的方法是执行xp_cmdshell命令来破坏数据库,为了数据库安全起见,最好禁止使用xp_cmdShell。

xp_cmdshell可以让系统管理员以操作系统命令行解释器的方式执行给定的命令字符串,并以文本行方式返回任何输出,是一个功能非常强大的扩展存贮过程,

一般情况下,xp_cmdshell对管理员来说也是不必要的,xp_cmdshell的消除不会对Server造成任何影响。

可以将xp_cmdshell消除:

Use Master

Exec sp_dropextendedproc N’xp_cmdshell’

Go

如果需要的话,可以把xp_cmdshell恢复回来:

Use Master

Exec sp_addextendedproc N’xp_cmdshell’, N’xplog70.dll’

Go

最好把Server的xp_cmdShell存贮过程消除。

原文转自:www.ltesting.net

篇5:一图胜千言:谈框架和架构的区别.net

(节选自《软件架构设计》书稿) 笔者发现,人们对软件架构存在非常多的误解,其中一个最为普遍的误解就是:将架构(Architecture)和框架(Framework)混为一谈, 一图胜千言,图2-7切中肯綮地点出了架构和框架的区别。一句话,框架是软件,架构不是软件。

(节选自《软件架构设计》书稿)

笔者发现,人们对软件架构存在非常多的误解,其中一个最为普遍的误解就是:将架构(Architecture)和框架(Framework)混为一谈。

一图胜千言,图2-7切中肯綮地点出了架构和框架的区别。一句话,框架是软件,架构不是软件。

javascript.:if(this.width>498)this.style.width=498;' nmousewheel = 'javascript.:return big(this)' class=fit-image nmousewheel=“javascript.:return big(this)” alt=“” src=“/files/uploadimg/1030/1059450.gif” nload=“javascript.:if(this.width>560)this.style.width=560;”>

图2-7:架构和框架的区别

框架是一种特殊的软件,它并不能提供完整无缺的解决方案,而是为你构建解决方案提供良好的基础,

框架是半成品。典型地,框架是系统或子系统的半成品;框架中的服务可以被最终应用直接调用,而框架中的扩展点是供应用开发人员定制的“可变化点”。

软件架构不是软件,而是关于软件如何设计的重要决策。软件架构决策涉及到如何将软件系统分解成不同的部分、各部分之间的静态结构关系和动态交互关系等。经过完整的开发过程之后,这些架构决策将体现在最终开发出的软件系统中;当然,引入软件框架之后,整个开发过程变成了“分两步走”,而架构决策往往会体现在框架之中。或许,人们常把架构和框架混为一谈的原因就在于此吧!

来源链接:tb.blog.csdn.net/TrackBack.aspx?PostId=1355814

(责任编辑 火凤凰 sunsj@51cto.com TEL:(010)68476636-8007)

原文转自:www.ltesting.net

篇6:.NET 框架中的 XML:在 .NET 框架中使用 XML 架构执行代码生成.net

自动代码生成 ― 无论是数据访问层、业务实体类还是用户界面 ― 可以大大提高开发人员的生产效率。这一生成过程可以基于许多输入,例如数据库、任意 XML 文件、UML 关系图等。Microsoft?Visual Studio?.NET 随附了对从 W3C XML 架构文件 (XSD) 进行代码生成的内置支持,分为两种形式:类型化数据集以及与XmlSerializer配合使用的自定义类。

XSD 文件描述了允许包含在 XML 文档中以便被该文档视为有效的内容。由于需要以类型安全的方式对数据进行处理(这些数据最终将被序列化为 XML 数据以供使用),因此产生了各种将 XSD 转换为类的方法。我们可以回想一下,XSD 并“不是”作为一种描述对象及其关系的手段而创建的。已经存在一种更好的格式可用于该目的,它就是 UML,并且已被广泛用来对应用程序进行建模以及根据模型生成代码。因此,在 .NET 及其面向对象的编程 (OOP) 概念以及 XSD 的概念之间存在某些(预料的)不匹配现象。当您将 XSD 映射到类时,请记住这一点。

也就是说,可以将 CLR 类型系统视为 XSD 的子集:它支持一些无法映射到常规 OO 概念的功能。因此,如果您只是使用 XSD 来对类进行建模,而不是对文档进行建模,您很可能找不到任何冲突。

在本文的其余部分,我们将讨论类型化数据集方法,还将讨论通过 xsd.exe 工具生成的自定义类如何有助于得到更好的解决方案,以及如何扩展和自定义从 XSD 到类的生成过程的输出。

为了最深入地领会本文的内容,您需要对 CodeDom 有一些基本的了解。

类型化数据集有什么问题?

类型化数据集正在越来越多地用于表示业务实体,也就是说,用于充当应用程序各个层之间的实体数据传送器,甚至充当 Web 服务的输出。与“正常的”数据集不同,类型化数据集很有吸引力,因为您还可以获得对表、行、列等的类型化访问。然而,它们并非没有代价和/或限制:

•实现开销:数据集包含许多可能不为您的实体所需的功能,如更改跟踪、类似于 SQL 的查询、数据视图、大量事件等等。•性能:与 XML 之间的序列化速度不够快。XmlSerializer的性能很容易超过它。•互操作性:对于返回类型化数据集的 Web 服务的非 .NET 客户端而言,可能难以解决。•XML 结构:许多分层的(而且完全有效的)文档及其架构无法扁平化为表模型。

获取有关类型化数据集的更多信息。

因此,除非数据集的其他功能普遍对您有用,否则使用类型化数据集进行数据传递可能不是最佳选择。值得庆幸的是,还有另一个可以利用的选择。

XmlSerializer 和自定义类

XmlSerializer改善了 XML 数据处理方法。通过序列化特性,XmlSerializer能够根据对象的 XML 表示形式还原对象,并且能够反序列化到 XML 形式。此外,它还能够以非常有效的方式完成这些工作,因为它可以生成动态编译的基于XmlReader的(因而也是流式的)类,该类专门用于序列化(以及反序列化)具体的类型。所以,它确实非常快捷。

阅读有关 XML 序列化特性的更多内容。

当然,猜测使用哪些特性以便符合某个 XSD 绝对不是一件好玩的事情。为了解决这个问题,.NET SDK 随附了一个可以帮助您完成艰苦工作的实用工具:xsd.exe。它是一个命令行应用程序,能够根据 XSD 文件生成类型化数据集和自定义类。自定义类在生成后具有相应的 XML 序列化特性,因此在进行序列化时,可以保证完全忠实于架构。

阅读 Don Box 对 XSD 以及 CLR 映射和特性的介绍。

迄今为止,一切都很好。我们具有有效且快速的方法将 XML 转换为对象或者将对象转换为 XML,并且我们具有能够为我们生成类的工具。问题在于,我们有时希望得到与所生成的内容稍有不同的内容。例如,xsd.exe 所生成的类无法数据绑定到 Windows 窗体网格,因为它查找属性而不是公共字段来显示。我们可能希望在许多地方添加自己的自定义特性,将数组更改为类型化集合,等等。当然,我们在做这些事情的时候,应保证在序列化时能够与 XSD 兼容。

自定义 XSD 将明显改变所生成的类的形式。如果您只是期望将 PascalCaseIf 变成实际的 XML 标准以便使用 camelCase,那么我建议您三思而后行。MS 的一些即将问世的产品表明它们将要使用 PascalCase 来表示 XML,以便使它们更好地支持 .NET。

如果您需要进行更多的与上述自定义类似的自定义,您的选择是什么?人们几乎普遍认为 xsd.exe 是不可扩展的,并且没有办法对其进行自定义。这是不准确的,因为 .NET XML 团队实际上向我们提供了恰好可供该工具使用的类。您将需要自己动手使用CodeDom以便利用它们,但自定义程度只受到您需要的限制!

您可以在下列文章中阅读有关CodeDom的内容:

Generating and Compiling Source Code Dynamically in Multiple Languages

Generate .NET Code in Any Language Using CodeDOM

返回页首

篇7:.NET 框架中的 XML:在 .NET 框架中使用 XML 架构执行代码生成.net

为了自定义处理过程,我需要将信息传递给该工具,以便它知道要更改或处理的内容。此时有两种主要选择:

向 XSD 根 元素添加可被我的处理器理解的特性(可能添加很多),以便应用自定义,这种方法类似于类型化数据集方法。 单击此处可获得更多相关信息。

通过架构注释使用内置的 XSD 可扩展性,以便任意进行自定义。它只需向某种代码生成管线中添加类型,即可在基本生成发生后执行。

第一种方法最初可能很有吸引力,因为它非常简单。我只需添加一个特性,然后相应地修改处理器以检查该特性:

架构:

代码:

XmlSchema xsd;

// Load the XmlSchema.

...

foreach (XmlAttribute attr in xsd.UnhandledAttributes)

{

if (attr.NamespaceURI == “weblogs.asp.net/cazzu”)

{

switch (attr.LocalName)

{

case “fieldsToProperties”:

if (bool.Parse(attr.Value)) ConvertFieldsToProperties(ns);

break;

...

}

}

}

这正是您通常会在其他从 xsd 到类的生成器中看到的方法(您可以在 Code Generation Network 中找到大量类似的生成器)。遗憾的是,该方法将导致长长的 switch 语句、无尽的特性,并最终导致代码难以维护并缺乏可扩展性。

第二种方法更为健壮,因为它从一开始就考虑了可扩展性。XSD 通过 元素提供此类扩展工具,该元素可以是架构中几乎所有项目的子元素。我将利用该元素及其 子元素,以便使开发人员可以指定运行哪些(任意)扩展以及按什么顺序运行。这样的扩展架构将如下所示:

当然,每个扩展都将需要实现一个公共接口,以便自定义工具可以轻松地执行各个扩展:

public interface ICodeExtension { void Process( System.CodeDom.CodeNamespace code, System.Xml.Schema.XmlSchema schema ); }

通过预先提供此类可扩展性,当产生新的自定义需要时,就可以很容易地进行其他自定义。甚至还可以从一开始就将最基本的代码实现为扩展。

可扩展的代码生成工具

我将修改 Processor 类以添加这种新功能,并且将简单地从架构中检索各个 元素。尽管如此,这里还需要提出一个警告:与那些为元素、特性、类型等公开的 Post Schema Compilation Infoset 属性不同,在架构级别没有针对注释的类型化属性。也就是说,没有 XmlSchema.Annotations 属性。因此,需要对 XmlSchema.Items 的一般性预编译属性进行迭代,以便查找注释。而且,在检测到 XmlSchemaAnnotation 项目之后,再次需要对其自己的 Items 一般性集合进行迭代,这是因为除了 子元素以外,还可能有 子元素,而它也缺少类型化属性。当最终通过 XmlSchemaAppInfo.Markup 属性获得 appinfo 的内容之后,我们所得到的全部内容是一个 XmlNode 对象数组。您可以想像如何进行后续处理:对节点进行迭代,再对其子元素进行迭代,等等。这将产生非常丑陋的代码。

值得庆幸的是,XSD 文件只是一个 XML 文件,因此可以使用 XPath 来对其进行查询。

为了提高执行速度,我将在 Processor 类中保留 XPath 的静态编译表达式,它将在其静态构造函数中进行初始化:

public sealed class Processor

{

public const string ExtensionNamespace = “weblogs.asp.net/cazzu”;

private static XPathExpression Extensions;

static Processor()

{

XPathNavigator nav = new XmlDocument().CreateNavigator();

// Select all extension types.

Extensions = nav.Compile

(“/xs:schema/xs:annotation/xs:appinfo/kzu:Code/kzu:Extension/@Type”);

// Create and set namespace resolution context.

XmlNamespaceManager nsmgr = new XmlNamespaceManager(nav.NameTable);

nsmgr.AddNamespace(“xs”, XmlSchema.Namespace);

nsmgr.AddNamespace(“kzu”, ExtensionNamespace);

Extensions.SetContext(nsmgr);

}

注 有关 XPath 预编译和执行的优点、细节和高级应用的更多信息,请参阅 PerformantXML (I): Dynamic XPath expressions compilation 和 Performant XML (II): XPath execution tips。

Process() 方法需要在将 CodeNamespace 返回给调用方之前,执行该查询并执行它找到的每个 ICodeExtension 类型:

XPathNavigator nav;

using ( FileStream fs = new FileStream( xsdFile, FileMode.Open ) )

{ nav = new XPathDocument( fs ).CreateNavigator(); }

XPathNodeIterator it = nav.Select( Extensions );

while ( it.MoveNext() )

{

Type t = Type.GetType( it.Current.Value, true );

// Is the type an ICodeExtension?

Type iface = t.GetInterface( typeof( ICodeExtension ).Name );

if (iface == null)

throw new ArgumentException( “Invalid extension type '” +

it.Current.Value + “'.” );

ICodeExtension ext = ( ICodeExtension ) Activator.CreateInstance( t );

// Run it!

ext.Process( ns, xsd );

}

return ns;

我使用 Type.GetInterface() 而不是 Type.IsAssignableFrom() 来测试接口实现情况,因为它能够快速跳到非托管代码,所以需要的开销较少。它们的效果是相同的,然而,使用后者将返回一个布尔值,而不是一个“类型”(如果未找到接口,则返回空值)。

返回页首

网络经济(Net Economy)

NET程序员专业简历

net后缀的单词10个

第五章 类 (rainbow 翻译).net

用Delphi制作中国式报表.net

下载针对 .NET 框架的安全编码指南.net(整理7篇)
针对 .NET 框架的安全编码指南.net.doc
将本文的Word文档下载到电脑,方便收藏和打印
推荐度:
点击下载文档
热门文章
    猜你喜欢
    点击下载本文文档