`
jandroid
  • 浏览: 1896621 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

Silverlight 安全性:保护您的 Silverlight 应用程序的安全

 
阅读更多

文章做收藏之用。

作为一名 Microsoft 服务顾问,我定期与客户和合作伙伴一起进行应用程序安全性讨论。 在本文中,我将介绍一些在这些讨论中提出的主题。 特别是,我将重点介绍编程人员在尝试保护 Silverlight 应用程序的安全时所面临的新挑战,而且我将考虑开发团队应该将其资源集中于哪些方面。

  本文提到了许多技术概念,您可以在其他位置(包括本杂志)找到这些概念的更多详细信息。 因此,我就不在技术层面更加深入地讨论这些主题。 本文的目标是“理清头绪”并介绍如何利用这些概念保护您的应用程序的安全。

  当规划应用程序的安全性时,考虑三个 A 非常有用:身份验证 (Authentication)、授权 (Authorization) 和审核 (Audit)。

  身份验证是确认用户身份的行为。 我们通常使用用户名和密码执行此操作。

  授权是指在进行身份验证之后,确认用户实际上具有执行特定操作或访问特定资源的适当权限的过程。

  审核是维护活动记录,以便用户无法拒绝对系统执行的操作和请求的行为。

  在 Silverlight 应用程序上下文中,我将重点介绍前两项(身份验证和授权)。 由于这是一个富 Internet 应用程序 (RIA),因此本文中描述的大多数概念同样适用于异步 JavaScript 和 XML (AJAX) 或其他 RIA 方法。 我还将讨论如何防止对您的 Silverlight 应用程序文件进行不必要的访问。

  拓扑

  Silverlight 是一种跨浏览器插件,其利用 Windows Presentation Foundation (WPF) 率先采用的许多图形概念,使 Web 开发人员能够创建丰富的用户体验,这些用户体验将超出仅使用 HTML 和 JavaScript 创建的体验。

  与 ASP.net 不同的是,Silverlight 是一种客户端技术,它在用户的计算机上运行。 因此,Silverlight 开发无疑与 Windows 窗体或 WPF 有许多共同之处,而与 ASP.NET 的共同之处相对较少。 在许多方面,这是 Silverlight 的最大优势之一,因为它消除了 Web 应用程序的无状态性所导致的许多问题。 不过,由于所有 UI 代码都是在客户端计算机上运行的,因此您不能再相信它。

  服务

  与 Windows 窗体不同的是,Silverlight 在浏览器沙盒内运行且拥有的功能减少,因此它所提供的安全程度提高(尽管在 Silverlight 4 中,用户可以将某些应用程序标识为可信并将程序的权限提升为允许 COM 互操作)。 正因为如此,Silverlight 不能直接连接到数据库,您必须创建一个可提供对您的数据和业务逻辑的访问的服务层。

  例如,您通常会将这些服务承载于您的 Web 服务器上,就像使用 ASP.NET Web 窗体一样。 假定 Silverlight 代码运行于服务器与现实世界之间的信任边界的可信度较差的一侧(参见图 1),您的团队的工作重点应始终是保护服务的安全。

  图 1 Silverlight 运行于信任边界的可信度较差的一侧

  在您的 Silverlight 代码内实现严格的安全检查几乎没有意义。 毕竟,攻击者可以很容易就完全摆脱 Silverlight 应用程序并直接调用您的服务,从而避开您实现的任何安全措施。 此外,恶意人员可以使用像 Silverlight Spy 或 Debugging Tools for Windows 这样的实用程序更改您的应用程序在运行时的行为。

  我们要认识到的重要一点是:服务无法确切地知道哪个应用程序正在调用它或者该应用程序在某些方面尚未被修改。 因此,您的服务必须确保:

  调用方已经过适当的身份验证

  调用方已获授权执行所请求的操作

  鉴于上述原因,本文的大部分内容重点介绍如何采用与 Silverlight 兼容的方式保护服务的安全。 特别是,我将考虑通过 ASP.net 在 Microsoft IIS 中承载两种不同类型的服务。 第一种类型是使用 Windows Communication Foundation (WCF) 创建的服务,它为构建服务提供一种统一的编程模型。 第二种类型是 WCF 数据服务(以前称为“ADO.NET 数据服务”),其构建于 WCF 之上,允许您使用标准 HTTP 谓词(一种称为“具象状态传输”(REST) 的方法)快速公开数据。

  通常,如果担心安全性,则加密客户端和服务器之间的任何通信始终是明智之举。 建议使用 HTTPS/SSL 加密,且本文内假定使用此加密方法。

  目前,Web 开发人员在 Microsoft 平台上最常用的两种身份验证方法是 Windows 身份验证和窗体身份验证。

  Windows 身份验证

  Windows 身份验证利用本地安全机构或 Active Directory 验证用户凭据。 这在许多方案中都是一大优势;它意味着您可以使用系统管理员已经熟悉的工具集中管理用户。 Windows 身份验证可以使用 IIS 支持的任何方案,包括基本身份验证、摘要式身份验证、集成身份验证 (NTLM/Kerberos) 和证书。

  在使用 Windows 身份验证时,通常都会选择集成方案,因为用户无需再次提供其用户名和密码。 用户在登录到 Windows 之后,浏览器可采用用于确认个人身份的令牌或握手形式转发凭据。 但是由于客户端和服务器需要了解用户的域,使用集成身份验证有许多缺点。 因此,集成身份验证最适用于 Intranet 方案。 此外,尽管它自动与 Microsoft Internet Explorer 一起使用,但其他浏览器(如 Mozilla Firefox)需要进行额外配置。

  通常,基本身份验证和摘要式身份验证需要用户在启动与您的网站的会话时,重新输入其用户名和密码。 但是,由于这两种身份验证都属于 HTTP 规范,因此它们在大多数浏览器中均可正常使用,即使是从组织外部进行访问也是如此。

  Silverlight 利用浏览器进行通信,因此使用刚才讨论的任何 IIS 身份验证方法,均可轻松实现 Windows 身份验证。 有关如何实现的详细说明,建议阅读分步指南“如何:在 Windows 窗体中,使用 WCF 中的 basicHttPBinding 进行 Windows 身份验证并使用 TransportCredentialOnly”(网址为:msdn.microsoft.com/library/cc949012)。 此示例实际上使用 Windows 窗体测试客户端,但相同的方法也适用于 Silverlight。

  窗体身份验证

  窗体身份验证是一种为 ASP.net 中的自定义身份验证提供简单支持的机制。 因此,它特定于 HTTP,这意味着它也可在 Silverlight 中轻松使用。

  用户输入用户名和密码组合,此信息将提交给服务器进行验证。 服务器根据可信的数据源(通常是用户数据库)检查凭据,如果凭据正确,则返回一个 FormsAuthentication Cookie。 然后,客户端在随后的请求中提供此 Cookie。 Cookie 经过签名和加密,因此只有服务器才能解密,恶意用户既无法解密,也无法篡改。

  调用窗体身份验证的确切方式因登录屏幕的实现方式而异。 例如,如果在验证了用户的凭据后,使用重定向到您的 Silverlight 应用程序的 ASP.NET Web 窗体,您可能不再需要执行身份验证工作。 Cookie 已发送到浏览器,且每当请求该域时,您的 Silverlight 应用程序都将继续使用该 Cookie。

  不过,如果您希望在 Silverlight 应用程序内实现登录屏幕,您将需要创建一个公开您的身份验证方法并发送相应 Cookie 的服务。 但幸运的是,ASP.NET 已经提供了您所需要的身份验证服务, 您只需在您的应用程序中启用它即可。 有关详细指南,建议阅读“如何:使用 ASP.NET 身份验证服务通过 Silverlight 应用程序登录”(网址为:msdn.microsoft.com/library/dd560704(VS.96))。

  ASP.net 身份验证的另一项强大的功能是其可扩展性。 成员资格提供程序描述了用于验证用户名和密码的机制。 幸运的是,ASP.NET 附带了许多成员资格提供程序,包括一个可使用 SQL Server 数据库的成员资格提供程序,还有一个使用 Active Directory 的成员资格提供程序。 然而,如果没有符合您要求的提供程序,可直接创建一个自定义实现。

  ASP.NET 授权

  在您的用户通过身份验证后,请务必确保只有他们才能尝试调用服务。 在 ASP.NET 应用程序中,普通 WCF 服务和 WCF 数据服务均以 .sVC 文件表示。 本示例中,将在 IIS 中通过 ASP.NET 来承载服务,我将演示如何使用文件夹确保对服务的安全访问。

  采用这种方式保护 .svc 文件的安全有点令人迷惑不解,因为默认情况下,对此类文件的请求实际上会跳过大多数 ASP.NET 管道,从而绕过授权模块。 因此,为了能够利用许多 ASP.NET 功能,您必须启用 ASP.NET 兼容性模式。 在任何情况下,WCF 数据服务都会强制要求您启用它。 在您的配置文件内进行简单的切换即可完成任务:

<system.serviceModel> 
 <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/> 
</system.serviceModel> 
<system.Web> 
 <authorization> 
 <deny users="?"/> 
 </authorization> 
</system.web>

  启用 ASP.NET 兼容性后,可防止未经身份验证的用户通过使用 web.config 文件的授权部分(如上一代码段所示)进行访问。

  当使用窗体身份验证时,开发人员必须认真考虑站点的哪些部分需要可访问,即使是对于未经身份验证的用户也是如此。 例如,如果所有部分均仅限于经过身份验证的用户访问,那么未经身份验证的用户将如何登录?

  通常,创建一个支持您的基本授权要求的文件夹结构是最简单的方法。 在本示例中,我已经创建了一个包含 MyWcfService.sVC 和 MyWcfDataService.svc 文件的“Secured”文件夹,并且已经部署了一个 Web.config 文件。 在图 2 中,您可以看到文件夹结构,上一代码段显示了 web.config 文件的内容。

  图 2 包含 Web.config 文件的 Secured 文件夹

  请注意,应用程序的根必须允许匿名访问,否则用户无法到达登录页面。

  对于使用 Windows 身份验证的站点,在这一点上可能稍微简单一些,因为身份验证是在用户到达应用程序内所含的资源之前进行的,因此不需要具体的登录页面。 实际上,使用此方法可以采用更加详细的方式限制对服务的访问,从而只允许特定的用户或角色组访问资源。 

  此示例在某种程度上实现了授权,但是对于大多数方案而言,单独的文件夹级授权过于粗糙,难以依赖。

  WCF 服务中的授权

  使用 PrincipalPermission 属性是要求 Microsoft .net Framework 方法的调用程序限定于特定角色内的一种简单方法。 以下代码示例演示了如何在 WCF 中将其应用于 ServiceOperation,其中调用用户必须属于“OrderApprovers”角色:

[PrincipalPermission(SecurityAction.Demand, Role = "OrderApprovers")] 
public void ApproveOrder(int orderId) 
{ 
 OrderManag-er.ApproveOrder(orderId); 
}

  这在使用 Windows 身份验证以利用现有设施创建 Active Directory 组以便组织用户的应用程序中很容易实现。 借助使用窗体身份验证的应用程序,可以利用 ASP.NET 的另一项强大的基于提供程序的功能:RoleProviders。 此外,还有许多授权方法可用,如果这些方法均不适用,您可以实现自己的授权。

  当然,即便是依据方法的授权也远远不足以满足您的所有安全需求,您可以选择在您的服务内编写程序代码(如图 3 所示)。

  图 3 使用程序代码实现特定授权

Public void CancelOrder(int orderId) 
{ 
 // retrieve order using Entity Framework ObjectContext 
 OrdersEntities entities = new OrdersEntities(); 
 Order orderForProcessing = entities.Orders.Where(o => o.Id == 
  orderId).First(); 
 
 if (orderForProcessing.CreatedBy != 
  Thread.CurrentPrincipal.Identity.Name) 
 { 
  throw new SecurityException( 
   "Orders can only be canceled by the user who created them"); 
 } 
 
 OrderManager.CancelOrder(orderForProcessing); 
}

  WCF 是一个具有高度可扩展性的平台,随着所有功能都集成到 WCF 中,有许多方法可以在您的服务中实现授权。 Dominick Baier 和 Christian Weyer 在 2008 年 10 月期的 MSDN 杂志 中详细讨论了大量可能方案。 文章“基于 WCF 服务中的授权”(msdn.microsoft.com/magazine/cc948343) 甚至冒险尝试了基于声明的安全性(一种在您的应用程序中组织授权的结构化方法)。

  WCF 数据服务中的授权

  顾名思义,WCF 数据服务构建于 WCF 之上,以提供对数据源(通常大多数情况下可能是 LINQ-to-SQL 或 LINQ-to-Entity Framework 数据源)的基于 REST 的访问。 简而言之,这允许您使用映射到您的数据源公开的实体集的 URL 访问您的数据(实体集通常会映射到数据库的表中)。 这些实体集的权限可在服务代码隐藏文件内配置。 图 4 显示了 MyWcfDataService.sVC.cs 文件的内容。

  图 4 配置了实体集访问规则的 WCF 数据服务代码隐藏文件

Public class MyWcfDataService : DataService<SalesEntities> 
{ 
 // This method is called only once to initialize service-wide policies. 
 Public static void InitializeService(IDataServiceConfiguration config) 
 { 
  config.SetEntitySetAccessRule("Orders", EntitySetRights.AllRead); 
  config.SetEntitySetAccessRule("Products", EntitySetRights.AllRead | 
   EntitySetRights.WriteAppend | EntitySetRights.WriteDelete); 
 }}

  在这里,我针对 Orders 实体集授予了“读取”权限,并且配置了 Products 实体集以允许完全读取、插入新记录和删除现有记录。

  但是,由于 WCF 数据服务会自动基于此配置提供对您的数据的访问,您无法直接访问代码,因此显然无法实现任何特定授权逻辑。 WCF 数据服务支持允许开发人员在客户端和数据源之间实现逻辑的侦听器。 例如,可以指定一个筛选特定实体集结果的查询侦听器。 图 5 中的示例显示了两个添加到 MyWcfDataService 类的查询侦听器。

  图 5 WCF 数据服务中的查询侦听器

[QueryInterceptor("Products")] 
Public Expression<Func<Product, bool>> OnQueryProducts() 
{ 
 String userName =ServiceSecurityContext.Current.PrimaryIdentity.Name; 
 return product => product.CreatedBy == userName; 
} 
 
[QueryInterceptor("Orders")] 
Public Expression<Func<Comments, bool>> OnQueryOrders() 
{ 
 bool userInPrivateOrdersRole = 
  Thread.CurrentPrincipal.IsInRole("PrivateOrders"); 
 return order => !order.Private|| userInPowerUserRole; 
}

  第一个侦听器被应用于 Products 实体集并确保了用户只能检索其自己创建的产品。 第二个侦听器确保了只有 PrivateOrders 角色的用户才能读取标记“Private”的订单。

  同样,可以指定在插入、修改或删除某个实体之前运行的更改侦听器,如下所示:

[ChangeInterceptor("Products")] 
public void OnChangeProducts(Product product, UpdateOperations operations 
{ 
 if (product.CreatedBy != Thread.CurrentPrincipal.Identity.Name) 
 { 
  throw new DataServiceException( 
   "Only products created by a user can be deleted by that user"); 
 } 
}

  乍一看,此代码示例中的 OnChangeProducts 更改侦听器似乎暴露了一个安全漏洞,因为实现依赖于从外部数据源(特别是“product”参数)传递的数据。 但是,当在 WCF 数据服务中删除一个实体时,仅会将一个实体关键字从客户端传递到服务器。 这意味着必须再从数据库中获取一次实体(在本例为 Product),因此实体本身可以信任。

  但是,对于现有实体更新的情况(例如,当操作参数等于 UpdateOperations.Change 时),产品参数为客户端发送的反序列化实体,因此不可信。 客户端应用程序可能已被修改,以将此特定产品的 CreatedBy 属性指定为恶意用户自己的身份,从而提升篡夺者的权限。 这可能会允许不应修改某个产品的个人执行此操作。 为避免出现这种情况,建议您只基于实体关键字从受信任数据源重新获取原始实体,如图 6 所示。

  图 6 防止未授权插入、更新和删除操作的更改侦听器

[ChangeInterceptor("Products")] 
Public void OnChangeProducts(Product product, UpdateOperations operations) 
{ 
 if (operations == UpdateOperations.Add) 
 { 
  product.CreatedBy = Thread.CurrentPrincipal.Identity.Name; 
 } 
 else if (operations == UpdateOperations.Change) 
 { 
  Product sourceProduct = this.CurrentDataSource.Products.Where(p => 
   p.Id == product.Id).First(); 
  if (sourceProduct.CreatedBy != Thread.CurrentPrincipal.Identity.Name) 
  { 
   throw new DataServiceException( 
    "Only records created by a user can be modified by that user"); 
  } 
 } 
 else if (operations == UpdateOperations.Delete && 
  product.CreatedBy != Thread.CurrentPrincipal.Identity.Name) 
 { 
  Throw new DataServiceException( 
   "Only records created by a user can be deleted by that user"); 
 } 
}

  由于此实现在很大程度上依赖于 Product 实体的 CreatedBy 属性,因此从创建数据时起就以可靠的方式强制实施至关重要。 图 6 还显示了如何通过重写客户端为“添加”操作传递的任何值来实现此目标。

  请注意,按照示例目前的情况,UpdateOperations.Change 类型的处理操作不是问题。 图 4 中将服务配置为只允许对 Products 实体集执行 AllRead、WriteAppend(插入)和 WriteDelete 操作。 因此,永远无法为“更改”操作调用 ChangeInterceptor,因为服务会立即拒绝任何在此端点修改 Product 实体的请求。 要启用更新,图 4 中对 SetEntitySetAccessRule 的调用必须包括 WriteMerge、WriteReplace 或者两者均包括。

  跨域身份验证

  Silverlight 插件可进行跨域 HTTP 请求。 跨域调用是指对从其中下载 Silverlight 应用程序的域以外的其他域进行的 HTTP 请求。 能够进行此类调用在传统上被视为一种安全漏洞。 它允许恶意开发人员向另一个站点(例如,您的网上银行站点)发出请求,并自动转发与该域关联的任何 Cookie。 这可能会使攻击者能够访问相同浏览器进程内的另一个已登录的会话。


  为此,站点必须通过部署一个跨域策略文件选择允许跨域调用。 这是一个说明允许哪些类型的跨域调用(例如,从哪些域到哪些 URL)的 XML 文件。

  当决定向跨域调用公开任何敏感信息时,您应该始终小心谨慎。 但是,如果您决定这是一个需要随身份验证一起支持的方案,请务必注意,基于 Cookie 的身份验证方法(如前面描述的窗体验证)不再适用。 您可以考虑利用消息凭据,其中用户名和密码被传递给服务器并在每次调用时进行验证。 WCF 通过 TransportWithMessageCredential 安全模式支持此操作。 

应用原文地址:http://www.diybl.com/course/4_webprogram/asp.net/netjs/20100802/515400000067.html


分享到:
评论

相关推荐

    Silverlight 2 Toolkit July 2009

    以下各节说明如何使用 Silverlight 生成应用程序: Silverlight 的 .NET Framework 类库 Silverlight 概述 Silverlight 入门 应用程序和编程模型 布局、文本和输入 控件 图形、动画和媒体 XAML 将 ...

    比较全面总结Silverlight独立存储应用

    独立存储是一种数据存储机制,它在代码与保存的数据之间定义了标准化的关联方式,从而提供隔离性和安全性。同时,标准化也提供了其他好处。管理员可以使用旨在操作独立存储的工具来配置文件存储空间、设置安全策略及...

    MICROSOFT SILVERLIGHT 4从入门到精通

    《Microsoft Silverlight 4从入门到精通》作为一本容易上手的教程,沿用深受读者欢迎的Step by Step风格,通过实例手把手引导读者构建、部署和维护Silverlight应用程序。《Microsoft Silverlight 4从入门到精通》...

    Silverlight 参考手册 (全) [微软官方 MSDN] (chm格式,共2卷,2-2)

    以下各节说明如何使用 Silverlight 生成应用程序: Silverlight 的 .NET Framework 类库  Microsoft.CSharp.RuntimeBinder  Microsoft.Internal  Microsoft.VisualBasic  Microsoft.VisualBasic....

    silverlight4.0中文教程二(一共两个分卷压缩包下载全了再解压)

    Microsoft Silverlight 是一种跨浏览器、跨平台的 .NET Framework 实现,用于为 Web 生成和提供下一代媒体体验和丰富的交互式应用程序 (RIA)。Silverlight 统一了服务器、Web 和桌面的功能,统一了托管代码和动态...

    Silverlight 参考手册 (全) [微软官方 MSDN] (chm格式,共2卷,2-1)

    以下各节说明如何使用 Silverlight 生成应用程序: Silverlight 的 .NET Framework 类库  Microsoft.CSharp.RuntimeBinder  Microsoft.Internal  Microsoft.VisualBasic  Microsoft.VisualBasic....

    Silverlight3中文开发文档[4]

    +应用程序和编程模型 +布局文本和输入 +控件 +图型、动画和媒体 +XAML +将Silverlight 与网页集成 +类型、属性、方法和事件 +数据访问和数据结构 +网络和通信 +调试、错误处理和异常 +部署和本地化 +...

    silverlight4.0中文教程一(一共两个分卷压缩包下载全了再解压)

    Microsoft Silverlight 是一种跨浏览器、跨平台的 .NET Framework 实现,用于为 Web 生成和提供下一代媒体体验和丰富的交互式应用程序 (RIA)。Silverlight 统一了服务器、Web 和桌面的功能,统一了托管代码和动态...

    Silverlight3中文开发文档[3]

    +应用程序和编程模型 +布局文本和输入 +控件 +图型、动画和媒体 +XAML +将Silverlight 与网页集成 +类型、属性、方法和事件 +数据访问和数据结构 +网络和通信 +调试、错误处理和异常 +部署和本地化 +...

    Silverlight3中文开发文档[1]

    +应用程序和编程模型 +布局文本和输入 +控件 +图型、动画和媒体 +XAML +将Silverlight 与网页集成 +类型、属性、方法和事件 +数据访问和数据结构 +网络和通信 +调试、错误处理和异常 +部署和本地化 +...

    Silverlight3中文开发文档[2]

    +应用程序和编程模型 +布局文本和输入 +控件 +图型、动画和媒体 +XAML +将Silverlight 与网页集成 +类型、属性、方法和事件 +数据访问和数据结构 +网络和通信 +调试、错误处理和异常 +部署和本地化 +...

    Silverlight3中文开发文档[5]

    +应用程序和编程模型 +布局文本和输入 +控件 +图型、动画和媒体 +XAML +将Silverlight 与网页集成 +类型、属性、方法和事件 +数据访问和数据结构 +网络和通信 +调试、错误处理和异常 +部署和本地化 +...

    Siverlight 的安全性概览

    本文档介绍如何建立一个安全的Silverlight应用程序,使我们的Silverlight网站免于遭受恶意攻击。

    Silverlight在线几何绘图

    将已有的之前无法顺利迁移到web上的桌面应用程序(庞大交互复杂,有一定安全要求或者比较华丽总之就是Ajax无法胜任)使用Silverlight 3.0 技术迁移到web上。 对于一些良好的Flash应用的迁移。(这属于站坑拉屎,谁...

    ASP.NET4高级程序设计第4版 带目录PDF 分卷压缩包 part1

    5.2 global.asax应用程序文件 5.2.1 应用程序事件 5.2.2 演示应用程序事件 5.3 ASP.NET配置 5.3.1 machine.config文件 5.3.2 web.config文件 5.3.3 设置 5.3.4 5.3.5 5.3.6 5.3.7 通过编程读写...

    MSDN杂志2008年四月刊

    Speech Server 2007 使您可以使用 Microsoft .NET Framework 和 Visual Studio 工具集成来创建复杂的语音响应应用程序。以下是实现方法。 性能: ASP.NET 应用程序的扩展策略 性能问题可能会随着 Web 应用程序的不断...

    Csharp.2008编程参考手册.part1.rar

     ◆使用泛型提高应用程序的效率和类型安全性的方式  ◆使用LINQ查询检索数据的方式  ◆使用.NET Framework中的Thread类编写多线程应用程序的技术  ◆使用C#语言构建Windows、Web和Windows Mobile应用程序的方法 ...

    ASP.NET4高级程序设计(第4版) 3/3

    5.2 global.asax应用程序文件 140 5.2.1 应用程序事件 141 5.2.2 演示应用程序事件 143 5.3 ASP.NET配置 144 5.3.1 machine.config文件 144 5.3.2 web.config文件 147 5.3.3 设置 150 5.3.4 150 ...

    MSDN杂志 2008年第二期

    本期MSDN杂志的主要内容有: &lt;br&gt;• 自己动手: 创建 .NET Framework 语言编译器 &lt;br&gt;• WinUnit: 简化的本机 C++ 应用程序单元测试 &lt;br&gt;• Silverlight: 创建自定义 Expression Encoder 发布插件 &lt;br&gt;•...

Global site tag (gtag.js) - Google Analytics