Tag Archives: .net

答 zhangdi 同学的问题

Zhangdi 同学说,评论我的《Java 和 .Net 在异常处理机制上的区别》帖子说:我并不认为 C#(.NET)这样处理 Exception 是好的。我觉得这与 strong-type 的概念相冲突。几个问题:

在设计一个接口的时候,是不是需要同时告诉用户,这个接口有可能抛出什么类型的 Exception?

答:这种说法自然是没错的,异常应该是接口设计的一部分,但是告知用户的方式可以有很多种。Java 的这种方式只是其中较为有效的方式之一,他通过编译器做出严格的限制,这其中兼有优劣。

如果需要,那么这个 Exception 类型是否应该成为一个接口声明的一部分?

答:其实这还要从程序整体的设计角度来讨论。在 Java 中非 RuntimeException 其实已经不是一种普通的异常那么简单。因为最终在程序运行的时候,Java 不允许有非 RuntimeException 被抛出到虚拟机,这也就是说这种异常看上去只存在于设计时。他更像是程序运行期间的一种消息传递机制,只是首先这种消息“有别于(Except)”其他的返回类型,另外就是它会沿着调用栈强行向上追溯。显然这种异常更多的是在程序设计阶段起到操控程序非正常运行时行为的作用。因此他更像是一种接口,自然声明这样的接口也是理所应当的。

但是 Java 还有 RuntimeException 的概念,而 .Net 或许就是没有区分他们而已,因为一旦程序遇到异常,无论是被程序本身恢复,还是被虚拟机捕捉到导致强行非法结束,都是可以接受的,毕竟对用户而言除了造成不方便,并不会有安全上的隐患。

一个已知的有可能发生的 Exception 被操作系统捕获是否可以说是用户在处理这个接口的声明的时候,出现了疏忽呢?

答:这个自然,如果不是 Java 概念中的 RuntimeException,那么如果真有这样的异常被虚拟机捕捉到肯定就是 Bug,但至少是一个安全的 Bug。而同时,就像我在帖子里说到的,需要在开发难度和设计完整性上达到一定的平衡。.Net 这样自然忽视了后者,但同时显然使用就方便多了。

strong-type 的目标是在编译器发现尽可能多的关于类型的错误,如果我们把接口的定义看作类型的一部分,那么是否可以说,这个有可能将 checked exception 暴露给操作系统的编译器在 strong-type 上有缺陷呢?

答:没看懂!:-)

还是那句话,我觉得我写这篇帖子已经足够公正了。.Net 在异常处理的设计上显然不如 Java 更趋近完美,但毕竟还是可以用的,还是好用的,剩下的瑕疵是可以通过程序员的经验,以及良好的开发习惯和管理来弥补的。

Java 和 .Net 在异常处理机制上的区别

关于 Java 和 .Net 优劣的争论一直在继续,而在异常处理方面体现得最为激烈,因为他们之间的差异是如此明显。.Net 晚于 Java 出现,那么 Java 对 .Net 就理应起到很重要的借鉴作用,但是伟大的 Anders Hejlsberg 为什么没有继续 Java 的实现方式,而是另辟蹊径,这是一个非常值得研究的问题。因为我们要承认一个真理:正确的东西大家都是一样的正确,错误的却各有个的错误。可以肯定这不可能是 Anders 的疏忽,那么他的道理究竟何在,或者说他们之间究竟有什么区别?

在你能耐下心来看完这篇帖子之前,我想要明确告诉你一个结论:Java 和 .Net 在异常处理的本质上是没有区别的。

一、Java 是如何处理异常的

如果一个 Java 方法要抛出异常,那么需要在这个方法后面用 throws 关键字定义可以抛出的异常类型。倘若没有定义,就认为该方法不抛出任何异常。如果从方法的入口和出口的角度去考虑一下这个规范,我们知道参数可以认为是方法的入口(当然某些情况下也可以是出口),而返回值则是方法的出口,这是在程序正常执行的情况下,数据从入口入,出口出。要是程序非正常执行,产生异常又当如何? 被抛出的异常应该如何从方法中释放出来呢? Java 的这种语法规范就如同给异常开了一个后门,让异常可以堂而皇之“正确”地从方法里被抛出。

这样的规范决定了 Java 语法必须强行对异常进行 try-catch。设想一下,对于以下的方法签名:

public void foo() throws BarException { … }

暗含了两方面的意思:第一,该方法要抛出 BarException 类型的异常;第二,除了 BarException 外不能抛出其他的异常。而正是这第二点的缘故,我们要如何保证没有除 BarException 之外的任何异常被抛出呢? 很显然,就需要 try-catch 其他的异常。也就是说,一般情况下,方法不抛出哪些异常就要在方法内部 try-catch 这些异常。

Java 这样的机制既有优点,也有缺点。先来说说优点:

很显然,这种规范是由 Java 编译器决定的。倘若 Java 程序的入口点 main() 方法没有任何异常抛出,就是说要在 main() 方法内部,即整个程序内部捕捉所有的异常,否则将无法通过编译。这样编译器保证了程序对每个异常都有相应的计划和处理,不会有未处理的异常被泄露到虚拟机中,导致程序意外中断或退出,也就是增强了程序的健壮性。当然,Java 有 RuntimeException 的概念,这样的异常仍然可以随时被抛出到虚拟机中。

强行 try-catch 要求把异常作为程序设计的一部分看待。就如同方法的参数和返回值一样,在编写一个方法时,要结合上下文做出通盘的考虑和打算。虽然异常是所谓的“意外情况”,但是这些“例外”理应是被我们全部了解并处理的。

方便调试。异常理应在正确的位置被捕捉。当异常发生时,我们能更清楚的了解到其来源和相应处理程序的位置,而免去了在整个调用栈中摸索的麻烦。

在不借助任何文档的情况下,从方法签名就可以知晓应该对哪些异常进行处理。

Java 异常处理机制的这些优点也直接导致了他的致命弱点:将程序变得异常繁复。往往一个简单的程序,功能代码寥寥几行,而异常处理部分却占用了程序的绝大部分篇幅;同时导致缩进深度加深,既不利于书写,也不利于阅读。另外他的强行 try-catch 需要程序员有更高深的造诣,能够通盘考虑异常处理设计问题,这个在程序开始之初或者对于初学者是一个不小的门槛。这往往会阻碍其推广与发展,因为低水平初学者的信心往往因此而受到打击。然而对于高手来说,编译器是否能帮助他们找到未被处理的异常只是一个方便与否的问题,只要在编写方法时注意了异常处理,即便没有编译器的支持,情况也不会糟糕太多。反而倒是由于要遵循这样复杂的异常处理规范,以至于大多数人都可能为了图一时方便,对异常的基类型 Exception 或 Throwable 进行笼统地捕捉,这样做的危害就是那些你无法处理的异常被溺死在处理程序中,(按照异常处理原则,我们应该只捕捉那些可以被处理或恢复的异常,而把其他的异常继续抛出。至于这样做的优势,以及不这样做所带来的问题,不是一两句能够说清楚,这里就不展开讨论了。)导致程序的不稳定和不确定。既没有发挥 Java 语法在这方面的优势,反而增加了忧患。

二、.Net 是如何处理异常的

一句话概括 .Net 的异常处理方式就是随心所欲。没有人要求你一定要抛出异常,也更没有人要求你一定要捕捉异常。未被捕捉的异常会被以 Unhandled Exception 的形式抛出到虚拟机中。在此我就要先解决一下文章开头提到的问题,为什么说 Java 和 .Net 这两种异常处理机制在本质上是相同的。可以从两个方面来考虑:

默认情况下。Java 在默认情况下 main() 方法是不抛出异常的,正如前面所说的,这要求所有的异常都必须在 main() 方法内部被捕捉;而 .Net 则没有这种约束,他的 Main() 以至于整个应用程序中的任何一个方法对异常都是完全开放的。这样来看,这两者刚好是对立互补的。
非默认情况下。Java 可以通过在 main() 方法后面加 throws 关键字使得整个应用程序对异常开放;而 .Net 则可以通过给应用程序域(Application Domain)的 UnhandledException 事件添加委托达到捕捉所有异常的目的,很显然这又是对立互补的。因此,就好像一个是“正反”,一个是“反正”,加在一起“正反反正”都是一样的,对于达到控制异常的目录来说,是没有区别的。

很多 Java 爱好者都鄙视 .Net 的这种行为,一方面他令程序变得不够健壮,因为默认情况下没有强制的办法要求所有的异常都被处理,或被正确处理;另外,他为调试增加了困难,不借助文档或代码你将无法了解到一个方法可能抛出什么异常,而当一个异常被抛出的时候,同时异常处理代码又写得不够完善,你将不得不仔细查看整个调用栈来确定异常出现的位置,而对于这一点 Java 默认是强制的。

但是 Anders 的想法总是有道理的。

.Net 代码写起来非常容易。这是对于初学者,或者那些只是想实现一些测试性小功能的人而言,你完全没有必要考虑太多异常处理的细节,你要的就是写代码,然后让他跑起来。这样的简单性无疑是你希望看到的,这样的简单性无疑更有利于 .Net 在市场上的推广。由于他在这方面并没有什么理论上的漏洞,也就仍然适合构建庞大的项目,只是感觉没有那么舒服罢了。

一定程度上增加了程序的安全性。难道不捕捉异常可以被成为是安全的吗?这个话也许要从另外一方面来想,前面说过,有些 Java 程序员(绝对不占少数)为了图省事,在强行捕捉异常的压迫下,选择捕捉异常的基类型,也就是捕捉所有的异常。这样当有你无法处理的异常出现时,他们就溺死在了你的代码中,而外面的程序全然不知,还在以一种不确定的状态运行着,这就可能是危险的开始。而如果是 .Net,那么 Unhandled Exception 会被虚拟机捕获,导致程序异常退出,虽然这从面子上对于用户不是一个好的交代,但是深层次地他避免了程序在危险的状态下继续运行。

总之,萝卜白菜各有所爱。我的这篇帖子力求公正地讨论了这个问题,希望能对你有所帮助。

CS0013 or CS0016 Compilation Errors in ASP.NET Web Applications

SYMPTOMS
When you view a Microsoft ASP.NET Application in a Web browser, you may receive the following error messages:

For the Microsoft .NET Framework version 1.1, the error message is the following:

CS0016: Could not write to output file ‘c:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\Temporary
ASP.NET Files\application1\c11b43f6\cf3ec03\rizcntet.dll’ . The directory name is invalid.

For the .NET Framework 1.0, the error message is the following:

CS0013: Unexpected error writing metadata to file
‘C:\WINDOWS\Microsoft.NET\Framework\v1.0.3705\Temporary ASP.NET Files\application2\3fc72f26\eb731247\ev2bslce.dll’.
The directory name is invalid.

CAUSE
The system TEMP and TMP variables point to a folder that does not exist.

The compiler generates temporary files in the folder where the TEMP and the TMP variables point to before the files are copied to the Temporary ASP.NET Files folder. However, the folder where the system variables point to is deleted when you restart the computer. Therefore, the compiler cannot generate the temporary files.

RESOLUTION

  1. Create a temporary folder under %Systemroot%, and then name it Temp.
  2. Grant full permissions on the Temp folder to the aspnet user account in .NET Framework 1.0 or to the NETWORK SERVICE user account in .NET Framework 1.1.
  3. Right-click My Computer, and then click Properties.
  4. On the Advanced tab, click Environment Variables.
  5. Select the TEMP variable under System variables, and then click Edit.
  6. Type %SystemRoot%\TEMP in the Variable Value box, and then click OK.
  7. Repeat steps 5 and 6 to edit the TMP variable. Click OK two times.
  8. Click Start, and then click Run.
  9. To reset Internet Information Services (IIS), type iisreset on the command prompt.

Note If the error message that is mentioned in the “Symptoms” section of this article persists, restart the computer.

MORE INFORMATION
Steps to Reproduce the Behavior

  1. Start Microsoft Visual Studio .NET.
  2. Create a new ASP.NET Web Application project by using Microsoft Visual C# .NET or Microsoft Visual Basic .NET, and then name the project CompileTest.
  3. On the Build menu, click Build Solution.
  4. Right-click My Computer, and then click Properties.
  5. On the Advanced tab, click Environment Variables.
  6. Select the TEMP variable under System variables,
    and then click Edit.
  7. Type %SystemRoot%\TEMP1 in the Variable Value box to point to the nonexistent TEMP1 folder, and then click OK.
  8. 8.Repeat steps 6 and 7 to edit the TMP variable to point to the nonexistent TEMP1 folder.
  9. 9.Click OK two times.
  10. 10.To notice one of the error messages that are mentioned in the “Symptoms” section of this article, visit the following URL: http://localhost/CompileTest/WebForm1.aspx

.net Remoting 中的几个重要概念和实现方法

应用程序域 (Application domain)
一般的 Windows 应用程序都是以进程的方式在操作系统中运行,操作系统负责分配并管理程序所请求的资源。但是对于使用 .net 编写的托管程序而言,有一个特殊的进程被称为公共语言运行时 (CLR) ,该进程负责加载托管程序并运行。对于在 CLR 中运行的每一个托管程序而言,都有一个被称之为应用程序域的边界,每个托管程序都在自己的应用程序域中安全的运行。不同的应用程序域之间互不干扰。

上下文 (Application context)
将应用程序域进一步细分,就形成了上下文,上下文确保一套常用约束和使用语法负责管理其中的所有对象访问。每个应用程序域至少包含一个上下文,称为默认上 下文 (Default context) 。除非某个对象明确的要求一个专门的上下文,否则运行时 (Common language runtime) 将在默认上下文中创建那个对象。

.net Remoting 边界
对于应用程序而言应用程序域的边界是 .net Remoting 的边界。对应用程序域而言上下文的边界是 .net Remoting 边界,一个普通的对象无法穿越 .net Remoting 边界。

不可远程化对象和可远程化对象
一、不可远程化对象 (Non remotable object)
在默认情况下,没有经过任何特殊处理的对象都是不可远程化对象。不可远程化对象无法以任何方式(拷贝或引用)被跨应用程序域的对象所访问,当企图把对象引用传递到其他的应用程序域中时,会有异常产生。
二、可远程化对象 (emotable object) 如果一个类的实例可以穿越 .net Remoting 边界并可以在边界外被访问,则该对象就是可远程化的。在应用程序域外可远程化对象被访问的方式有两种:可以通过对象的完整副本来访问,还可以通过对象的引用(在 .net Remoting 中是以代理的模式来实现的)来访问。

.net Remoting 的服务器和客户端
.net Remoting 的服务器和客户端与以往的概念没有什么差别,但是在这里要强调的是 .net Remoting 的服器器和客户端显然是处于不同的应用程序域中,但是并不一定处于不同的计算机上。

按值列集 (Marshal by value) 和按引用列集 (Marshal by reference)
一、当运行时可以获得一个对象的完整副本的时候,则该对象就可以以所谓按值列集的方式被传递到其他的应用程序域中。实现按值列集的途径就是在声明类的时候添加 Serializable 特性 (Attribute) 或者实现 ISerializable 接口。例如:
[Serializable]
class Foo {

}

因此能够按值列集的对象也被称为可序列化的对象。客户端将获得该对象的完整副本。
二、当一个类直接或间接地从 MarshalByRefObject 类继承的时候,运行时就可以在客户端创建一个该对象的代理。例如:
class Foo : MarshalByRefObject {

}

上下文邦定 (Context bound)
当一个类型的实例只停留在具体的上下文内的时候,该类型就是上下文邦定的类型,在这个域内的其它上下文中的对象不能直接访问该对象。上下文邦定类型通过继承 System.ContextBoundObject 类来实现。
代理 (Proxy)
刚才提到,当对象以按引用列集的方式在应用程序域中传递的时候,运行时将在接收方创建一个该对象的代理。该代理可以想象为(通常也是这样实现的)一个封装了该对象所有或部分成员的接口,负责将接收方(客户端)的调用信息发送给发送方(服务器)。

通道 (Channel)
通道用于在跨应用程序域的远程对象间传递消息 (Message) 。服务器将选择侦听请求的通道,而客户端则选择希望与服务器进行通信的通道。运行时提供了两种内置的通道 Http 通道和 Tcp 通道。
using System.Runtime.Remoting; // .net Remoting 命名空间
using System.Runtime.Remoting.Channels; // .net Remoting 通道命名空间
using System.Runtime.Remoting.Channels.Http; // .net Remoting Http 通道命名空间
using System.Runtime.Remoting.Channels.Tcp; // .net Remoting Tcp 通道命名空间
. . .
// ChannelServices 是一个工具类,里面封装了大量的与通道相关的静态方法
ChannelServices.RegisterChannel ( new HttpChannel() ); // 注册 Http 通道并使用默认端口
ChannelServices.RegisterChannel ( new TcpChannel(4242) ); // 注册 Tcp 通道并使用4242端口

激活 (Activation)
一、服务器端激活 (Server Activation)
服务器端激活方式是指对象的生存周期(何时生成与何时被垃圾回收)由服务器来决定。服务器端激活有两种方式:单件 (Singleton) 和单调用 (SingleCall) 。
二、客户端激活 (Client Activation)
客户端激活方式是指对象的生存周期由客户端来决定。客户端激活只有一种方式,称为 CAO (Client Activation Object) 。每一个客户端激活创建一个对象,该对象存在于如下两个事件之一到来之前:客户端失掉对对象的引用,对象租借过期。客户端激活模式可以存储每一个客户端的状态,并接受构造函数参数。可以使用如下的代码,在服务器端设置客户端激活的远程对象:
RemotingConfiguration.RegisterActivatedServiceType( typeof( SomeMBRType ) );

然后在客户端设置如下:
RemotingConfiguration.RegisterActivatedClientType( typeof( SomeMBRType ), “http://SomeURL” );

此处的 SomeURL 是指服务器端的地址或计算机名。
众所周知 (Well-known object) 的对象
服务器端激活的类型我们就称之为众所周知的。在使用众所周知的对象时,服务器要进行如下的设置:对象的类型,何时以及如何实例化对象,和一个客户端用来与该类型联系的名称(或叫终端 end point )。而同时客户端要设置连接到哪一个服务器,并在终端上获得众所周知的类型。使用 RemotingConfiguration.RegisterWellKnownServiceType 这个方法来注册一个众所周知的类型,该方法需要提供三个参数:待注册的类型,传达给客户端的终端名称和激活模式。例如:
using System.Runtime.Remoting; // .net Remoting 名名空间
. . .
WellKnownServiceTypeEntry WKSTE = new WellKnownServiceTypeEntry(
typeof( MyNameSpace.SomeMBRType ),
“SomeURI”,
WellKnownObjectMode.SingleCall );
RemotingConfiguration.RegisterWellKnownServiceType(WKSTE);

其中先创建一个 WellKnownServiceTypeEntry 类型的对象,用于在 RegisterWellKnownServiceType 方法中传递参数。
typeof( MyNameSpace.SomeMBRType ) 这个参数待注册的远程对象的类型, SomeMBRType 是 MyNameSpace 命名空间下的一个类。
SomeURI 便是传达给客户端的终端名称。(它的用途在后面会介绍。)
WellKnownObjectMode.SingleCall 就是将该类型注册为单调用模式。
单件和单调用
一、单调用:服务器为每个客户端的方法调用生成一个单调用对象,每个对象服务且仅 服务一个请求。只有当方法调用到达的时候才按需求创建对象,并且对象的生存期直至调用结束。单调用模式适用于无需保存状态的应用程序,是解决负载平衡的最好选择。可以通过如下的方法在服务器端将可远程化类型配置为单调用模式:
// RemotingConfiguration 是一个工具类
RemotingConfiguration.RegisterWellKnownServiceType(
typeof( SomeMBRType ), // 从 MasharlByRef 继承来的数据的类型信息
“SomeURI”, // 发布该服务器端激活对象时使用的 URI
WellKnownObjectMode.SingleCall ); // 设定为单调用模式

然后用如下的方法在客户端再配置一次:
RemotingConfiguration.RegisterWellKnownClientType(
typeof( SomeMBRType ), // 要从服务器端得到的远程类型
“http://SomeWellKnownURL/SomeURI” ); // 众所周知的对象发布的 URL

二、单件:服务器在任何情况下都只创建该类型的一个实例,客户端的所有请求都由这 一个实例来处理,且该实例的生存期与服务器的生存期相同。单件模式适用于由状态的应用 程序,但是这种模式的对象只能保存非客户端的状态。同上面的代码,只要将 WellKnownObjectMode 设置为 Singleton 就可以了。
众所周知对象的 URL
服务器端激活的对象是在 URL 上发布的,该 URL 是被客户端众所周知的。众所周知对象的 URL 看上去如下:
ProtocolScheme://ComputerName:Port/ApplicationName/ObjectUri

其中 ProtocolScheme 代表 .net Remoting 通道所使用的协议,例如 Http 或 Tcp 等。
ComputerName 代表 .net Remoting 服务器的名称或地址。
Port 是注册通道时使用的端口。
ApplicationName 就是服务器端应用程序的名称。当使用 IIS 作为服务器端宿主的时候, ApplicationName 就变成了 IIS 的虚拟目录。
ObjectUri 就是在使用 RegisterWellKnownServiceType 时注册的 SomeURI 。 ObjectUri 必须以 .rem 或 .soap 结束,以区别是使用 Tcp 还是 Http 协议。
基于租借的生存期
客户端激活对象的生存期由与对象相关的租借来控制,租借有一个租借期, .net Remoting 基础设施在租借过期的时候放弃对象的引用,每一次方法调用可以更新租借,客户端可以使用代理更新租借,发起者也可以更新租期。

客户端激活对象的 URL
客户端激活对象不需要为每一个对象配备一个单独的 URL ,客户端激活对象的 URL 看上去如下:
ProtocolScheme://ComputerName:Port/ApplicationName

ASP.NET Starter Kit

微软的东西使得程序员的工作变得越来越轻松,当然同时也使得靠吃程序员这碗饭过活的人日子越来越难过了。最近我在微软 MSDN 中文网站上又发现了新东西(当然对于经常关注微软动态,且经常浏览微软几大英文门户的人们来说,这也许算是老掉牙的东西了),一套开放源代码的、完全免费的且说不好具有什么种类版权的门户网站入门套件: ASP.NET Starter Kit 。据说如果你有足够的实力,经过重写的源代码甚至是可以直接拿出来卖钱的。(怎么越看越像 BSD 版权呢?)有这么好的东西,怎么能不尝试一下呢?以下就是我对 ASP.NET Starter Kit 体验的随笔。

一、什么是 ASP.NET Starter Kit
关于这个问题我是无法给出什么权威性的解释的,但是现如今像这样的东西满网络里到处都是,大家肯定也不会陌生,只不过她系出名门,由微软的牛人们写出来的,地位自然显赫得多了。不要简单的望文生义,以为这套东西是为初学者准备的,虽然我还没有看到那里,但据说这个套件里的各个部分都使用了看起来令人兴奋的设计模式,绝对是学习和使用的不二之选。

本套件包含五个部分, Portal Starter Kit (门户网站入门套件), Commerce Starter Kit (电子商务入门套件), Reports Starter Kit (报表产生入门套件), TimeTracker Starter Kit (项目追踪入门套件) 和 Community Starter Kit (论坛入门套件)。每一个部分都可以直接拿过来部署,也可以经过自己的定制,重新发布。据说已经有几个网站开始采用该套件中的 Portal 部分部署自己的门户网站,相应的内容大家可以到微软的网站上找到。

二、如何获得 ASP.NET Starter Kit
当然是在微软的网站上,英文版的可以到以下网址找到: http://www.asp.net/default.aspx?tabindex=9&tabid=47 , 而中文版的可以到 MSDN 中文网站上去下载。

三、ASP.NET Starter Kit的版权声明
随着 ASP.NET Starter Kit 的分发,微软也同时也分发了该套件的版权声明,在 EULA.rtf 文档中,如果真的有人想要用它做些什么东西的话,最好还是好好看一看。我粗略的看了一下,怎么看怎么像 BSD 版权,包括什么必须保留微软版权、不允许加入其它的有版权产品,不允许使用微软的标志等等。

四、ASP.NET Starter Kit主要部分介绍
由于本人也是刚刚开始接触这个东西,加之目前有一个项目恰好可以用它来改,所以我首先选择了门户部分作为学习的突破口。

4.1 ASP.NET Portal
4.1.1 安装
据文档中说,该套件应该包含一个 .exe 或 .msi 的安装程序,不幸的是我在相应的 Setup 目录下没有找到这样的程序(很可能是我找到的这个发布包有问题),所以只好用功能强大的,但是相对难使的命令行工具。在 Setup 目录下可以找到 Cmd Installer 目录,进到里面就会看到 PRTL.exe 的命令行安装程序、两个写好的用于本地和远程安装的示例批处理文件 LocalFullInstall.bat 和 RemoteFullInstall.bat 、用于安装数据库的数据库脚本和 PRTL.exe 安装程序的用户文档。根据该用户文档,PRTL命令的参数如下:

4.1.1.1 INSTALL_OPTION
用于设定安装模式,有四个选项:

FULL 既安装 Web 又安装数据库。启用此选项需要提供所有的必选参数。
DB 仅安装指定的数据库实例。启用此选项需要提供 DB_FILES_DIR, DB_SERVER, DB_NAME, DB_OWNER_LOGIN, DB_OWNER_PWD, SQL_ADMIN_USER, SQL_ADMIN_PWD参数。
WEB 仅安装Web组件。启用此选项需要提供WEB_FILES_DIR, TARGET_DIR, IIS_SERVER, WEB_SITE, VDIR, DB_CONNECTION, DB_NAME, DB_SERVER, DB_OWNER_LOGIN, DB_OWNER_PWD 参数。
COPY_ONLY 仅将 WEB_FILES_DIR 目录下的所有内容拷贝到 IIS_SERVER 机器上的 TARGET_DIR目录下。启用此选项需要提供 IIS_SERVER, WEB_FILES_DIR, TARGET_DIR 参数。
4.1.1.2 WEB_FILES_DIR
待安装的 Web 页面所在的相对或绝对路径。一般在你解开的压缩包里会有一个名为 PortalCSVS (如果是 VB 工程的话似乎就应该叫 PortalVBVS )的目录,此下的文件就是所谓的待安装的 Web 页面。

4.1.1.3 DB_FILES_DIR
数据库脚本所在的相对或绝对路径。在 PRTL.exe 文件所在的目录里面有一个名为 SQL Scripts 的目录,它就是数据库脚本所在的目录。其中有四个 SQL 脚本文件。

4.1.1.4 TARGET_DIR
WEB_FILES_DIR 目录下的内容将要被拷贝到的目标路径(相对或绝对均可)。在该目标路径下 PRTL 会创建一个子目录。

4.1.1.5 IIS_SERVER
安装有 IIS 的机器的计算机名。同时该计算机也是 TARGET_DIR 所在的计算机。

4.1.1.6 WEB_SITE
一个已经存在的 Web 站点, VDIR 参数指定的虚拟目录将会在该站点中创建。此参数应该提供站点属性中描述栏中的内容。例如默认网站。

4.1.1.7 VDIR
将要安装的门户网站所在的虚拟目录的名字。建议该名称最好叫做 PortalCSVS (即与 分发包中 Web 页面内容所在的那个目录名相同,因为当用 VS.net 打开工程的时候,会默认到名称为 PortalCSVS 的虚拟目录下寻找站点)。

4.1.1.8 DB_CONNECTION
门户应用程序连接数据库的方式。只能使用SQL选项, Windows 集成认证是不支持的。

4.1.1.9 DB_SERVER
安装有 SQL Server 数据库的机器的计算机名。

4.1.1.10 DB_NAME
SQL Server 中将要安装所有数据库对象的数据库名称。(不是数据库服务器实例的名称。)

4.1.1.11 DB_OWNER_LOGIN
将要具有该数据库 Owner 角色的登陆名。如果该登陆名不存在,则会被创建。

4.1.1.12 DB_OWNER_PWD
登陆名的密码。

4.1.1.13 SQL_ADMIN_USER
具有可以创建数据库实例和用户权限的 SQL 用户名。

4.1.1.14 SQL_ADMIN_PWD
该用户的密码。

4.1.1.15 LOG_FILE (Optional)
安装日志的文件名。可以提供希望存储该日志的完整路径。如果没有提供该参数,日志文件将会被创建到同PRTL.exe相同的目录下。

例如在我的机器上,安装命令如下:
PRTL.exe INSTALL_OPTION=FULL WEB_FILES_DIR=”..\..\PortalCSVS” DB_FILES_DIR=”SQL Scripts” TARGET_DIR=”C:\inetput\wwwroot\portal” IIS_SERVER=”localhost” WEB_SITE=”默认网站” VDIR=”PortalCSVS” DB_CONNECTION=SQL DB_SERVER=”localhost” DB_NAME=”Portal” DB_OWNER_LOGIN=”PortalAdmin” DB_OWNER_PWD=”123″ SQL_ADMIN_USER=”SA” SQL_ADMIN_PWD=”sa” LOG_FILE=”installLog.txt”

生成的安装日志内容如下:
[2004-07-10 15:03:46] Setup Initialized …
[2004-07-10 15:03:46] Start Installing Web Components …
[2004-07-10 15:03:46] Copying web files from H:\Documents and Settings\MyUserProfile\My Documents\Visual Studio Projects\ASP.NET Portal (CSVS)\PortalCSVS to C:\inetput\wwwroot\portal\PortalCSVS
[2004-07-10 15:03:47] Updating web.config…
[2004-07-10 15:03:47] Updating EditHtml.aspx…
[2004-07-10 15:03:47] Creating Virtual Directory: PortalCSVS, on PASTHERO
[2004-07-10 15:03:48] Start Installing Database: localhost.Portal …
[2004-07-10 15:03:48] Executing SQL Script: 8b21cd7e-006f-4f09-8016-7a26f8dfd412CreateDB.sql
[2004-07-10 15:03:50] Executing SQL Script: f241dac2-1e7e-4e70-80ab-49e5ad575e98CreateDBObjects.sql
[2004-07-10 15:03:52] Executing SQL Script: c51bb364-0d31-4c67-9813-747f3ad813cfGrantPermission.sql
[2004-07-10 15:03:52] Executing SQL Script: 2f166c1a-ef9a-4422-841e-63026f5f13f2LoadData.sql
[2004-07-10 15:03:54] Setup Completed!