1. 简介
1.1 什么是 .NET?
1.2 .NET 只是 Windows DNA 的一个新名字吗?
1.3 .NET 只适用于建立 Web 网站吗?
1.4 .NET 是在什么时候宣布的?
1.5 .NET 将在何时发布?
1.6 如何开发 .NET 应用程序
1.7 可以从哪里下载 .NET SDK 和 Visual Studio 7?
1.8 .NET 中的关键技术是什么?
1.9 .NET 框架将运行在什么平台上?
1.10 .NET 框架支持什么语言?
1.11 .NET 框架符合标准化趋势吗?
 
2. 基本术语
2.1 什么是 CLR?
2.2 什么是 CTS?
2.3 什么是 CLS?
2.4 什么是 IL?
2.5 什么是 C#?
2.6 在 .NET 范畴里,“被管理”是什么含义?
2.7 什么是映像?
 
3. 元件
3.1 什么是元件?
3.2 怎样创建元件?
3.3 私有元件和共享元件有什么不同?
3.4 元件如何相互找到?
3.5 元件版本如何起作用?
 
4. 应用程序域
4.1 什么是应用程序域?
4.2 如何创建 AppDomain?
4.3 我能编写自己的 .NET 宿主吗?
 
5. 垃圾收集
5.1 什么是垃圾收集?
5.2 对对象的最后一个引用撤销后,它并不一定立即被破坏,对吗?
5.3 .NET 为什么不提供确定化的析构?
5.4 在 .NET 中缺少确定化的析构有问题吗?
5.5 确定化的析构是否影响在被管理代码中使用 COM 对象?
5.6 我听说应该避免使用 Finalize 方法,那么是否应该在我的类里实现 Finalize?
5.7 我有控制垃圾收集算法的手段吗?
5.8 我怎么知道垃圾收集器在做什么?
 
6. 属性
6.1 什么是属性?
6.2 我能创建自己的 metadata 属性吗?
6.3 我能创建自己的 context 属性吗?
 
7. 代码访问安全性
7.1 什么是代码访问安全性 (CAS)?
7.2 CAS 如何起作用?
7.3 谁定义 CAS 代码组?
7.4 如何定义自己的代码组?
7.5 如何改变代码组的权限集?
7.6 能否创建自己的权限集?
7.7 CAS 有问题时,如何诊断自己的程序?
7.8 我受不了 CAS 带来的麻烦,能否关掉它?
 
8. 中间语言 (IL)
8.1 我能看到元件的中间语言吗?
8.2 能否通过反向工程从 IL 中获得源代码?
8.3 如何防止别人通过反向工程获得我的代码?
8.4 我能直接用 IL 编程吗?
8.5 IL 能做到 C# 中做不到的事吗?
 
9. 关于 COM
9.1 COM 消亡了吗?
9.2 DCOM 消亡了吗?
9.3 MTS/COM+ 消亡了吗?
9.4 能在 .NET 中使用 COM 组件吗?
9.5 能在 COM 中使用 .NET 组件吗?
9.6 在 .NET 的世界中 ATL 是多余的吗?
 
10. 杂项
10.1 .NET 的远程计算如何工作?
10.2 如何在 .NET 程序中获得 Win32 API?
 
11. 类库
11.1 文件 I/O
11.1.1 如何读文本文件?
11.1.2 如何写文本文件?
11.1.3 如何读写二进制文件?
11.1.4 如何删除文件?
11.2 文本处理
11.2.1 是否支持正规表达式?
11.3 Internet
11.3.1 如何下载网页?
11.3.2 如何使用代理服务器 (proxy)?
11.4 XML
11.4.1 是否支持 DOM?
11.4.2 是否支持 SAX?
11.4.3 是否支持 XPath?
11.5 线程
11.5.1 是否支持多线程?
11.5.2 如何产生一个线程?
11.5.3 如何停止一个线程?
11.5.4 怎样使用线程池?
11.5.5 怎样知道我的线程池工作项目是在何时完成的?
11.5.6 怎样防止对数据的并发访问?
11.6 跟踪
11.6.1 有内置的跟踪/日志支持吗?
11.6.2 能否将跟踪输出重定向到一个文件?
11.6.3 能否定制跟踪的输出?
 
12. 资源
12.1 从哪里可以获得关于 .NET 的详情?
12.2 示例代码和实用程序

1. 简介
1.1 什么是 .NET?
很难用一句话来讲清楚。根据 Microsoft 的说法,.NET 是一个“革命性的新平台,建立在开放的 Internet 协议和标准之上,通过工具和服务将计算和通讯以崭新的方式融合到一起” 。

更为实际的定义是:.NET 是一个开发和运行软件的新环境,便于开发基于 Web 的服务,拥有丰富的运行库服务以支持用多种编程语言编写的组件,具有跨语言和跨平台的互操作能力。

注意,本文中使用术语“.NET”时,它仅指新的 .NET 运行库及其相关技术。有时我们也称其为“.NET 框架”。本文不包括其它 Microsoft 正在往上添加 .NET 名字的任何现有的产品和技术 (例如 SQL Server.NET)。

1.2 .NET 只是 Windows DNA 的一个新名字吗?
不。在很多地方,Windows DNA 仅仅是指使用现有技术的一种途径(即所谓的三阶式途径)的市场术语。.NET 更为急进,并且包括一个完整的软件开发和运行库框架。

1.3 .NET 只适用于建立 Web 网站吗?
不。如果你编写任何 Windows 软件 (使用 ATL/COM、MFC、VB 甚至 Win32 裸接口),.NET 都可能为你正在做的事情提供可行的选择 (或补充)。当然,如果你就是在开发 Web 网站,.NET 有很多令你感兴趣的东西—不仅仅是 ASP+。

1.4 .NET 是在什么时候宣布的?
在 2000 年 6 月 22 日举行的 Forum 2000 论坛上,Bill Gates 做了一次 演说,勾画了 .NET 的“前景”。2000 年 7 月的 PDC 会议上针对 .NET 技术做了很多会谈,会谈代表得到了包含 .NET Framework/SDK 和 Visual Studio 7 预览版的光盘。

1.5 .NET 将在何时发布?
预计是在 2001 年的下半年。

1.6 如何开发 .NET 应用程序?
.NET Framework SDK 包含可用于建立 .NET 应用程序的命令行编译器和实用程序。Visual Studio 的下一版本 (称为 Visual Studio 7 或 Visual Studio.NET) 将完全集成对 .NET 开发的支持。

1.7 可以从哪里下载 .NET SDK 和 Visual Studio 7?
从 http://msdn.microsoft.com/net 可以下载 SDK 的 Beta 1 版。如果你是 MSDN Universal 订户,你还可以下载 Visual Studio 7 的 Beta 1 版。

1.8 .NET 中的关键技术是什么?
ASP.NET、CLR (Common Language Runtime—通用语言运行库)、C# (新一代的类-Java 语言)、SOAP、XML、ADO.NET、多语言支持 (Eiffel、COBOL 等等)

1.9 .NET 框架将运行在什么平台上?
Beta 1 支持在 Windows 2000、NT4 SP6a, Windows Me 和 Windows 98 上进行开发。Windows 95 支持运行库。

Microsoft 将按照和 .NET 运行库相似的时间表发布一个新版本 Windows。它的代号是“Whistler”,在很大程度上是对 Windows 2000 的扩充性更新,对 GUI 有重要的改变。Microsoft 将以“.NET-enabled”作为新操作系统的卖点,但看起来它没有和 .NET 运行库绑在一起。如果 .NET 运行库能及时完成,它将包含在 Whistler 之内;否则,Whistler 将单独发货。

1.10 .NET 框架支持什么语言?
开始 Microsoft 将提供 C#、C++、VB 和 JScript 编译器。其它供应商宣布他们有意开发像 COBOL、Eiffel、Perl、Smalltalk 和 Python 等语言的 .NET 编译器。

1.11 .NET 框架符合标准化趋势吗?
C# 以及称为“通用语言基础结构”的一些东西的推荐标准草案已经提交给了 ECMA。参见 http://msdn.microsoft.com/net/ecma/

2. 基本术语
2.1 什么是 CLR?
CLR = Common Language Runtime—通用语言运行库。CLR 是一组标准资源集合,无论编程语言是什么,所有 (理论上) .NET 程序都能从中获益。Robert Schmidt (Microsoft) 在他的 MSDN PDC# 文章 中列出了以下 CLR 资源:

面向对象的编程模型 (继承、多态、异常处理、垃圾收集)
安全模型
类型系统
所有的 .NET 基础类
许多 .NET 框架类
开发、调试和测评工具
运行和代码管理
IL-机器语言 转换器和优化器

这些的含义是,在 .NET 世界里,不同的编程语言将在能力上比过去任何时候都更平等,虽然显然不是所有语言都支持所有 CLR 服务。

2.2 什么是 CTS?
CTS = Common Type System—通用类型系统。它是指 .NET 运行库所理解、并且随后 .NET 应用程序可以使用的一系列类型。然而,注意不是所有的 .NET 语言都将支持 CTS 中的所有类型。CTS 是 CLS 的超集。

2.3 什么是 CLS?
CLS = Common Language Specification—通用语言规范。它是预计所有 .NET 语言都支持的一个 CTS 的子集。这一思想是让使用 CLS-相容类型的任何程序和以任何语言编写的 .NET 程序可以互相操作。

理论上它能允许在不同的 .NET 语言之间有紧密的互操作性—例如允许从一个 VB 类里继承一个 C# 类。

2.4 什么是 IL?
IL = Intermediate Language—中间语言。又称为 MSIL。所有 .NET 源代码 (使用任何语言) 被编译为 IL。然后在软件的安装点上或者运行时,IL 由即时 (JIT) 编译器转换为机器码。

2.5 什么是 C#?
C# 是在 .NET 框架中运行的一种新语言。在他们的“C# 简介”白皮书中,Microsoft 这样描述 C#:

“C# 是从 C 和 C++ 派生出来的一种简单的、面向对象的、并且是类型安全的现代编程语言。C# (发音为‘C sharp’) 牢固地根植于在 C 和 C++ 家族之树,将很快为 C 和 C++ 程序员所熟悉。C# 帮助开发者将 Visual Basic 的高生产率和 C++ 的直接控制能力结合起来。”

将以上引言中的“C#”换成“Java”,你会发现这句陈述依然很正确

假如你是一位 C++ 程序员,你可能想看看我的 C# FAQ。

2.6 在 .NET 范畴里,“被管理”是什么含义?
术语“被管理”导致了很多误解。在 .NET 里的不同地方都使用了它,分别指相互差别不大的不同东西。

被管理代码:.NET 框架为运行在其上的程序提供了几个核心的运行服务—例如异常处理和安全性。为使这些服务能工作,代码必须提供运行时的最低程度的一些信息。这样的代码被称为被管理代码。默认情况下,所有 C#、Visual Basic.NET 和 JScript.NET 代码都是被管理代码。如不指明,VS7 C++ 代码不是被管理代码,但能通过一个命令行开关 (/com+) 使编译器产生被管理代码。

被管理数据:是指由 .NET 运行库的垃圾收集器分配和回收的数据。C#、VB.NET 和 JScript.NET 数据总是被管理的。即使使用了 /com+ 开关,默认情况下 VS7 C++ 数据也不是被管理的,但可以使用 __gc 关键字将其指定为被管理数据。

被管理类:通常在 C++ 的 Managed Extensions (ME) 范畴中涉及。使用 ME C++ 时,可以用 __gc 关键字将其指定为被管理的。名副其实,该类的实例所占用的内存由垃圾收集器管理,但还不止如此。该类还成为了完全的 .NET 团体的成员,同时带来了好处和限制。好处之一是获得了与其它语言编写的类之间的互操作性—例如,一个被管理 C++ 类可以继承 VB 类。限制之一是被管理类只能继承一个基类。

2.7 什么是映像?
所有的 .NET 编译器都产生关于它们所产生的模块中的类型定义的特殊数据。这些特殊数据同模块封装在一起 (随后模块被封装到元件中),可以通过称为映像 的机制来访问。System.Reflection 命名空间中包含向模块或元件询问其类型的类。

使用映像来访问 .NET 的特殊数据同使用 ITypeLib/ITypeInfo 来访问 COM 中的类型库数据非常相似,而且使用的目的也很相似—例如确定数据类型大小,以便在上下文、进程、机器的边界间调度它们。

映像还可以被用来动态调用方法 (参见 System.Type.InvokeMember),甚至在运行时动态创建类型 (参见 System.Reflection.Emit.TypeBuilder )。

3. 元件
3.1 什么是元件?
元件有时被描述为一个逻辑上的 .EXE 或 .DLL,它可以是任何一个应用程序 (有一个主入口点) 或库。一个元件由一个或多个文件组成 (dll、exe、html 文件等等),表示一组资源、类型定义以及这些类型的实现。一个元件也可以包含对其它元件的引用。这些资源、类型和引用在称为清单的一个数据块中描述。清单是元件的一部分,这样一来元件就是自描述的。

元件的一个重要方面使他们是一个类型的唯一标志的一部分。类型的唯一标志是将它所在的元件和类型名组合在一起得到的。这就是说,例如,如果元件 A 输出了一个称为 T 的类型,同时元件 B 输出了一个也称为 T 的类型,.NET 运行库将它们视为完全不同的两个类型。此外,不要混淆元件和命名空间—命名空间仅仅是组织类型名字的一种层次化方法。对于运行库,不论使用哪一个命名空间来组织名字,类型名就是类型名。从运行库来看,是元件加上类型名 (无论类型名属于哪个命名空间) 唯一地标识出一个类型。

元件在 .NET 的安全方面也很重要—许多安全限制是在元件的边界上实施的。

最后,元件是 .NET 中版本控制的单元—详情见下文。

3.2 怎样创建元件?
创建元件最简单的方法是直接使用 .NET 编译器。例如,以下 C# 程序:

public class CTest
{
    public CTest()
    {
        System.Console.WriteLine( “Hello from CTest” );
    }
}
能用以下方法编译为一个库元件 (dll):

csc /t:library ctest.cs
通过运行 .NET SDK 所带的“IL 反汇编”工具,你能看到元件的内容。

另外你也能把你的源代码编译成模块,然后使用元件连接器 (al.exe) 将模块组合成一个元件。对 C# 编译器,/target:module 开关可以指定产生模块而不是元件。

3.3 私有元件和共享元件有什么不同?
空间分配和可见性:私有元件通常由一个应用程序使用,被存储到这个应用程序的目录或其下的子目录之下。共享元件通常存储到全局的元件缓冲区中,这里是 .NET 运行库维护的元件的储藏所。共享元件通常是许多应用程序都要用到的代码库,例如 .NET 框架类。
 
版本控制:运行库只对共享元件实施版本约束,而不对私有元件实施。

3.4 元件如何相互找到?
通过寻找目录路径。有几个因素会影响路径 (比如 AppDomain 宿主、应用程序配置文件等),但对于私有元件,搜索路径通常是应用程序的目录及其子目录。对于共享元件,搜索路径通常和私有元件的一样,再加上共享元件缓冲区。

3.5 元件版本如何起作用?
每个元件由一个称为兼容性版本的版本号。同样,对元件的引用 (从另一个元件) 包括被引用元件的名称和版本。

版本号有四个数字部分 (例如 5.5.2.33)。前两部分不相同的元件被视为不兼容的。如果前两部分相同,但第三部分不同,元件被认为“可能兼容”。如果仅仅第四部分不同,则元件被视为是兼容的。然而,这只是默认的指导方针—是 版本策略决定施用这些规则的范围。版本策略可以在应用程序配置文件中指定。

记住:版本控制仅仅针对于共享元件,而不对私有元件。

4. 应用程序域
4.1 什么是应用程序域?
应用程序域 (AppDomain) 可以被看作一个轻型的进程。在一个 Win32 进程中可以存在多个 AppDomain。AppDomain 的主要目的是将应用程序和其它应用程序隔离开来。

通过使用独立的地址空间,Win32 进程提供隔离性。这种方法很有效,但开销很大并且伸缩性不好。.NET 运行库通过控制对内存的是用来施加 AppDomain 隔离—AppDomain 中的所有内存是由 .NET 运行库来管理的,所以运行库可以确保 AppDomain 之间不能访问彼此的内存。

4.2 如何创建 AppDomain?
AppDomains 通常有宿主创建。宿主包括 Windows Shell、ASP+ 和 IE。当你从命令行运行一个 .NET 应用程序时,宿主是 Shell。Shell 为每个应用程序创建一个新的 AppDomain。

AppDomains 也可以由 .NET 应用程序来显式创建。这里是一个创建 AppDomain 的一个 C# 例子,它创建对象的一个实例,并随后执行对象的一个方法:

using System;
using System.Runtime.Remoting;

public class CAppDomainInfo : MarshalByRefObject
{
    public string GetAppDomainInfo()
    {
        return “AppDomain = ” + AppDomain.CurrentDomain.FriendlyName;
    }

}

public class App
{
    public static int Main()
    {
        AppDomain ad = AppDomain.CreateDomain( “Andy’s new domain”, null, null );
        ObjectHandle oh = ad.CreateInstance( “appdomaintest.exe”, “CAppDomainInfo” );
        CAppDomainInfo adInfo = (CAppDomainInfo)(oh.Unwrap());
        string info = adInfo.GetAppDomainInfo();
       
        Console.WriteLine( “AppDomain info: ” + info );
        return 0;
    }
}

4.3 我能编写自己的 .NET 宿主吗?
能。关于怎样来做的例子,看看 Jason Whittington 和 Don Box 开发的 dm.net moniker 的源代码 (http://staff.develop.com/jasonw/clr/readme.htm)。在 .NET SDK 中也有一个叫作 CorHost 的代码示例。

5. 垃圾收集
5.1 什么是垃圾收集?
垃圾收集是一个系统,运行库组件通过它来管理对象的生存周期和它们占用的堆内存。对 .NET 而言它并不是一个新概念—Java 和许多其它语言/运行库使用垃圾收集已经有一段时间了。

5.2 对对象的最后一个引用撤销后,它并不一定立即被破坏,对吗?
是的。垃圾收集器并不提供销毁对象并是放其内存的时间保证。

关于 C# 中隐含的非确定化对象析构,Chris Sells 有一个令人感兴趣的线索:http://discuss.develop.com/archives/wa.exe?A2=ind0007&L=DOTNET&P=R24819

2000 年 10 月,Microsoft 的 Brian Harry 贴出了一个针对这个问题的很长的分析:http://discuss.develop.com/archives/wa.exe?A2=ind0010A&L=DOTNET&P=R28572

Chris Sells 对 Brian 贴子的答复在这里:http://discuss.develop.com/archives/wa.exe?A2=ind0010C&L=DOTNET&P=R983

5.3 .NET 为什么不提供确定化的析构?
因为垃圾收集算法。.NET 的垃圾收集器通过周期地扫描应用程序正在使用的所有对象的列表来工作。扫描过程中所有未被发现的对象就可以被销毁并释放内存。当对对象的最后一个引用撤销后,算法的这种实现使运行库不能立即得到通知—它只能在下一次清理堆时发现。

而且,这种算法尽可能少地进行垃圾收集,以便工作得最有效率。通常,堆容量的消耗会触发收集过程。

5.4 在 .NET 中缺少确定化的析构有问题吗?
这确实会影响组件的设计。如果你的对象需要昂贵或紧缺的资源 (例如对数据库的锁定),你需要提供某种方法让客户端在工作完成后能告诉对象以释放资源。Microsoft 建议,为此目的你应提供一个称为 Dispose () 的方法。然而,这样会在分布式对象中引起问题—在一个分布式系统中由谁来调用 Dispose () 方法?需要有某种形式的引用-计数机制或所有者管理机制来处理分布式对象—不幸的是运行库对此爱莫能助。

5.5 确定化的析构是否影响在被管理代码中使用 COM 对象?
是的。从被管理代码中使用 COM 对象时,你实际上是依赖垃圾收集器来最终释放你的对象。如果你的 COM 对象占有昂贵的资源且只能在最终释放对象后才能释放,你可能需要在你的对象上提供一个新接口以支持显式的 Dispose () 方法。

5.6 我听说应该避免使用 Finalize 方法,那么是否应该在我的类理实现 Finalize?
对垃圾收集器而言,拥有 Finalize 方法的对象比没有此方法的对象需要做更多的工作。同时也不保证对象 Finalized 的次序,所以对于从 Finalized 方法访问其它对象有不同的看法。最后,不能保证 Finalized 方法一定能被调用。所以,永远不应该依赖它来清理对象的资源。

Microsoft 建议使用以下方式:

public class CTest
{
    public override void Dispose()
    {
        … // Cleanup activities
        GC.SuppressFinalize(this);
    }
   
    protected override void Finalize()
    {
        Dispose();
    }
}
一般情况下客户端调用 Dispose (),对象的资源被释放,并且通过调用 SuppressFinalize (),垃圾收集器被免除了对它进行 Finalize 的义务。在最不利的情况下,即客户端忘记了调用 Dispose (),有很大的机会通过垃圾收集器调用 Finalize () 来最终释放对象的资源。由于垃圾收集算法的缺陷,这看起来像是相当合理的处理办法了。

5.7 我有控制垃圾收集算法的手段吗?
有一点。System.GC 类提供了一对有趣的方法。第一个是 Collect 方法—它强制垃圾收集器立即收集所有未被引用的对象。另一个是 RequestFinalizeOnShutdown (),它告诉垃圾收集器在应用程序关闭时一定要对每个对象运行 Finalize () 方法。在应用程序关闭时,垃圾收集器一般优先选择快速的推出方式而不是调用 Finzlize (),所以这个方法能手工强制运行库多负一点责任。

如果你想验证这不仅仅是理论上的说法,是一十下面的测试程序:

using System;

class CTest
{
    protected override void Finalize()
    {
        Console.WriteLine( “This is the Finalizer.” );
    }
}

class CApplication
{
    public static void Main()
    {
        Console.WriteLine( “This is Main.” );
        CTest test = new CTest();
       
        // GC.RequestFinalizeOnShutdown();
    }
}
运行此程序,然后再去掉 GC.RequestFinalizeOnShutdown() 这一行前面的注释标记并重新运行,注意有什么不同……

5.8 我怎么知道垃圾收集器在做什么?
.NET 运行库中很多令人感兴趣的统计通过 ‘COM+ Memory’ 性能对象输出。使用 Performance Monitor 查看它们。

6. 属性
6.1 什么是属性?
最少有两种类型的 .NET 属性。第一类我称其为 metadata 属性—它允许将某些数据附加到类或方法上。这些数据称为类的 metadata 的一部分,并且可以像类的其它 metadata 一样通过映射来访问。metadata 的另一种属性是 [serializable],将它附加到类上表示类的实例可以被串行化。

[serializable] public class CTest {}
另一种类型的属性是上下文属性。上下文类型的属性使用和 metadata 相似的语法,但实际上它们是不同的。上下文类型属性提供一种解释机制,通过这种机制,实例的活动和方法调用可以是预先处理和/或随后处理的。如果你了解 Keith Brown 的通用委托器你可能熟悉这种思想。

6.2 我能创建自己的 metadata 属性吗?
是的。简单地从 System.Attribute 导出一个类并将其标记为 AttributeUsage 属性。例如:

[AttributeUsage(AttributeTargets.Class)]
public class InspiredByAttribute : System.Attribute
{
    public string InspiredBy;

public InspiredByAttribute( string inspiredBy )
    {
        InspiredBy = inspiredBy;
    }
}

[InspiredBy(”Andy Mc’s brilliant .NET FAQ”)]
class CTest
{
}
class CApp
{
    public static void Main()
    {
      object[] atts = typeof(CTest).GetCustomAttributes();
            foreach( object att in atts )
         if( att is InspiredByAttribute )
        Console.WriteLine( “Class CTest was inspired by {0}”, _
           ((InspiredByAttribute)att).InspiredBy  );
    }
}

6.3 我能创建自己的 context 属性吗?
是的。看看 http://www.develop.com/dbox/dotnet/threshold/ 处的 Don Box 的例子 (叫作 CallThreshold) 和 http://www.razorsoft.net/ 处的 Perter Drayton 的 Tracehook.NET

7. 代码访问安全性
7.1 什么是代码访问安全性 (CAS)?
CAS 是 .NET 安全性模型的一部分,它确定一段代码是否允许被运行,以及当它运行是可以使用什么资源。例如,CAS 可以防止一个 .NET 的 Web applet 将你的硬盘格式化。

7.2 CAS 如何起作用?
CAS 安全策略设计两个关键概念—代码组和权限。每个 .NET 元件是特定 代码组的成员,并且每个代码组被授予由有名权限集所指定的权限。

例如,使用默认的安全策略时,一个从 Web 站点下载的控件属于“Zone - Internet”代码组,它保持由有名权限集“Internet”所定义的权限。(自然,有名权限集“Internet”表示一组受到严格限制的权限。)

7.3 谁定义 CAS 代码组?
Microsoft 定义了一些默认代码组,但你可以改变这些甚至创建你自己的代码组。要想看到你的系统中定义的代码组,可以从命令横行运行“caspol -lg”命令。再我的系统里它看起来像这些:

Level = Machine

Code Groups:

1.  All code: Nothing
   1.1.  Zone - MyComputer: FullTrust
      1.1.1.  Honor SkipVerification requests: SkipVerification
   1.2.  Zone - Intranet: LocalIntranet
   1.3.  Zone - Internet: Internet
   1.4.  Zone - Untrusted: Nothing
   1.5.  Zone - Trusted: Internet
   1.6.  StrongName - 0024000004800000940000000602000000240000525341310004000003
000000CFCB3291AA715FE99D40D49040336F9056D7886FED46775BC7BB5430BA4444FEF8348EBD06
F962F39776AE4DC3B7B04A7FE6F49F25F740423EBF2C0B89698D8D08AC48D69CED0FC8F83B465E08
07AC11EC1DCC7D054E807A43336DDE408A5393A48556123272CEEEE72F1660B71927D38561AABF5C
AC1DF1734633C602F8F2D5: Everything
注意代码组的层次—顶层 (’All code’) 是最通用的,它随后分为几个组,每个还可以再分。同时注意,和一般的想象不同,子组可以被赋予比它的上级更宽的权限集。

7.4 如何定义自己的代码组?
使用 caspol。例如,假定你信任来自 www.mydomain.com 的代码,并且希望它对你的系统拥有完全的访问权,但是希望对其它 Internet 站点保持默认的限制。要实现这些,你可以在“Zone - Internet”组中增加一个子组,就像下面那样:

caspol -ag 1.3 -site www.mydomain.com FullTrust
现在如果你运行 caspol -lg 就可以看到新的代码组被增加为 1.3.1 组:


   1.3.  Zone - Internet: Internet
      1.3.1.  Site - www.mydomain.com: FullTrust

注意数字标号 (1.3.1) 只是 caspol 编出来以便能从命令行方便地操纵代码组的。底层的运行库永远看不到它。

7.5 如何改变代码组的权限集?
使用 caspol。如果你是机器的管理员,你能在 ‘machine’ 层次上操作—这不仅意味着你所做的改变将成为机器的默认设置,而且用户不能把权限改得更宽。如果你是一个普通用户 (不是管理员) 你仍然可以修改权限,但只能使它们变得更严格。例如,为使 intranet 代码能做它们想做的事,你可能需要这样:

caspol -cg 1.2 FullTrust
注意,因为 (在标准的系统里) 这比默认的安全策略权限更大,你应该在 machine 层次上做这些—在 user 层次上这样做不起作用。

7.6 能否创建自己的权限集?
是的。使用 caspol -ap,指定一个包含权限集中所有的权限的 XML 文件。这里 是一个指定 ‘Everything’ 权限集的示例文件—修改它以适应你的需要,这样可以节省一些时间。修改完成后,用以下方法将它添加到可用的权限集中:

caspol -ap samplepermset.xml
然后,用以下方法将此权限集施加到一个代码组上:

caspol -cg 1.3 SamplePermSet
(默认情况下,1.3 是 ‘Internet’ 代码组)

7.7 CAS 有问题时,如何诊断自己的程序?
caspol 有一组可能有用的选项。首先,使用 caspol -rsg,你能让 caspol 告诉你一个元件属于哪一个代码组。类似地,使用 caspol -rsp,你能询问在特定元件上施加了什么权限。

7.8 我受不了 CAS 带来的麻烦,能否关掉它?
是的,只要你是系统管理员。只要运行:

caspol -s off

8. 中间语言 (IL)
8.1 我能看到元件的中间语言吗?
是的。Microsoft 提供了一个称为 Ildasm 的工具,它可以用来查看元件的 metadata 和 IL。

8.2 能否通过反向工程从 IL 中获得源代码?
是的。相对而言,从 IL 来重新生成高级语言源代码 (例如 C#) 通常是很简单的。

8.3 如何防止别人通过反向工程获得我的代码?
目前唯一的办法是运行带有 /owner 选项的 ilasm。这样生成的元件的 IL 不能通过 ildasm 来查看。然而,意志坚定的代码破译者能够破解 ildasm 或者编写自己的 ildasm 版本,所以这种方法只能吓唬那些业余的破译者。

不幸的事,目前的 .NET 编译器没有 /owner 选项,所以要想保护你的 C# 或 VB.NET 元件,你需要像下面那样做:

csc helloworld.cs
ildasm /out=temp.il helloworld.exe
ilasm /owner temp.il
(这个建议是 Hany Ramadan 贴到 DOTNET 上的。)

看起来过一段时间能有 IL 加密工具 (无论来自 Microsoft 或第三方)。这些工具会以这样的方式来“优化” IL:使反向工程变得更困难。

当然,如果你是在编写 Web 服务,反向工程看起来就不再是一个问题,因为客户不能访问你的 IL。

8.4 我能直接用 IL 编程吗?
是的。Peter Drayton 在 DOTNET 邮件列表里贴出了这个简单的例子:

.assembly MyAssembly {}
.class MyApp {
  .method static void Main() {
    .entrypoint
    ldstr      “Hello, IL!”
    call       void System.Console::WriteLine(class System.Object)
    ret
  }
}
将其放入名为 hello.il 的文件中,然后运行 ilasm hello.il,将产生一个 exe 元件。

8.5 IL 能做到 C# 中做不到的事吗?
是的。一些简单的例子是:你能抛出不是从 SystemException 导出的异常,另外你能使用非以零起始的数组。

9. 关于 COM
9.1 COM 消亡了吗?
就像你在邮件列表中看到的那样,这个主题导致了激烈的争论。看看以下两个地方:

http://discuss.develop.com/archives/wa.exe?A2=ind0007&L=DOTNET&D=0&P=68241
http://discuss.develop.com/archives/wa.exe?A2=ind0007&L=DOTNET&P=R60761

我的理解是:COM 包含很多内容,并且对于不同的人而言它是不同的东西。但是对我来说,COM 基本上是关于一小段代码如何找到另一小段代码,以及当它们相互找到后该如何相互通讯。COM 准确地指明了这种定位和通讯该如何进行。在完全由 .NET 对象构成的“纯” .NET 世界里,小段代码依然相互寻找并相互交谈,但它们不使用 COM 来做这些。它们使用在某些地方和 COM 很相像的一种模型—例如,类型信息保存在和组件封装在一起的表单中,这和在 COM 组件中封装一个类型库十分相似。但它不是 COM。

所以,这里有什么问题吗?好吧,我确实不关心大多数 COM 消失了—我不关心寻找组件不再和注册表有关,我也不使用 IDL 来定义我的借口。但有一件东西我不希望它消失—我不希望失去基于接口的开发这种思想。照我看来,COM 最强大的力量是它坚持在接口和实现之间竖起铸铁般的隔墙。不幸的是,看来 .NET 不再那样坚持—它允许你做基于接口的开发,但它并不坚持。一些人可能会辩解说有一个选择总不会是坏事,可能他们是对的,但我不能不觉得这可能是一个退步。

9.2 DCOM 消亡了吗?
差不多是,尤其是对于 .NET 开发者。.NET 框架有一个不基于 DCOM 的新的远程模型。当然 DCOM 还会在互操作场合下使用。

9.3 MTS/COM+ 消亡了吗?
不。第一个 .NET 版本考虑的是提供对现有 COM+ 服务 (通过一个互操作层) 而不是使用 .NET 自己的服务来取代它们。很多工具和属性被用以实现尽可能平滑的过渡。.NET SDK 的 PDC 版本包括对核心服务 (JIT 活动、事务) 的支持,但不包括一些高层服务 (例如 COM+ 事件、队列化组件)。

在一段时间内看来,互操作性可以预期是无缝集成的—这意味着一些服务将成为 CLR 的一部分,并且/或者意味着一些服务将以可管理代码的形式重写并运行在 CLR 的顶层。

关于这个主题,参见 Joe Long 的贴子—Joe 是 Microsoft 的 COM+ 组的经理。从这里开始:

http://discuss.develop.com/archives/wa.exe?A2=ind0007&L=DOTNET&P=R68370

9.4 能在 .NET 中使用 COM 组件吗?
可以。可以通过 Runtime Callable Wrapper (RCW) 从 .NET 中访问 COM 组件。它通过将 COM 组件映射为与 .NET 兼容的接口来使 COM 接口可以被访问。对于 oldautomation 接口,可以自动地从一个类型库中产生。对于非 oleautomation 接口,可以开发一个定制的 RCW,以便手工地将 COM 接口的类型映射为与 .NET 兼容的类型。

对于熟悉 ATL 的读者,这里有一个简单的示例。首先,创建一个 ATL 组件以实现以下 IDL:

import “oaidl.idl”;
import “ocidl.idl”;

[
    object,
    uuid(EA013F93-487A-4403-86EC-FD9FEE5E6206),
    helpstring(”ICppName Interface”),
    pointer_default(unique),
    oleautomation
]

interface ICppName : IUnknown
{
    [helpstring(”method SetName”)] HRESULT SetName([in] BSTR name);
    [helpstring(”method GetName”)] HRESULT GetName([out,retval] BSTR *pName );
};

[
    uuid(F5E4C61D-D93A-4295-A4B4-2453D4A4484D),
    version(1.0),
    helpstring(”cppcomserver 1.0 Type Library”)
]
library CPPCOMSERVERLib
{
    importlib(”stdole32.tlb”);
    importlib(”stdole2.tlb”);
    [
        uuid(600CE6D9-5ED7-4B4D-BB49-E8D5D5096F70), 
        helpstring(”CppName Class”)
    ]
    coclass CppName
    {
        [default] interface ICppName;
    };
};
建立了组件以后,你会得到一个 typelibrary。在 typelibrary 上运行 TLBIMP 实用程序,就像这样:

tlbimp cppcomserver.tlb
如果成功,你会得到像这样的信息:

Typelib imported successfully to CPPCOMSERVERLib.dll
现在你需要一个 .NET 客户端—我们用 C# 创建一个包含以下代码的 .cs 文件:

using System;
using CPPCOMSERVERLib;

public class MainApp
{
    static public void Main()
    {
        CppName cppname = new CppName();
        cppname.SetName( “bob” );
        Console.WriteLine( “Name is ” + cppname.GetName() );
    }
}
注意我们使用 typelibrary 的名字作为命名空间,COM 类的名字作为类名。我们也可以选择使用 CPPCOMSERVERLib.CppName 作为类名而且不需要语句 using CPPCOMSERVERLib。

像这样编译以上 C# 代码:

csc /r:cppcomserverlib.dll csharpcomclient.cs
注意,编译被告知,引用我们刚才用 TLBIMP 从 typelibrary 产生的 DLL。

现在你应该可以运行 csharpcomclient.exe,并从控制台得到如下输出:

Name is bob

9.5 能在 COM 中使用 .NET 组件吗?
可以。可以通过一个 COM Callable Wraper (CCW) 从 COM 中访问 .NET 组件。这和 RCW 很相似 (参见上一个问题),但以相反的方向工作。同样,如果它不能由 .NET 开发工具自动产生,或不想要自动产生的行为逻辑,可以开发一个定制的 CCW。为使 COM 可以“看见” .NET 组件,.NET 组件必须在注册表里注册。

这里是一个简单的例子。创建一个名为 testcomserver.cs 的 C# 文件并输入下面的代码:

using System;

namespace AndyMc
{
    public class CSharpCOMServer
    {
        public CSharpCOMServer() {}
        public void SetName( string name ) { m_name = name; }
        public string GetName() { return m_name; } 
        private string m_name;
    }         
}
然后编译 .cs 文件:

csc /target:library testcomserver.cs
你会得到一个 dll,这样将它注册:

regasm testcomserver.dll /tlb:testcomserver.tlb
现在你需要创建一个客户端程序来测试你的 .NET COM 组件。VBScript 可以—将以下内容放到一个名为 comclient.vbs 的文件中:

Dim dotNetObj
Set dotNetObj = CreateObject(”AndyMc.CSharpCOMServer”)
dotNetObj.SetName (”bob”)
MsgBox “Name is ” & dotNetObj.GetName()
运行此脚本:

wscript comclient.vbs
嘿!你得到一个显示文本“Name is bob”的消息框。

(注意,编写此程序时,看起来可以通过几种路径将 .NET 类作为 COM 组件访问—为了避免问题,在 testcomserver.dll 相同的目录下运行 comclient.vbs。

一种替代的方法是使用 Jason Whittington 和 Don Box 开发的 dm.net moniker。到这里 http://staff.develop.com/jasonw/clr/readme.htm 查看。

9.6 在 .NET 的世界中 ATL 是多余的吗?
是的。如果你在编写 .NET 框架内的应用程序。当然许多开发者希望继续使用 ATL 来编写 .NET 框架以外的 C++ COM 组件,但当你在 .NET 框架内时你差不多总是希望使用 C#。在 .NET 世界里,原始的 C++ (以及基于它的 ATL) 并没有太多的地位—它太直接了,并且提供了太多的适应性,以至于运行库不能管理它。

10. 杂项
10.1 .NET 的远程计算如何工作?
.NET 的远程计算涉及通过通道发送消息。两种标准的通道是 HTTP 和 TCP。仅仅在局域网上才倾向于使用 TCP—HTTP 能在局域网和广域网 (internet) 上使用。

现在提供了对多种消息串行化格式的支持,例如 SOAP (基于 XML) 和二进制格式。默认情况下,HTTP 通道使用 SOAP (通过 .NET 运行库的 Serialization SOAP Formatter),而 TCP 通道使用二进制格式 (通过 .NET 运行库的 Serialization Binary Formatter)。但每个通道可以使用任一串行化格式。

这里是远程访问的一些方式:

SingleCall。每个来自客户端的请求由一个新对象服务。当请求完成后对象被丢弃。可以在 ASP+ 环境中使用 ASP+ 国家服务来保存应用程序或会话的国家,从而使这种模型 (无国家之分的) 变成有国家支持的。
 
Singleton。所有来在客户端的请求由单一的服务器对象处理。
 
Client-activated object。这是老的有国家支持的 (D)COM 模型,这里客户端受到一个远端对象的引用并保留此引用 (以保持远端对象的生存),直到对它的访问完成。
对象的分布式垃圾收集由称为“基于租用的生命周期”管理。每个对象拥有一个租用时间,这个时间到达时,从 .NET 运行库的远程子结构断开对象。对象有默认的更新时间—从客户端发起的成功调用会更新租用时间。客户端也可以显示地更新租用时间。

如果你对使用 XML-RPC 来代替 SOAP,可以看看 Charles Cook 在 http://www.cookcomputing.com/xmlrpc/xmlrpc.shtml 的 XML-RPC.Net 站点。

10.2 如何在 .NET 程序中获得 Win32 API?
使用 P/Invoke。它使用了和 COM 互操作性相似的技术,但被用来访问静态 DLL 入口点而不是 COM 对象。以下是一个调用 Win32 MessageBox 函数的 C# 程序示例:

using System;
using System.Runtime.InteropServices;

class MainApp
{
    [dllimport(”user32.dll”, EntryPoint=”MessageBox”, SetLastError=true, CharSet=CharSet.Auto)]
    public static extern int MessageBox(int hWnd, String strMessage, String strCaption, uint uiType);

public static void Main()
    {
        MessageBox( 0, “Hello, this is PInvoke in operation!”, “.NET”, 0 );
    }
}

11. 类库
11.1 文件 I/O
11.1.1 如何读文本文件?
首先,使用 System.IO.FileStream 对象打开文件:

FileStream fs = new FileStream( @”c:\test.txt”, FileMode.Open, FileAccess.Read );
FileStream 继承于 Stream,所以你可以用一个 StreamReader 对象把 FileStream 对象包装起来。这样为一行一行地进行流处理提供了一个良好的界面:

StreamReader sr = new StreamReader( fs );
string curLine;
while( (curLine = sr.ReadLine()) != null )
    Console.WriteLine( curLine );
最后关闭 StreamReader 对象:

sr.Close();
注意这样将自动地在底层 Stream 对象上调用 Close (),所以不必显示地执行 fs.Close()。

11.1.2 如何写文本文件?
和读文件的例子相似,只是把 StreamReader 换成 StreamWriter。

11.1.3 如何读写二进制文件?
和文本文件类似,只是要用 BinaryReader/Writer 对象而不是 StreamReader/Writer 来包装 FileStream 对象。

11.1.4 如何删除文件?
在 System.IO.File 对象上使用静态方法 Delete ():

File.Delete( @”c:\test.txt” );

11.2 文本处理
11.2.1 是否支持正规表达式?
是的。使用 System.Text.RegularExpressions.Regex 类。例如,以下代码更新 HTML 文件的标题:

FileStream fs = new FileStream( “test.htm”, FileMode.Open, FileAccess.Read );
StreamReader sr = new StreamReader( fs );

Regex r = new Regex( “<TITLE>(.*)</TITLE>” );
string s;
while( (s = sr.ReadLine()) != null )
{
    if( r.IsMatch( s ) )
        s = r.Replace( s, “<TITLE>New and improved ${1}</TITLE>” );
    Console.WriteLine( s );
}

11.3 Internet
11.3.1 如何下载网页?
首先使用 System.Net.WebRequestFactory 类来获得一个 WebRequest 对象:

WebRequest request = WebRequestFactory.Create( “http://localhost” );
然后请求应答:

WebResponse response = request.GetResponse();
GetResponse 方法被阻塞直到下载完成。然后你能像下面那样访问应答流:

Stream s = response.GetResponseStream();

// Output the downloaded stream to the console
StreamReader sr = new StreamReader( s );
string line;
while( (line = sr.ReadLine()) != null )
    Console.WriteLine( line );
注意 WebRequest 和 WebReponse 对象分别向下兼容 HttpWebRequest 和 HttpWebReponse 对象,它们被用来访问和 http 相关的功能。

11.3.2 如何使用代理服务器 (proxy)?
两种—这样做以便影响所有 Web 请求:

System.Net.GlobalProxySelection.Select = new DefaultControlObject( “proxyname”, 80 );
另外一种,要想对特定的 Web 请求设置代理服务,这样做:

ProxyData proxyData = new ProxyData();
proxyData.HostName = “proxyname”;
proxyData.Port = 80;
proxyData.OverrideSelectProxy = true;

HttpWebRequest request = (HttpWebRequest)WebRequestFactory.Create( “http://localhost” );
request.Proxy = proxyData;

11.4 XML
11.4.1 是否支持 DOM?
是的。看看以下示例 XML文档:

<PEOPLE>
    <PERSON>Fred</PERSON>
    <PERSON>Bill</PERSON>   
</PEOPLE>   
可以这样处理此文档:

XmlDocument doc = new XmlDocument();
doc.Load( “test.xml” );

XmlNode root = doc.DocumentElement;

foreach( XmlNode personElement in root.ChildNodes )
    Console.WriteLine( personElement.FirstChild.Value.ToString() );
输出为:

Fred
Bill

11.4.2 是否支持 SAX?
不。作为替换,提供了一个新的 XmlReader/XmlWriter API。像 SAX 一样,它是基于流的,但它使用“pull”模型而不是 SAX 的“push”模型。这是一个例子:

XmlTextReader reader = new XmlTextReader( “test.xml” );

while( reader.Read() )
{
    if( reader.NodeType == XmlNodeType.Element && reader.Name == “PERSON” )
    {
        reader.Read(); // Skip to the child text
        Console.WriteLine( reader.Value );
    }
}

11.4.3 是否支持 XPath?
是的,通过 XmlNavigator 类 (DocumentNavigator 是从 XmlNavigator 导出的):

XmlDocument doc = new XmlDocument();
doc.Load( “test.xml” );

DocumentNavigator nav = new DocumentNavigator(doc);
nav.MoveToDocument();

nav.Select( “descendant::PEOPLE/PERSON” );

while( nav.MoveToNextSelected() )
{
    nav.MoveToFirstChild();
    Console.WriteLine( “{0}”, nav.Value );
}

11.5 线程
11.5.1 是否支持多线程?
是的,对多线程有广泛的支持。系统能产生新线程,并提供应用程序可以使用的线程池。

11.5.2 如何产生一个线程?
创建 System.Threading.Thread 对象的一个实例,把将要在新线程中执行的 ThreadStart 示例传递给它。例如:

class MyThread
{
    public MyThread( string initData )
    {
        m_data = initData;
        m_thread = new Thread( new ThreadStart(ThreadMain) );
        m_thread.Start();
    }

// ThreadMain() is executed on the new thread.
    private void ThreadMain()
    {
        Console.WriteLine( m_data );
    }

public void WaitUntilFinished()
    {
        m_thread.Join();
    }

private Thread m_thread;
    private string m_data;
}
这里创建 MyThread 的一个实例就足以产生线程并执行 MyThread.ThreadMain () 方法:

MyThread t = new MyThread( “Hello, world.” );
t.WaitUntilFinished();

11.5.3 如何停止一个线程?
有好几个办法。首先,你能使用自己的通讯机制告诉 ThreadStart 方法结束。另外 Thread 类有内置的支持来命令线程停止。基本的两个方法是 Thread.Interrupt () 和 Thread.Abort ()。前者导致抛出一个 ThreadInterruptedException 并随后进入 WaitJoinSleep 状态。换句话说,Thread.Interrupt 是一种礼貌的方式,它请求线程在不再进行任何有用的工作时自行停止的。与此相对应,Thread.Abort () 抛出一个 ThreadAbortException 而不管线程正在做什么。而且,ThreadAbortException 不能像通常的异常那样被捕获 (即使最终将执行 ThreadStart 的终止方法)。Thread.Abort () 是一般情况下不需要的非常手段。

11.5.4 怎样使用线程池?
通过向 ThreadPool.QueueUserWorkItem () 方法传递 WaitCallback 的一个实例:

class CApp
{
    static void Main()
    {
        string s = “Hello, World”;
        ThreadPool.QueueUserWorkItem( new WaitCallback( DoWork ), s );

Thread.Sleep( 1000 );    // Give time for work item to be executed
    }

// DoWork is executed on a thread from the thread pool.
    static void DoWork( object state )
    {
        Console.WriteLine( state );
    }
}

11.5.5 怎样知道我的线程池工作项目是在何时完成的?
没有方法询问线程池这类信息。你必须在 WaitCallback 方法中放置代码来发出信号以表明它已经完成。这里事件也很有用处。

11.5.6 怎样防止对数据的并发访问?
每个对象有一个与之相联的并发锁 (受批评的部分)。System.Threading.Monitor.Enter/Exit 方法用来获得和释放锁。例如,下面类的实例只允许一个线程同时进入方法 f ():

class C
{
    public void f()
    {
        try
        {
            Monitor.Enter(this);
            …
        }
        finally
        {
            Monitor.Exit(this);
        }
    }
}
C# 有一个关键字‘lock’提供了以上代码的简单形式:

class C
{
    public void f()
    {
        lock(this)
        {
            …
        }
    }
}
注意,调用 Monitor.Enter (myObject) 并不意味着对 myObject 的所有访问都被串行化了。它意味着请求同 myObject 相联的同步锁,并且在调用 Monitor.Exit(o) 之前,没有任何其它线程可以请求该锁。换句话说,下面的类和以上给出的类在功能上是等同的:

class C
{
    public void f()
    {
        lock( m_object )
        {
            …
        }
    }

private m_object = new object();
}

11.6 跟踪
11.6.1 有内置的跟踪/日志支持吗?
是的,在 System.Diagnostics 命名空间中。有两个处理跟踪的主要的类—Debug 和 Trace。它们以相似的方式工作—不同之处是 Debug 类中的跟踪只能在用 DEBUG 标志生成的代码中工作,而 Trace 类中的跟踪只能在指明了 TRACE 标记生成的代码中工作。典型地,这意味着你应该在你希望能在 debug 和 release 版本中都能跟踪时使用 System.Diagnostics.Trace.WriteLine,而在你希望只能在 debug 版本中能跟踪时使用 System.Diagnostics.Debug.WriteLine。

11.6.2 能否将跟踪输出重定向到一个文件?
是的。Debug 类和 Trace 类都有一个 Listeners 属性,它们分别收集你用 Debug.WriteLine 或 Trace.WriteLine 产生的输出。默认情况下 Listeners 只有一个收集槽,它是 DefaultTraceListener 类的一个实例。它将输出发送到 Win32 的 OutputDebugString () 函数和 System.Diagnostics.Debugger.Log () 方法。调试时这很有用,但如果你试图从客户站点跟踪一个问题,将输出重定向到一个文件中就更为恰当。幸运的是,为此目的提供了 TextWriterTraceListener 类。

这里是 TextWriterTraceListener 如何将 Trace 输出重定向到一个文件:

Trace.Listeners.Clear();
FileStream fs = new FileStream( @”c:\log.txt”, FileMode.Create, FileAccess.Write );
Trace.Listeners.Add( new TextWriterTraceListener( fs ) );

Trace.WriteLine( @”This will be writen to c:\log.txt!” );
注意使用 Trace.Listeners.Clear () 去掉了默认的 listener。如果不这样做,输出将在文件和 OutputDebugString () 中同时产生。一般情况下你不希望如此,因为 OutputDebugString () 导致很大的性能开销。

11.6.3 能否定制跟踪的输出?
是的。你能编写你自己的 TraceListener 导出类,并把所有的输出重定向到它上面。这里有一个简单的例子,它从 TextWriterTraceListener 导出 (并随后内建了对写文件的支持) 并在每个输出行上添加时间信息和线程 ID:

class MyListener : TextWriterTraceListener
{
    public MyListener( Stream s ) : base(s)
    {
    }

public override void WriteLine( string s )
    {
        Writer.WriteLine( “{0:D8} [{1:D4}] {2}”,
            Environment.TickCount - m_startTickCount,
            AppDomain.GetCurrentThreadId(),
            s );
    }

protected int m_startTickCount = Environment.TickCount;
}
(注意这个实现并不完整—例如没有覆盖 TraceListener.Write 方法。)

这种方法的美妙之处在于,向 Trace.Listener 添加 MyListener 之后,所有对 Trace.WriteLine () 的调用都转向了 MyListener,包括从对 MyListener 一无所知的被引用元件发出的调用。

12. 资源
12.1 从哪里可以获得关于 .NET 的详情?
Microsoft .NET 主页位于 http://www.microsoft.com/net/。Microsoft 同时将它发布在 GOTDOTNET。

Microsoft 还发布了 .NET Framework FAQ,和本文很相似。可以在那里找到这里许多问题更“权威”的解答。

Robert Scoble 编辑了一个很容易理解的在线列表 http://www.devx.com/dotnet/resources/,这里还有一个 http://www.singularidad.com.ar/dotnet.asp。

在 http://www.devx.com/free/press/2000/vs-qalist.asp Robert 还有一个 .NET“著名问题与解答”主页。

Richard Grimes 和 Richard Anderson 有一个叫作 Managed World.COM.的站点。

http://www.ibuyspy.com/ 是一个以展示 .NET 平台为目的创建的示例站点。

还有我的 C# FAQ for C++ Programmers。

12.2 示例代码和实用程序
Peter Drayton 的 .NET Goodies 主页位于 http://www.razorsoft.net/
Don Box 的 CallThreshold 示例位于 http://www.develop.com/dbox/dotnet/threshold
Don 的 UnwindScope Service 位于 http://www.develop.com/dbox/dotnet/unwind
Don 的 CLR scripting host 位于 http://www.develop.com/dbox/dotnet/clrscript
Don 和 Jason 的 dm.net COM moniker 位于 http://staff.develop.com/jasonw/clr/readme.htm 在 http://www.bearcanyon.com/dotnet/ 有 Mike Woodring 的一些 .NET 例子。
在 http://www.cookcomputing.com/xmlrpc/xmlrpc.shtml 可以找到 Charles Cook 的 XML-RPC.Net library。

 

Permalink Comments

学习掌握.NET的第一步

March 27, 2005 at 1:01 pm · Filed under .Net 技术

本文将对Microsoft.NET和XML Web服务平台进行初步介绍。这一讲我们不想涉及很精深的技术,任何稍微懂一点电脑和Internet知识的人都可以理解本篇内容,这也是我们学习掌握.NET的第一步。

什么是.NET
Microsoft.NET是微软的XML Web服务平台,包含了建立和运行基于XML的软件所需要的全部部件。.NET解决了当今软件开发中的一些核心问题:

● 应用程序的互操作性、集成性和应用程序的可扩展性是很难实现的,而.NET依靠XML消除了数据共享和软件集成的障碍,使得问题得以容易地解决。

● 无数具有相当竞争力的私有软件技术使得软件的集成变得非常复杂,而.NET是建立在一个开放的标准上的,它包含了几乎所有的编程语言。

● 当终端用户使用软件时,他们总觉得不够简便。因为他们无法在程序之间方便地共享数据或无法对能访问的数据进行操作。XML使数据交换变得更加容易,.NET软件可以使得用户只要一得到数据就能对它们进行操作。

● 终端用户在使用Web的时候,无法对自己的个人信息和数据进行控制,这导致了个人隐私和安全泄漏问题。而.NET提供了一套服务,使用户可以管理他们的个人信息,并且控制对这些信息的访问。

● .COM公司和Web站点开发者们很难为用户提供足够的有价值的数据,部分原因是由于他们的应用程序和服务无法很好地和其他程序和服务合作,只是一个不和外界连接的信息孤岛。而.NET的设计宗旨就是为了使来自于多个站点和公司的数据或服务能够整合起来。

在.NET中,程序员设计编写的是XML Web服务,而不再是服务器或客户端的独立应用程序。他们把这些服务组合成松散耦合、相互协作的软件群,XML Web服务之间使用XML message进行通信。

.NET平台
Microsoft的平台是由用于创建和运行XML Web服务组成的,它包含了下面四个组件:

Microsoft.NET框架和Visual Studio.NET:这些是开发人员用来生成XML Web服务的工具(见图)。.NET框架是Microsoft.NET平台核心中的一套编程接口,而Visual Studio.NET是一套多语言系列的编程工具。

服务器基本结构:.NET的服务器基本结构是一系列用于生成、发布和操作XML Web服务的基础程序,包括Windows和各种.NET企业服务器。主要的技术包括对XML、scale-out及跨程序和服务的商务流程的支持。

块构建服务:块构建服务是一套以用户为中心的XML Web 服务,它把用户数据的控制权从应用程序移到了用户手上,使Web有了一个翻天覆地的变化,做到了程序、服务和设备之间的简单性及一致性,这保证了所有的交易都必须得到用户的同意。这些服务包含了Passport(用于用户身份验证)、服务之间的消息传递、文件存储、用户个性设置的管理、日历管理和其他一些功能。Microsoft将在那些对.NET基本结构起至关重要作用的领域内提供一些块构建服务。大量的合作伙伴和开发商将对这些块构建服务作重要的扩展。

.NET的好处
Microsoft.NET为程序员、商业领导、IT部门以及消费者带来了很多好处。

● Microsoft.NET使编程工作变得更加容易,开发投资的回报率趋于最大化。开发者们可以创建能重用的XML Web服务,而不再是单一的程序;这些Web服务易于编写和调试,彼此之间相互独立,通过XML message通信及合作。所以对某一个服务的修改不会影响到其他的服务。

由于XML Web服务可以被很多.NET客户端共同使用,所以对一个服务模块的有效更新,也即更新了所有使用这个模块的.NET客户端。任何编程语言都可以用来编写XML Web服务(如:C、C++、Visual Basic、Cobol、Perl、Python和Java等),程序员可以选择他们最熟悉的语言来编程,这大大提高了开发效率。更值得一提的是,他们并没有因为使用了不同的语言而失去跨服务或跨组件的调试能力。

● Microsoft.NET减少了程序员要写的代码量,一个XML Web服务能适用于所以的设备,不必再去为每一个设备编写一个不同的版本。另外,将显示特性与.NET体验分开以便以后加入新的接口技术,比如语音或手写识别,而不必去重写程序。

● Microsoft.NET 开创了全新的商业模型,它使得一个公司可以用多种方法来把自己的技术商品化。举个例子来说,一个通信公司可以使用XML Web服务的方式提供语音信件和呼叫者ID的访问 ,让用户从一个即时消息程序、电子邮件或用户所选的其他信息编译器中访问到上述信息。技术提供商可以把他们现有的软件包转变为XML Web服务,并把这些服务出售给需要这些功能的第三方或是给.NET客户端提供商,用以构建新的软件包。

● Microsoft.NET允许IT部门使用其他提供商的XML Web服务,减少内部研发的开销,并能提高工作效率。

● Microsoft.NET 对“用户界面友好”作了重新定义。终端用户能够享受一个智能化的、个性化的Internet,它能记住用户的个人设置,并在适当的时候,向用户使用的智能设备上发送适当的数据。

.NET如何改变计算
Microsoft.NET将从根本上改变我们的思考和使用电脑的方式。目前“服务器”和“桌面电脑”这两种概念占据了计算领域的统治地位,然而Microsoft.NET是一种分布式计算范例,它没有了传统上的服务器和桌面电脑的区别,取而代之的是,计算的处理被放在最合适的地方进行,可能是服务器、PC,也有可能是手提电脑以及其他智能设备。

.NET的计算模型对商务和终端用户都产生了重要影响,但方法不同。对终端用户来说,这个新计算模式更具个性化、综合程度更高,会给他们带来一种史无前例的新体验。对商务来说,这个模式改变了制造和销售软件的方法,使IT成为一个公司成功的重要贡献者,并建立起新的商务模型。

什么东西没有变
尽管Microsoft.NET给计算带来了一些翻天覆地的变化,但还有很多东西依然没有改变。

● 终端用户将依然使用熟悉的界面,这可以减少再培训的开支,也意味着用户可以马上开始使用.NET软件。

● 硬件上运行的还是像Windows、Unix、Windows CE和Palm OS一样的操作系统,实际上,.NET增加了软件的运行场所,但同时减少了开发的负担。由于XML Web服务只使用XML与设备通信,所以任何智能设备都可以享用XML Web服务。

● 对程序员来说,他们依然可以使用他们原先熟悉的编程语言。.NET平台借助于.NET框架的公共语言运行时(CLR)使得用不同语言开发的XML Web服务之间也可以相互操作。你依旧可以用Visual Basic、Java,甚至是Cobol创建XML Web服务,这种对编程语言的中立性意味着不用为了进入.NET世界而抛弃已有的投资。

● 原先系统无需被替换。一部分的Microsoft.NET产品就是为了能方便地将现有的系统整合到新的XML Web服务和.NET体验中去而设计的。Host Integration Server就是个例子,它简化了对主机的访问。

所以这种下一代的分布式计算是建立在目前这一代基础上的。Microsoft.NET 不是对现在的应用软件作大规模的替换,而是一个自然的进化过程,是在原先的技术孤岛之间建立了协作关系,协同工作能力逐渐加强,我们也将从中受益无穷。

Microsoft.NET 是微软的XML Web服务的平台。这是下一代的Internet计算模型,各个XML Web服务之间彼此是松散耦合的,通过XML进行通信,协同完成某一特定的任务。Microsoft.NET战略提供了一个用以建立新.NET体验的软件平台、一个编程模型、用以建立和整合XML Web服务的工具以及一套可编程的Web接口。

现在我们正处于向.NET转变的过程中。微软已经宣布了.NET框架的第一个部分——.NET平台、Visual Studio.NET和一些块构建服务以及最初的.NET体验。微软在今后会提供更多的工具和服务。

Permalink Comments

ASP.NET中Cookie编程的基础知识

March 27, 2005 at 1:01 pm · Filed under .Net 技术

简介

  Cookie 为 Web 应用程序保存用户相关信息提供了一种有用的方法。例如,当用户访问您的站点时,您可以利用 Cookie 保存用户首选项或其他信息,这样,当用户下次再访问您的站点时,应用程序就可以检索以前保存的信息。

  本文概要介绍 Cookie 在 ASP.NET 应用程序中的应用,为您展示在 ASP.NET 中应用 Cookie 的技术细节,例如编写 Cookie、然后再读取它们。同时,还将为您介绍 Cookie 的各种特性和各种特殊情况,以及 ASP.NET 对 Cookie 的支持。

  什么是 Cookie?

  Cookie 是一小段文本信息,伴随着用户请求和页面在 Web 服务器和浏览器之间传递。用户每次访问站点时,Web 应用程序都可以读取 Cookie 包含的信息。

  假设在用户请求访问您的网站 www.contoso.com 上的某个页面时,您的应用程序发送给该用户的不仅仅是一个页面,还有一个包含日期和时间的 Cookie。用户的浏览器在获得页面的同时还得到了这个 Cookie,并且将它保存在用户硬盘上的某个文件夹中。

  以后,如果该用户再次访问您站点上的页面,当该用户输入 URL www.contoso.com 时,浏览器就会在本地硬盘上查找与该 URL 相关联的 Cookie。如果该 Cookie 存在,浏览器就将它与页面请求一起发送到您的站点,您的应用程序就能确定该用户上一次访问站点的日期和时间。您可以根据这些信息向用户发送一条消息,也可以检查过期时间或执行其他有用的功能。

  Cookie 是与 Web 站点而不是与具体页面关联的,所以无论用户请求浏览站点中的哪个页面,浏览器和服务器都将交换 www.contoso.com 的 Cookie 信息。用户访问其他站点时,每个站点都可能会向用户浏览器发送一个 Cookie,而浏览器会将所有这些 Cookie 分别保存。

  以上就是 Cookie 的基本工作原理。那么,Cookie 有哪些用途呢?最根本的用途是 Cookie 能够帮助 Web 站点保存有关访问者的信息。更概括地说,Cookie 是一种保持 Web 应用程序连续性(即执行“状态管理”)的方法。浏览器和 Web 服务器除了在短暂的实际信息交换阶段以外总是断开的,而用户向 Web 服务器发送的每个请求都是单独处理的,与其他所有请求无关。然而在大多数情况下,都有必要让 Web 服务器在您请求某个页面时对您进行识别。例如,购物站点上的 Web 服务器跟踪每个购物者,以便站点能够管理购物车和其他的用户相关信息。因此 Cookie 的作用就类似于名片,它提供了相关的标识信息,可以帮助应用程序确定如何继续执行。

  使用 Cookie 能够达到多种目的,所有这些目的都是为了使 Web 站点记住您。例如,一个实施民意测验的站点可以简单地利用 Cookie 作为布尔值,表示您的浏览器是否已经参与了投票,从而避免您重复投票; 而那些要求用户登录的站点则可以通过 Cookie 来确定您是否已经登录过,这样您就不必每次都输入凭据。

  有关 Cookie 的更多背景信息,建议您阅读 Verizon Web 站点中的“How Internet Cookies Work”一文,地址为 http://www22.verizon.com/about/community/learningcenter/articles/displayarticle1/0,4065,1022z1,00.html(英文)。其作者详细解释了什么是 Cookie 以及 Cookie 是如何在浏览器和服务器之间交换信息的,他还全面总结了 Cookie 涉及的隐私问题。

  顺便问一下,您是否想知道它们为什么被称作“Cookie”?Jargon File(又称为“The New Hacker’s Dictionary”)版本 4.3.3 对这一术语的词源给出了准确的定义和合理的解释。您可以在 http://www.catb.org/~esr/jargon/jargon.html#cookie(英文)找到相关的条目。

  在此后的内容中,本文将假设您已经知道什么是 Cookie,并且假设您已经清楚为什么要在 ASP.NET 应用程序中使用 Cookie。

  Cookie 的限制

  在开始讨论 Cookie 的技术细节之前,我想先介绍一下 Cookie 应用的几条限制。大多数浏览器支持最多可达 4096 字节的 Cookie,如果要将为数不多的几个值保存到用户计算机上,这一空间已经足够大,但您不能用一个 Cookie 来保存数据集或其他大量数据。在实际应用中,您可能并不希望在 Cookie 中保存大量的用户信息,而只希望保存用户编号或其他标识符。之后,当用户再次访问您的站点时,您就可以使用该用户 ID 在数据库中查找用户的详细信息。(有关保存用户信息的说明,请参阅 Cookie 和安全性。)

  浏览器还限制了您的站点可以在用户计算机上保存的 Cookie 数。大多数浏览器只允许每个站点保存 20 个 Cookie。如果试图保存更多的 Cookie,则最先保存的 Cookie 就会被删除。还有些浏览器会对来自所有站点的 Cookie 总数作出限制,这个限制通常为 300 个。

  您最可能遇到的 Cookie 限制是:用户可以设置自己的浏览器,拒绝接受 Cookie。您很难解决这个问题,除非完全不使用 Cookie 而是通过其他机制来保存用户相关信息。保存用户信息的一种常用方法是会话状态,但会话状态又依赖于 Cookie。这一点在后面的 Cookie 和会话状态中阐述。

  注意:有关状态管理和 Web 应用程序中用于保存信息的选项的详细信息,请参阅 Introduction to Web Forms State(英文)和 State Management Recommendations(英文)。
更一般的经验很可能是,尽管 Cookie 在应用程序中非常有用,应用程序也不应该依赖于能够保存 Cookie。利用 Cookie 可以做到锦上添花,但不要利用它们来支持关键功能。如果您的应用程序必须使用 Cookie,则您可以通过测试来确定浏览器是否接受 Cookie。我在本文后面的检查浏览器是否接受 Cookie 一节中简单介绍了一种测试方法。

  编写 Cookie

  您可以利用页面的 Response(英文)属性来编写 Cookie,该属性提供的对象使用户可以将信息添加到由页面向浏览器呈现的信息中。Response 对象支持一个名为 Cookies(英文)的集合,您可以向其中添加要写入浏览器的 Cookie。

  注意:下面要讨论的 Response 对象和 Request 对象分别是包含 HttpResponse(英文)和 HttpRequest(英文)类实例的页面的属性。要在文档中查找 Response 和 Request 的信息,请参阅 HttpResponse 和 HttpRequest 下的内容。

  在创建 Cookie 时,您需要指定几个值。最初,您要指定 Cookie 的名称和其中保存的值。您可以创建多个 Cookie,每个 Cookie 都必须具有唯一的名称,以便日后读取时识别。(Cookie 是按名称保存的,所以如果您创建了两个名称相同的 Cookie,后保存的那一个将覆盖前一个。)

  您可能还希望指定 Cookie 的过期日期和时间。Cookie 一般都写入到用户的磁盘,然后可能一直都留在磁盘上。因此,您可以指定 Cookie 过期的日期和时间。当用户再次访问您的站点时,浏览器会先检查您站点的 Cookie 集合,如果某个 Cookie 已经过期,浏览器不会把这个 Cookie 随页面请求一起发送给服务器,而是删除这个已经过期的 Cookie。(您的站点可能已经在用户计算机上写入了多个 Cookie,每个 Cookie 都有各自的过期日期和时间。) 请注意,由浏览器负责管理硬盘上的 Cookie,这将影响您在应用程序中对 Cookie 的使用,我很快会介绍这方面的内容。

  一个 Cookie 的有效期应为多长?这取决于 Cookie 的用途,换句话说,取决于您的应用程序需要 Cookie 值保持有效的时间有多长。如果利用 Cookie 统计网站的访问者,您可以把有效期设置为 1 年,如果某个用户已有一年时间未访问您的站点,则可以把该用户当作新的访问者; 如果利用 Cookie 来保存用户的首选项,则可以把其设置为永远有效(例如 50 年后到期),因为定期重新设置首选项对用户而言是比较麻烦的。有时,您可能需要编写在数秒或数分钟内即过期的 Cookie。在本文后面的检查浏览器是否接受 Cookie 一节中,我列举了一个示例,该示例中创建的 Cookie 的实际有效期就只有几秒。

  注意:不要忘记用户随时可以删除自己计算机上的 Cookie,所以即使您保存了长期有效的 Cookie,用户也可以自行决定将其全部删除,同时清除保存在 Cookie 中的所有设置。

  如果没有设置 Cookie 的有效期,还是可以创建 Cookie,但它不会保存到用户的硬盘上,而是会成为用户会话信息的一部分。如果用户关闭浏览器或会话超时,该 Cookie 就会被删除。这种非永久性的 Cookie 很适合用来保存只需短时间保存的信息,或者保存由于安全原因不应该写入客户计算机磁盘的信息。例如,如果用户使用的是一台公用计算机,而您不希望把 Cookie 写入这种计算机的磁盘上,这时就可以使用非永久性的 Cookie。

  您可以通过多种方法把 Cookie 添加到 Response.Cookies 集合中。以下示例介绍了两种完成此任务的方法:

Response.Cookies(”userName”).Value = “mike”
Response.Cookies(”userName”).Expires = DateTime.Now.AddDays(1)

Dim aCookie As New HttpCookie(”lastVisit”)
aCookie.Value = DateTime.Now.ToString
aCookie.Expires = DateTime.Now.AddDays(1)
Response.Cookies.Add(aCookie)

  该示例向 Cookies 集合中添加了两个 Cookie,一个称为“userName”,另一个称为“lastVisit”。对于第一个 Cookie,我直接设置了 Response.Cookies 集合的值。您可以使用这种方法向集合中添加值,因为 Response.Cookies 是从 NameObjectCollectionBase(英文)类型的特殊集合派生得到的。

  对于第二个 Cookie,我创建了 Cookie 对象的一个实例(HttpCookie [英文] 类型),并设置了其属性,然后通过 Add 方法把它添加到 Response.Cookies 集合。实例化 HttpCookie 对象时,您必须把 Cookie 名称作为构造函数的一部分进行传递。

  这两个示例完成了相同的任务,即向浏览器写入一个 Cookie。您要采用哪种方法主要取决于您的个人喜好。您可能会发现第二种方法在设置 Cookie 属性方面要稍微容易一些,但同时您也会注意到两者的差别并不是很大。

  在这两种方法中,有效期值必须为 DateTime 类型。而“lastVisited”值也是日期/时间值。但在这种情况下,我必须把日期/时间值转换为字符串,因为 Cookie 中的任何值最终都是以字符串的形式保存的。

  查看您的 Cookie

  您可能会发现,了解创建 Cookie 的效果会对您很有帮助。而查看 Cookie 是比较容易的,因为它们都是文本文件,关键在于您能找到它们。不同的浏览器保存 Cookie 的方式也不同。我将介绍 Internet Explorer 是如何保存 Cookie 的。如果您使用的是其他浏览器,请查看该浏览器的帮助,以了解有关 Cookie 处理方面的知识。

  查看 Cookie 的一个简便方法是让 Internet Explorer 为您查找。在 Internet Explorer 中,从“工具”菜单中选择“Internet 选项”,在“常规”选项卡中单击“设置”,然后单击“查看文件”。Internet Explorer 将打开一个窗口,显示所有的临时文件,包括 Cookie。在窗口中查找以“Cookie:”开头的文件 或查找文本文件。双击一个 Cookie,在默认的文本文件中打开它。

  您也可以在硬盘上查找 Cookie 的文本文件,从而打开 Cookie。Internet Explorer 将站点的 Cookie 保存在文件名格式为 <user>@<domain>.txt 的文件中,其中 <user> 是您的帐户名。例如,如果您的名称为 mikepope,您访问的站点为 www.contoso.com,那么该站点的 Cookie 将保存在名为 mikepope@www.contoso.txt 的文件中。(该文件名可能包含一个顺序的编号,如 mikepope@www.contoso[1].txt。)

  这个 Cookie 文本文件是与用户相关的,所以会按照帐户分别保存。例如,在 Windows XP 中,您可以在如下所示的目录中找到 Cookie 文件:

c:\Documents and Settings\<user>\Cookies

  要查找最新创建的 Cookie,可以按修改日期对目录内容进行排序,并查找最近修改的文件。

  您可以使用文本编辑器打开 Cookie。如果该文件包含多个 Cookie,这些 Cookie 之间将用星号 (*) 分隔。每个 Cookie 的第一行是 Cookie 的名称,第二行是值,其余各行则包含 Cookie 的日常处理信息,例如过期日期和时间。Cookie 中还有一个简单的校验和,如果更改 Cookie 名称或值的长度,浏览器就会检测到修改并删除该 Cookie。

  多值 Cookie(子键)

  以上示例为每个要保存的值(用户名、上次访问时间)都使用了一个 Cookie 。您也可以在一个 Cookie 中保存多个名称/值对。名称/值对也称作“键”或“子键”,具体取决于您读取的内容。(如果您熟悉 URL 的结构,就会发现子键与其中的查询字符串非常相象。) 例如,如果不希望创建名为“userName”和“lastVisit”的两个单独的 Cookie,可以创建一个名为“userInfo”的 Cookie,并使其包含两个子键:“userName”和“lastVisit”。

  有很多原因会让我们用子键来代替单独的 Cookie。最显而易见的是,把相关或类似的信息放在一个 Cookie 中会比较有条理。另外,由于所有信息都在一个 Cookie 中,所以诸如有效期之类的 Cookie 属性就适用于所有信息。(当然,如果要为不同类型的信息指定不同的过期日期,就应该把信息保存在单独的 Cookie 中。)

  带有子键的 Cookie 还可以帮助您减小 Cookie 的大小。如前面的 Cookie 的限制一节所述,Cookie 的总大小限制在 4096 字节以内,而且不能为一个网站保存超过 20 个 Cookie。利用带子键的单个 Cookie,站点的 Cookie 数量就不会超过 20 个的限制。此外,一个 Cookie 会占用大约 50 个字符的基本空间开销(用于保存有效期信息等),再加上其中保存的值的长度,其总和接近 4K 的限制。如果使用五个子键而不是五个单独的 Cookie,您可以省去四个 Cookie 的基本空间开销,总共能节省大约 200 个字节。

  要创建带子键的 Cookie,您可以使用用于编写单个 Cookie 的各种语法。以下示例显示了编写同一 Cookie 的两种不同方法,其中的每个 Cookie 都带有两个子键:

Response.Cookies(”userInfo”)(”userName”) = “mike”
Response.Cookies(”userInfo”)(”lastVisit”) = DateTime.Now.ToString
Response.Cookies(”userInfo”).Expires = DateTime.Now.AddDays(1)

Dim aCookie As New HttpCookie(”userInfo”)
aCookie.Values(”userName”) = “mike”
aCookie.Values(”lastVisit”) = DateTime.Now.ToString
aCookie.Expires = DateTime.Now.AddDays(1)
Response.Cookies.Add(aCookie)

  控制 Cookie 有效范围

  默认情况下,一个站点的全部 Cookie 都一起保存在客户机上,而且所有这些 Cookie 都会随着对该站点发送的请求一起发送到服务器,也就是说,站点的每个页面都能得到该站点的所有 Cookie。但有时候,您可能希望 Cookie 更具有针对性,这时,您可以通过两种方法设置 Cookie 的有效范围:

  把 Cookie 的有效范围限制在服务器上的一个文件夹中,实际上这样就将 Cookie 限制到站点上的某个应用程序。

  把有效范围设置为某个域,从而允许您指定域中的哪些子域可以访问 Cookie。

  将 Cookie 限制到某个文件夹或应用程序

  要将 Cookie 限制到服务器上的某个文件夹,请按如下方法设置 Cookie 的 Path 属性:

Dim appCookie As New HttpCookie(”AppCookie”)
appCookie.Value = “written ” & Now.ToString
appCookie.Expires = Now.AddDays(1)
appCookie.Path = “/Application1″
Response.Cookies.Add(appCookie)

  当然,您也可以通过直接设置 Response.Cookies 来编写 Cookie,如前文所述。

  路径可以是站点根目录下的物理路径,也可以是虚拟根目录。这样一来,Cookie 就只能用于 Application1 文件夹或虚拟根目录中的页面。例如,如果您的站点名为 www.contoso.com,则前面示例中生成的 Cookie 就只能用于路径为 http://www.contoso.com/Application1/ 的页面以及该文件夹下的所有页面,而不适用于其他应用程序中的页面,如 http://www.contoso.com/Application2/ 或 http://www.contoso.com/ 下的页面。

  提示:通过对 Internet Explorer 和 Mozilla 浏览器进行测试发现,此处使用的路径是区分大小写的。一般而言,Windows 服务器上的 URL 不区分大小写,但这种情况例外。您无法控制用户如何在浏览器中输入 URL,但是,如果您的应用程序依赖于与特定路径相关的 Cookie,则请确保您所创建的所有超链接中的 URL 与 Path 属性值的大小写相匹配。

  将 Cookie 的有效范围限制到域

  默认情况下,Cookie 与特定的域相关联。例如,如果您的站点是 www.contoso.com,那么当用户向该站点请求页面时,您编写的 Cookie 就被发送到服务器。(有特定路径值的 Cookie 除外,我在上一节刚刚解释过。) 如果您的站点有子域(例如 contoso.com、sales.contoso.com 和 support.contoso.com),就可以把 Cookie 同特定的子域相关联。为此,需要设置 Cookie 的 Domain 属性,如下所示:

Response.Cookies(”domain”).Value = DateTime.Now.ToString
Response.Cookies(”domain”).Expires = DateTime.Now.AddDays(1)
Response.Cookies(”domain”).Domain = “support.contoso.com”

  如果按照这种方式设置域,则 Cookie 只能用于指定子域中的页面。

  您也可以利用 Domain 属性来创建可在多个子域中共享的 Cookie。例如,对域进行如下设置:

Response.Cookies(”domain”).Value = DateTime.Now.ToString
Response.Cookies(”domain”).Expires = DateTime.Now.AddDays(1)
Response.Cookies(”domain”).Domain = “contoso.com”

  这样,该 Cookie 就可用于主域、sales.contoso.com 和 support.contoso.com。

  读取 Cookie

  当浏览器向服务器发送请求时,该服务器的 Cookie 会与请求一起发送。在 ASP.NET 应用程序中,您可以使用 Request 对象来读取 Cookie。Request 对象的结构与 Response 对象的结构基本相同,所以从 Request 对象中读取 Cookie 的方法与向 Response 对象中写入 Cookie 的方法非常类似。以下示例显示了两种方法,目的都是获取名为“username”的 Cookie 的值并将值显示在 Label 控件中:

If Not Request.Cookies(”userName”) Is Nothing Then
Label1.Text = Server.HtmlEncode(Request.Cookies(”userName”).Value)
End If

If Not Request.Cookies(”userName”) Is Nothing Then
Dim aCookie As HttpCookie = Request.Cookies(”userName”)
Label1.Text = Server.HtmlEncode(aCookie.Value)
End If

  在获取 Cookie 的值之前,应该确保该 Cookie 确实存在。否则,您将得到一个 System.NullReferenceException(英文)异常。还需要注意的是,在页面中显示 Cookie 的内容之前,我调用了 HttpServerUtility.HtmlEncode(英文)方法对 Cookie 的内容进行编码。之所以这样做,是因为我要显示 Cookie 的内容(一般您不会这样做)而且要确保没有任何恶意用户在 Cookie 中添加了可执行脚本。有关 Cookie 安全性的详细信息,请参阅 Cookie 和安全性。

  注意:由于不同的浏览器保存 Cookie 的方式也不同,所以同一台计算机上的不同浏览器不一定能够相互读取各自的 Cookie。例如,如果使用 Internet Explorer 测试一个页面,然后再使用其他浏览器进行测试,那么后者就不会找到 Internet Explorer 保存的 Cookie。当然,大多数人一般都是使用同一种浏览器进行 Web 交互的,因此在大多数情况下不会出现问题。但有时还是会遇到问题,比如您要测试应用程序对浏览器的兼容性。

  读取 Cookie 中子键值的方法与设置该值的方法类似。以下是获取子键值的一种方法:

If Not Request.Cookies(”userInfo”) Is Nothing Then
Label1.Text = _
Server.HtmlEncode(Request.Cookies(”userInfo”)(”userName”))
Label2.text = _
Server.HtmlEncode(Request.Cookies(”userInfo”)(”lastVisit”))
End If

  在上面的示例中,我获取的是子键“lastVist”的值,在此前的讨论中我把该值设置为 DateTime 值的字符串表示形式。请记住,Cookie 是用字符串的形式保存值的,所以要将 lastVisit 值用作日期,就必须对其进行转换:

Dim dt As DateTime
dt = CDate(Request.Cookies(”userInfo”)(”lastVisit”))

  Cookie 中子键的类型是 NameValueCollection(英文)类型的集合。因此,另一种获取单个子键的方法是先获取子键集合,然后按名称提取子键的值,如下所示:

If Not Request.Cookies(”userInfo”) Is Nothing Then
Dim UserInfoCookieCollection As _
System.Collections.Specialized.NameValueCollection
UserInfoCookieCollection = Request.Cookies(”userInfo”).Values
Label1.Text = Server.HtmlEncode(UserInfoCookieCollection(”userName”))
Label2.Text = Server.HtmlEncode(UserInfoCookieCollection(”lastVisit”))
End If

  就像设置 Cookie 一样,使用哪种方法读取 Cookie 也由您自己决定。

  什么是有效期?

  您可以读取 Cookie 的名称和值,除此以外,需要了解的有关 Cookie 的信息并不是很多。虽然您可以获取 Domain 和 Path 属性,但是这些属性的用途很有限。例如,您可以读取 Domain 属性,但如果您的页面与 Cookie 不在相同的域,您根本就不会在页面的位置接收到该 Cookie。

  您无法读取的是 Cookie 的过期日期和时间。事实上,当浏览器向服务器发送 Cookie 信息时,浏览器并未将过期信息包括在内。您可以读取 Expires 属性,但总是返回为零的日期/时间值。

  在前面的编写 Cookie 一节中,我已经讲过,是浏览器负责管理 Cookie 的,Expires 属性就很好地印证了这一点。Expires 属性的主要作用是帮助浏览器执行有关 Cookie 保存的日常管理。从服务器的角度来看,Cookie 要么存在要么不存在,所以对服务器而言,有效期并不是有用的信息。所以,浏览器在发送 Cookie 时并不提供此信息。如果您需要 Cookie 的过期日期,就必须重新设置,关于这一点我将在修改和删除 Cookie 中介绍。

  更确切地说,您可以在向浏览器发送 Cookie 之前读取已在 Response 对象中设置的 Expires 属性,但您无法从返回的 Request 对象中获取有效期信息。

  读取 Cookie 集合

  前面的示例假设您要读取名称已知的 Cookie。有时,您可能需要读取可供页面使用的所有 Cookie。要读取可供页面使用的所有 Cookie 的名称和值,您可以利用如下代码遍历 Request.Cookies 集合:

Dim i As Integer
Dim output As String = “”
Dim aCookie As HttpCookie
For i = 0 to Request.Cookies.Count - 1
aCookie = Request.Cookies(i)
output &= “Cookie 名称 = ” & Server.HtmlEncode(aCookie.Name) & “<br>”
output &= “Cookie 值 = ” & Server.HtmlEncode(aCookie.Value) & _
& “<br><br>”
Next
Label1.Text = output

  注意:运行此代码时,您很可能会看到一个名为“ASP.NET_SessionId”的 Cookie,ASP.NET 用这个 Cookie 来保存您的会话的唯一标识符。这个会话 Cookie 不会永久保存到您的硬盘上。有关会话 Cookie 的详细信息,请参阅本文后面的 Cookie 和会话状态。
前面的示例有一个限制:如果 Cookie 有子键,就会以一个单独的名称/值字符串来显示子键。Cookie 的 HasKeys(英文)属性可以告诉您该 Cookie 是否有子键。如果有子键,您可以在子键集合中向下钻取,获取各个子键的名称和值。

  如前文所述,您可以从 Cookie 属性 Values(英文)中获取有关子键的信息,该属性是类型 NameValueCollection 的集合。您可以根据索引值从 Values 集合中直接读取子键值。相应的子键值可以从 Values 集合的成员 AllKeys(英文)中得到,该成员将返回一个字符串集合。

  以下示例是对前一示例的修改。示例中使用 HasKeys 属性来测试子键,如果检测到子键,就从 Values 集合中获取子键:

Dim i As Integer
Dim j As Integer
Dim output As String = “”
Dim aCookie As HttpCookie
Dim subkeyName As String
Dim subkeyValue As String
For i = 0 To Request.Cookies.Count - 1
aCookie = Request.Cookies(i)
output &= “名称 = ” & aCookie.Name & “<br>”
If aCookie.HasKeys Then
For j = 0 To aCookie.Values.Count - 1
subkeyName = Server.HtmlEncode(aCookie.Values.AllKeys(j))
subkeyValue = Server.HtmlEncode(aCookie.Values(j))
output &= “子键名称 = ” & subkeyName & “<br>”
output &= “子键值 = ” & subkeyValue & “<br><br>”
Next
Else
output &= “值 = ” & Server.HtmlEncode(aCookie.Value) & “<br><br>”
End If
Next
Label1.Text = output

  您也可以把子键作为 NameValueCollection 对象进行提取,如下所示:

If aCookie.HasKeys Then
Dim CookieValues As _
System.Collections.Specialized.NameValueCollection = aCookie.Values
Dim CookieValueNames() As String = CookieValues.AllKeys
For j = 0 To CookieValues.Count – 1
subkeyName = Server.HtmlEncode(CookieValueNames(j))
subkeyValue = Server.HtmlEncode(CookieValues(j))
output &= “子键名称 = ” & subkeyName & “<br>”
output &= “子键值 = ” & subkeyValue & “<br><br>”
Next
Else
output &= “值 = ” & aCookie.Value & “<br><br>”
End If

  注意:请记住,我之所以调用 Server.HtmlEncode 方法,只是因为我要在页面上显示 Cookie 的值。如果您只是测试 Cookie 的值,就不必在使用前对其进行编码。

  修改和删除 Cookie

  有时,您可能需要修改某个 Cookie,更改其值或延长其有效期。(请记住,由于浏览器不会把有效期信息传递到服务器,所以您无法读取 Cookie 的过期日期。)

  当然,实际上您并不是直接更改 Cookie。尽管您可以从 Request.Cookies 集合中获取 Cookie 并对其进行操作,但 Cookie 本身仍然存在于用户硬盘上的某个地方。因此,修改某个 Cookie 实际上是指用新的值创建新的 Cookie,并把该 Cookie 发送到浏览器,覆盖客户机上旧的 Cookie。

  以下示例说明了如何更改用于储存站点访问次数的 Cookie 的值:

Dim counter As Integer
If Request.Cookies(”counter”) Is Nothing Then
counter = 0
Else
counter = CInt(Request.Cookies(”counter”).Value)
End If
counter += 1
Response.Cookies(”counter”).Value = counter.ToString
Response.Cookies(”counter”).Expires = DateTime.Now.AddDays(1)

  或者:

Dim ctrCookie As HttpCookie
Dim counter As Integer
If Request.Cookies(”counter”) Is Nothing Then
ctrCookie = New HttpCookie(”counter”)
Else
ctrCookie = Request.Cookies(”counter”)
End If
counter = CInt(ctrCookie.Value) + 1
ctrCookie.Value = counter.ToString
ctrCookie.Expires = DateTime.Now.AddDays(1)
Response.Cookies.Add(ctrCookie)

  删除 Cookie

  删除 Cookie(即把该 Cookie 从用户的硬盘上物理删除)是修改 Cookie 的一种形式。由于 Cookie 位于用户的计算机中,所以您无法直接将其删除。但是,您可以让浏览器为您删除 Cookie。修改 Cookie 的方法前面已经介绍过(即用相同的名称创建一个新的 Cookie),不同的是将其有效期设置为过去的某个日期。当浏览器检查 Cookie 的有效期时,就会删除这个已过期的 Cookie。

  所以,删除 Cookie 的方法与创建该 Cookie 的方法是相同的,只不过要把其有效期设置为过去的某个日期。以下示例比删除单个 Cookie 要稍微有趣一些,它使用的方法可以删除当前域的所有 Cookie:

Dim i As Integer
Dim cookieName As String
Dim limit As Integer = Request.Cookies.Count - 1
For i = 0 To limit
aCookie = Request.Cookies(i)
aCookie.Expires = DateTime.Now.AddDays(-1)
Response.Cookies.Add(aCookie)
Next

  修改或删除子键

  修改单个子键的方法与最初创建它的方法相同:

Response.Cookies(”userInfo”)(”lastVisit”) = DateTime.Now.ToString
Response.Cookies(”userInfo”).Expires = DateTime.Now.AddDays(1)

  比较复杂的问题是如何删除单个子键。您不能只是简单地重新设置 Cookie 的过期日期,因为这样只能删除整个 Cookie 而不能删除单个子键。实际的解决方案是对包含子键的 Cookie 的 Values 集合进行操作。首先,通过从 Request.Cookies 对象中获取 Cookie 来重新创建 Cookie。然后,您就可以调用 Values 集合的 Remove 方法,将要删除的子键名称传递到 Remove 方法。接下来,您通常可以将修改后的 Cookie 添加到 Response.Cookies 集合,以便将修改后的 Cookie 发送回浏览器。

  以下代码显示了如何删除子键。在示例中,要删除的子键的名称在变量中指定。

Dim subkeyName As String
subkeyName = “userName”
Dim aCookie As HttpCookie = Request.Cookies(”userInfo”)
aCookie.Values.Remove(subkeyName)
aCookie.Expires = DateTime.Now.AddDays(1)
Response.Cookies.Add(aCookie)

  Cookie 与安全性

  在使用 Cookie 时,您必须意识到其固有的安全弱点。我所指的安全性并不是隐私问题,正如我在前面的什么是 Cookie?中所述,隐私在更大程度上是某些用户面对的问题:这些用户很关心 Cookie 中的信息是如何被使用的。而 Cookie 的安全性问题与从客户机获取数据的安全性问题类似。对于初学者,就应用程序而言,Cookie 是用户输入的另一种形式,因而很容易被他人非法获取和利用。由于 Cookie 保存在用户自己的计算机上,所以用户至少可以看到您保存在 Cookie 中的信息。如果用户愿意,还能在浏览器向您发送 Cookie 之前修改该 Cookie。

  所以,您千万不要在 Cookie 中保存保密信息 - 用户名、密码、信用卡号等等。在 Cookie 中不要保存不应该由用户掌握的内容,也不要保存可能被其他窃取 Cookie 的人控制的内容。

  同样,要对从 Cookie 中得到的任何信息都持怀疑态度。不要认为得到的数据就是您当初设想的信息。处理 Cookie 值时采用的安全措施应该与处理 Web 页面中用户键入的数据时采用的安全措施相同。例如,在页面中显示值之前,我会对 Cookie 中的内容进行 HTML 编码。这是一种标准的方法,可以在显示之前净化从用户处得到的信息,对 Cookie 的处理与此相同。

  另一个需要关心的问题是,Cookie 是以纯文本的形式在浏览器和服务器之间传送的,任何可以截取 Web 通信的人都可以读取 Cookie。您可以对 Cookie 的属性进行设置,使其只能在使用安全套接字层(SSL,又称 https://)的连接上传输。SSL 并不能防止保存在用户计算机上的 Cookie 被他人读取或操作,但它能防止 Cookie 在传输途中被他人截取。本文不讨论 SSL,但您必须清楚,您可以对 Cookie 进行传输保护。有关 SSL 的详细信息,请参阅 Secure Sockets Layer: Protect Your E-Commerce Web Site with SSL and Digital Certificates(英文)。

  面对这些安全问题,如何才能安全地使用 Cookie?您可以在 Cookie 中保存一些不重要的数据,如用户首选项或其他对应用程序没有重大影响的信息。如果确实需要把某些敏感信息(如用户 ID)保存在 Cookie 中,就对这些信息进行加密。一种可行的方法是利用 ASP.NET Forms Authentication 实用程序创建一个身份验证票据,作为 Cookie 保存。本文不讨论有关加密的问题,但是,如果您需要在 Cookie 中保存敏感信息,就应该试着采取措施来隐藏信息,防止被他人盗用。

  在 Mitigating Cross-site Scripting With HTTP-only Cookies(英文)一文中,您可以了解到更多有关 Cookie 及其安全弱点的信息。

  检查浏览器是否接受 Cookie

  我在前面的 Cookie 的限制一节中曾经提到一个潜在问题,即用户可以设置自己的浏览器拒绝接受 Cookie。如何才能知道您是否可以读写 Cookie?在不能写入 Cookie 时不会出现任何错误(例如 Response.Cookies 不会抛出异常),因为服务器并不跟踪呈现页面后出现的情况。浏览器同样不会向服务器发送任何有关其当前的 Cookie 设置的信息。(也许您需要了解,但 HttpBrowserCapabilities.Cookies Property [英文] 属性并不会告诉您 Cookie 是否被启用,而只能告诉您当前的浏览器是否支持 Cookie。)

  一种确定浏览器是否接受 Cookie 的方法是先编写一个 Cookie,然后再尝试读取这个 Cookie。如果不能读取这个 Cookie,则可以认为该浏览器不接受 Cookie。

  我编写了一个简单的示例来说明如何测试 Cookie 是否被接受。该示例包含两个页面。在第一个页面中,我编写了一个 Cookie,然后把浏览器重新定向到第二个页面。第二个页面尝试读取这个 Cookie,转而将浏览器重新定向到第一个页面,并向 URL 添加一个带有测试结果的查询字符串变量。

  第一个页面的代码如下:
Sub Page_Load()
If Not Page.IsPostBack Then
If Request.QueryString(”AcceptsCookies”) Is Nothing Then
Response.Cookies(”TestCookie”).Value = “ok”
Response.Cookies(”TestCookie”).Expires = _
DateTime.Now.AddMinutes(1)
Response.Redirect(”TestForCookies.aspx?redirect=” & _
Server.UrlEncode(Request.Url.ToString))
Else
labelAcceptsCookies.Text = “接受 Cookie = ” & _
Request.QueryString(”AcceptsCookies”)
End If
End If
End Sub

  第一个页面测试是否有回信,如果没有,就搜索包含测试结果的查询字符串变量 (AcceptsCookies)。如果没有找到查询字符串变量,则表示测试还没有完成,代码就写出一个名为“TestCookie”的 Cookie。写出 Cookie 之后,示例调用 Response.Redirect 来切换到测试页面 (TestForCookies.aspx)。附加到测试页面的 URL 的是名为 redirect 的查询字符串变量,该变量中包含了当前页面的 URL,这样就能在执行测试后把重定向到该页面。

  测试页面可以完全由代码组成,不需要包含控件。以下就是我使用的代码:

Sub Page_Load()
Dim redirect As String = Request.QueryString(”redirect”)
Dim acceptsCookies As String
‘ 是否接受 Cookie?
If Request.Cookies(”TestCookie”) Is Nothing Then
‘ 没有 Cookie,因此不需要接受
acceptsCookies = 0
Else
acceptsCookies = 1
‘ 删除测试 Cookie
Response.Cookies(”TestCookie”).Expires = _
DateTime.Now.AddDays(-1)
End If
Response.Redirect(redirect & “?AcceptsCookies=” & acceptsCookies, _
True)
End Sub

  读取 redirect 查询字符串变量后,代码就尝试读取 Cookie。为了实现日常管理,如果该 Cookie 确实存在,就会被立即删除。测试完成后,代码从 redirect 查询字符串变量传递的 URL 构造一个新的 URL。新的 URL 也包括一个包含测试结果的查询字符串变量。最后一步是使用新的 URL 将浏览器重定向到原来的页面。

  这个示例十分简单,但说明了通过运行程序并查看结果来进行测试的基本原则。其中最需要改进的地方是要永久保存 Cookie 测试结果,这样用户就不必在每次浏览原始页面时都重复进行测试。但是,实际上并不能做到这一点。Cookie 不会起作用,原因是显而易见的。另一种可能是把测试结果保存在会话状态中,但在默认情况下,会话状态也依赖于 Cookie,而如果浏览器不接受 Cookie,会话状态也不会起作用。解决后一个问题的办法是采用无 Cookie 的会话状态。下一节我将简要介绍会话状态如何与 Cookie 协作。

  Cookie 和会话状态

  当用户访问您的站点时,服务器会为该用户创建唯一的会话,会话将一直延续到用户访问结束。对于每个会话,ASP.NET 都维护一种基于服务器的结构(会话状态),在该结构中应用程序可以保存用户的相关信息。有关详细信息,请参阅 Session State(英文)。

  ASP.NET 需要能跟踪每个用户的会话 ID,这样才能把用户映射到服务器上的会话状态信息。默认情况下,ASP.NET 使用一个非永久性的 Cookie 来保存会话状态。如果您使用读取 Cookie 一节的“读取 Cookie 集合”中的示例,您可能就会在 Cookie 中发现一个会话状态 Cookie。

  但是如果用户禁用了浏览器的 Cookie,会话状态就不能使用 Cookie 来保存会话 ID,会话状态也不会起作用。这就是为什么我在前面的检查浏览器是否接受 Cookie 中说,无法在 Cookie 测试完毕后把测试结果实际保存在会话状态中,因为没有 Cookie 就没有会话状态。

  ASP.NET 提供了一种解决方案,即利用无 Cookie 的会话。您可以配置自己的应用程序,不在 Cookie 中保存会话 ID,而是在站点页面的 URL 中保存。会话 ID 保存在 URL 中,也就是 ASP.NET 将 ID 保存在浏览器中,从而能够在用户请求其他页面时取回 ID。

  无 Cookie 会话可以避免浏览器拒绝 Cookie 的问题,使您能够使用会话状态。如果您的应用程序依赖于会话状态,您可能就需要对其进行配置,使它能使用无 Cookie 会话。但是,在某些情况下,如果用户与其他人共享 URL - 可能是用户通过电子邮件将 URL 发送给同事,而该用户的会话仍然处于激活状态 - 那么最终这两个用户可能共享同一个会话,结果将难以预料。

Permalink Comments

ASP.NET中Cookie编程简明参考

March 27, 2005 at 1:01 pm · Filed under .Net 技术

一 写入Cookie

  1. Name 和 Value 属性由程序设定,默认值都是空引用。

  2. Domain属性的默认值为当前URL的域名部分,不管发出这个cookie的页面在哪个目录下的。

  例如,http://www.kent.com/application1/login.aspx 页面中发出一个cookie,Domain属性缺省就是www.kent.com ,可以由程序设置此属性为需要的值。

  3. Path属性的默认值是根目录,即 ”/” ,不管发出这个cookie的页面在哪个目录下的。可以由程序设置为一定的路径来进一步限制此cookie的作用范围。

  4. Expires 属性,这个属性设置此Cookie 的过期日期和时间。如果没有设置 Cookie 的有效期(默认设置),也可以创建 Cookie,但它不会保存到用户的硬盘上,而是会成为用户会话信息的一部分,关闭浏览器或会话超时这个Cookie即会消失,这种Cookie称作非永久性的 Cookie。存放SessionID的Cookie就是这样的一种Cookie,它不存放在硬盘上,只存在内存之中。

  5. 将要发出的Cookie附加到Response的Cookies属性中就可以将此Cookie发送到客户端:Reponse.Cookies.Add(Cookie)

  6. Domain属性+Path属性 相同的所有Cookie 在客户端都存在一个文件中,Cookie之间以”*”分割。每个Cookie的第一行是 Cookie 的名称,第二行是值,第三行是Domain属性+Path属性组成的一个字符串,指示此Cookie的作用域,其余各行则包含 Cookie 的日常处理信息,例如过期日期和时间。Cookie 中还有一个简单的校验和,如果更改 Cookie 名称或值的长度,浏览器就会检测到修改并删除该 Cookie。

  二 读取Cookie

  1. Request.Cookies 属性中包含了客户端发送到服务器的所有Cookie的集合,只有在请求URL的作用范围内的Cookie才会被浏览器连同Http请求一起发送到服务器。

  2. Name 和 Value 属性和子键的值很容易读到。

  3. Domain 和 Path 属性 是读不到的,读Domain属性永远是””,读Path属性永远是 ”/” 。本来这些属性的用途很有限。如果您的页面与 Cookie 不在相同的域,您根本就不会在页面的位置接收到该 Cookie。

  4. 也无法读取Cookie 的过期日期和时间。事实上,当浏览器向服务器发送 Cookie 信息时,浏览器并未将过期信息包括在内。您可以读取 Expires 属性,但总是返回为零的日期/时间值。Expires 属性的主要作用是帮助浏览器执行有关 Cookie 保存的日常管理。从服务器的角度来看,Cookie 要么存在要么不存在,所以对服务器而言,有效期并不是有用的信息。
所以,浏览器在发送 Cookie 时并不提供此信息。如果您需要 Cookie 的过期日期,就必须重新设置。

  三 修改和删除 Cookie

  1. 其实你不能直接修改一个Cookie,是创建一个同名的 Cookie,并把该 Cookie 发送到浏览器,覆盖客户机上旧的 Cookie。

  2. 同样您无法直接将其删除一个Cookie,可以通过修改一个Cookie达到让浏览器帮你删除Cookie的目的,修改Cookie的有效期为过去的某个时间,当浏览器检查 Cookie 的有效期时,就会删除这个已过期的 Cookie。

  四 Cookie同Session的关系

  1. asp.net中Session可以采用cookie 和cookieless两种方法,cookieless方式是将SessionID放在URL中在客户端和服务端中来回传递,不需要用到cookie,在这里不讨论这个方式。

  2. 在asp.net中客户第一次请求一个URL,服务器给这个客户生成一个SessionID,并以非永久性的 Cookie发送到客户端。

  3. 非永久性的 Cookie只有在浏览器关闭后这些Cookie才随之消失,Session的超时判断是这样的过程:

  3.1 第一次客户端访问服务器,会得到一个SessionID,以非永久性的 Cookie发送到客户端。

  3.2 在这个浏览器关闭之前访问这个URL,浏览器都会把这个SessionID发送到服务端,服务端根据SessionID来维持对应此客户的服务端的各种状态(就是Session中保存的各种值),在web应用程序中可以对这些Session进行操作。

  3.3 服务端维护此SessionID的过期时间,IIS中可以设置Session的超时时间。每次请求都将导致服务端将此SessioID的过期时间延长一个设置的超时时间。

  3.4 当服务端发现某个SessionID已经过时,即某个客户已经在设置的超时时间内没有再次访问此站点,即将此SessionID,连同跟此SessionID相关的所有Session变量删除。

  3.5 客户端的浏览器未关闭前,并不知道服务端已经将这个SessionID删除,客户端依旧发送此SessionID的cookie到服务端,只是此时的服务端已经不认识此SessionID了,会将此用户当做新用户,再次分配一个新的SessionID。

转载于:https://www.cnblogs.com/WilliamsQi/articles/542161.html

一些Dot Net 里面的概念相关推荐

  1. 关于物理像素/逻辑像素

    简单说明一下上图 其实没啥关系: 描述屏幕尺寸,通常从物理和逻辑两方面来,而DPI(dot per inch) 这个密度单位可以说是连接了物理和逻辑,表示每英寸的点数: 这个dot点,是个抽象的概念, ...

  2. 一文详解循环神经网络的基本概念(代码版)

    作者 | 李理 目前就职于环信,即时通讯云平台和全媒体智能客服平台,在环信从事智能客服和智能机器人相关工作,致力于用深度学习来提高智能机器人的性能. 写在前面 由于工作太忙,这个系列文章有一年多没有更 ...

  3. jsPlumb(3)-基本概念

    介绍 jsPlumb是用来连接元素的,所以核心是Connection,其中又分为以下四个概念: Anchor 一个和某个相关元素的位置.你不能创建它们,非可视化,只有逻辑上的position.更多信息 ...

  4. 有关手机屏幕的几个概念

    有关手机屏幕的几个概念   整理自:iiapk 的文章 原文链接:http://iiapk.com/?tag=dip Screen size:屏幕尺寸,指具体的屏幕物理长度,以屏幕对角线的长度作为标识 ...

  5. 基于keras的深度学习基本概念讲解

    基于keras的深度学习基本概念讲解 Tensorflow1.0正式发布,谷歌首届Tensorflow开发者大会在山景召开,深度学习迎来新的高潮和狂欢.随着深度学习框架的普及和推广,会有越来越多人加入 ...

  6. Opengl-基本概念-转换矩阵坐标系(最难理解的两章)

    前言 可能这是Opengl学习的时候最难理解的地方,很多人也因为这个地方放弃了.但是我觉得,我们可以不明白有些矩阵是如何推到出来的,但是我们要明白一些简单的矩阵为什么可以做到从一个坐标系到另一个坐标系 ...

  7. UA MATH571A 回归分析 概念与R code总结

    UA MATH571A 回归分析 概念与R code总结 Simple Linear Regression Multivariate Linear Regression Part 0 Basic R ...

  8. 点(Dot)与像素(Pixel)的区别

    DPI中的点(Dot)与图像分辨率中的像素(Pixel)是容易混淆的两个概念, DPI中的点可以说是硬件设备最小的显示单元, 而像素则既可是一个点,又可是多个点的集合.在扫描仪扫描图像时,扫描仪的每一 ...

  9. 浅谈移动Web开发:深入概念

    转载自http://kb.cnblogs.com/page/508655/ 如果你是一个开始接触移动Web开发的前端工程师,那么你或许也遇到了和我曾经遇到的过问题:有太多新的概念需要掌握,太多相似的概 ...

最新文章

  1. 【MM】更改供应商账户组
  2. 对于大规模机器学习的理解和认识
  3. .NET Conf 2021 回顾
  4. 写给准备参加秋招的学弟学妹们~一定要来看哦~
  5. linux so文件统一目录,linux加载指定目录的so文件
  6. RDP协议详细解析(四)
  7. 【HDFS】hdfs与fsck结合使用
  8. 基于Proteus的51单片机超声波测距
  9. SpringBoot逻辑删除
  10. vijos 1004 伊甸园日历游戏 博弈+打表找规律
  11. Android ViewBinding使用详解
  12. Gustafson 定律
  13. JAVA计算机毕业设计智慧茶园综合管理系统Mybatis+源码+数据库+lw文档+系统+调试部署
  14. 云服务器的系统镜像怎么选,买云服务器镜像怎么选择
  15. 视觉伺服入门第二步:带你从经典论文阅读Visual Servo Control Part II: Advanced Approaches进阶版
  16. 学会了手工计算开平方
  17. 安科瑞预付费系统在电力系统中的应用
  18. 关闭centos7下哔哔声
  19. c语言e怎么表示_如何一个月学完c语言
  20. [Spark的二次排序的实现]

热门文章

  1. aida64使用方法_aida64怎么用 【处理思路】
  2. 《公路工程适应自动驾驶附属设施总体技术规范(征求意见稿)》公开征求意见...
  3. 去除空格的正则表达式
  4. 基于Python的聊天室
  5. JavaScript 数组遍历方法的对比
  6. 自动化键盘鼠标简单操作(python)
  7. 怎样增加混凝土粘聚性_混凝土拌合物的粘聚性较差时,常用的改善措施是
  8. lc谐振计算机网络,LC谐振网络工作模式分析
  9. Apache ShardingSphere 首篇论文被 ICDE 收录,全球数据库发展迎来新局面
  10. 解析美团外卖智能配送的AI技术原理(附PPT)