转:一次.NET Web应用程序安装包的制作经历:Sql数据库安装的3种方式 配置IIS及Web.Config文件
一次难得的安装包制作经历,因为之前从没有制作过安装包,那就免不了遇到问题,在摸索和学习中获得了不少宝贵经验,在这里我将用图文并茂的形式详细描述一下流程及主要难点问题的解决方法,希望对需要的朋友有所帮助.
首先建一个Web应用程序的安装项目
建好项目后在该安装项目的文件系统中加入Web应用程序的所有文件,全选后拖到"Web应用程序文件夹"内即可,也可以在"Web应用程序文件夹"点击右键在菜单中选择添加文件
如果有多个Web应用程序需要放在同一个安装包内安装,可以在"目标计算机上的文件系统"上点右键添加"Web自定义文件夹",如图中的WebControls
在Web文件夹的属性里,可以设置一下默认页及虚拟目录名称,其他的属性可以无视,使用默认值就可以了
到此,一个Web应用程序的安装包就基本完成了,直接生成的安装程序就可以将安装到\Inetpub\wwwroot目录下成为一个虚拟站点了,如果你的站点没有数据库的话,基本可以打完收工了^_^
之后遇到的第一个问题就是如何安装数据库.在网上找了一些资料,虽然说的比较详细,但都没有图例,习惯了傻瓜教程的我花了不少时间去摸索,下面就图文说明一下
将"文本框(A)"移动到安装流程中合适的位置,设置一下属性,请注意,这里的EditProperty的值是传递用户输入数据的关键字,必填
下面就进入重点了,这个流程的操作搞了半天才搞明白,在解决方案中再建一个类库项目,名字叫DBInstall,在该项目创建一个继承于System.Configuration.Install.Installer的类
暂时不写代码,将此类库与安装项目关联起来先,在"Web应用程序文件夹"下的任意文件夹里点击右键添加一个"项目输出"
默认会选中当前的DBInstall项目,不用改任何选项直接点确定,会在文件夹中新增一个"主输出来自DBInstall(活动)"的文件项
接下来在项目自定义操作的"安装"项上点右键添加自定义操作,选择刚刚新增"主输出来自DBInstall(活动)"的文件夹,选中确定添加到安装项内
添加完毕后设置"主输出来自DBInstall(活动)"的CustomActionData属性的值为 /dbname=[CUSTOMTEXTA1] /server=[CUSTOMTEXTA2] /user=[CUSTOMTEXTA3] /pwd=[CUSTOMTEXTA4] /targetdir="[TARGETDIR]\" ,通过该格式化字符串接收用户输入传递给安装程序的自定义数据,前4个传入值是"文本框(A)"中相关联的TextBox的值,第5个参数"TARGETDIR"返回的是Web应用程序安装路径
现在,可以去写安装Sql数据库的代码,在继承于System.Configuration.Install.Installer的InstallDb类中重载Install方法,在该方法内编写安装过程中需要执行的代码
先编写个执行SQL语句的方法,后面编写安装数据库的代码时需要用到,传入的参数分别是 connStr - 数据库链接字符串,DatabaseName - 链接的数据库名称,Sql - 待执行的SQL语句
在研究过程中,我尝试了3种安装数据库的方法,下面将一一讲解一下
第一种方法是通过附加数据库的方式来实现安装数据库,首先将需要安装的初始化数据库从Sql Server中分离,将该数据库的mdf和ldf文件添加到"Web应用程序文件夹"中,这里是放在DataBase文件夹中的,在Install方法中添加以下代码
可以看到,通过this.Context.Parameters["KeyName"]的方法,可以获取由应用程序传递的自定义数据,获取数据库的相关信息拼成链接字符串,再拼一个附加数据库的SQL语句,用的是master库中的存储过程sp_attach_db,3个参数分别是数据库的名称,及mdf和ldf完整路径,因此要拼完整具体目录并加上文件名,这样,执行一下这个Sql语句即能将数据库附加到Sql Server,但是这样做有个问题,卸载安装时安装程序会删除mdf和ldf文件,如果数据库在使用中,则卸载出错,如果数据库未使用,则被删除,后果严重,因此,只能用脚本去安装数据库
第二种方法是从网上看来的,在进程中通过osql.exe去执行SQL脚本文件安装数据库
此种方法需要开一个进程,性能比较高,并且将进程的WindowStyle设置为Hidden,用户就看不到执行时的Dos窗体了,最后,一定要加上等待执行完成的WaitForExit方法,否则,数据库还未安装完成时安装程序很可能就已经完成安装提示用户可以退出了
第三种方法是直接用先前所写的ExecuteSql方法执行安装脚本中的Sql语句
通过GetSqlFile方法获得脚本中的Sql,这里用了两个脚本文件,一个是创建数据库的,一个是创建默认数据的,两个脚本文件是可以合并成一个文件的,执行一次即可
需要提醒的是,创建数据库的脚本文件中,需要将"GO","SET ANSI_NULLS ON","SET QUOTED_IDENTIFIER ON"去掉,否则通过SqlCommand执行会出错,另外,如果默认数据的Insert脚本中包含中文,读取Sql语句时要加上System.Text.Encoding.GetEncoding("GB2312")这个参数,否则中文变乱码,默认数据的Insert脚本可以在网上找到导出所有数据Insert语句的存储过程
3种安装数据库的方法中后两种比较实用,第一种方法虽然方便快捷,但是卸载时万一将数据库也误卸载,那就损失惨重了
接下来说两个小问题,安装好了肯定要配置Web.Config中的数据库链接字符串,网上有现成的修改App.Config和Web.Config的方法,和安装数据库时的方法一样,拼好数据库链接字符串,调用修改Web.Config的方法,通过键名找到相应的键,将键值改为新的数据库链接字符串即可
另一个问题是在不同系统环境下测试安装包时发现的,如果系统中有.NET 1.1和.NET 2.0两个环境,安装的虚拟站点默认使用1.1的.NET版本,如果系统先安装了.NET 2.0再安装IIS,那默认网站的ASP.NET版本选项是空的,没有选中2.0版本,因此安装的虚拟目录ASP.NET版本选项也是空的,问了不少网友,都说需要执行aspnet_regiis.exe -i去注册2.0版本的.NET,但是如果安装好了再让用户去做这些操作总觉得有些别扭,看了一下aspnet_regiis.exe的参数说明,发现可以对某个虚拟站点进行.NET版本注册,就写下面这段代码去注册当天安装的虚拟目录
第一行的代码是获取aspnet_regiis.exe的具体路径,然后通过一个进程去执行注册操作,这样,即使出现前两种情况,都不用担心没有将虚拟站点的ASP.NET版本注册正确Web应用程序无法运行了
这两部分代码可以建一个像安装数据库一样继承于System.Configuration.Install.Installer的自定义操作来执行,不过,方便起见,直接放在InstallDB类中就可以了
首先建一个Web应用程序的安装项目
![](https://www.cnblogs.com/images/cnblogs_com/magicam/Blog01.jpg)
建好项目后在该安装项目的文件系统中加入Web应用程序的所有文件,全选后拖到"Web应用程序文件夹"内即可,也可以在"Web应用程序文件夹"点击右键在菜单中选择添加文件
![](https://www.cnblogs.com/images/cnblogs_com/magicam/Blog02.jpg)
如果有多个Web应用程序需要放在同一个安装包内安装,可以在"目标计算机上的文件系统"上点右键添加"Web自定义文件夹",如图中的WebControls
![](https://www.cnblogs.com/images/cnblogs_com/magicam/Blog03.jpg)
在Web文件夹的属性里,可以设置一下默认页及虚拟目录名称,其他的属性可以无视,使用默认值就可以了
![](https://www.cnblogs.com/images/cnblogs_com/magicam/Blog04.jpg)
到此,一个Web应用程序的安装包就基本完成了,直接生成的安装程序就可以将安装到\Inetpub\wwwroot目录下成为一个虚拟站点了,如果你的站点没有数据库的话,基本可以打完收工了^_^
之后遇到的第一个问题就是如何安装数据库.在网上找了一些资料,虽然说的比较详细,但都没有图例,习惯了傻瓜教程的我花了不少时间去摸索,下面就图文说明一下
先设计用户录入SQL数据库信息的界面,这个VS.NET有现成的界面,稍微修改一下即可,非常简单,打开安装包项目的用户界面
![](https://www.cnblogs.com/images/cnblogs_com/magicam/Blog05.jpg)
在"启动"项里添加一个"文本框(A)"对话框
![](https://www.cnblogs.com/images/cnblogs_com/magicam/Blog06.jpg)
将"文本框(A)"移动到安装流程中合适的位置,设置一下属性,请注意,这里的EditProperty的值是传递用户输入数据的关键字,必填
![](https://www.cnblogs.com/images/cnblogs_com/magicam/Blog07.jpg)
下面就进入重点了,这个流程的操作搞了半天才搞明白,在解决方案中再建一个类库项目,名字叫DBInstall,在该项目创建一个继承于System.Configuration.Install.Installer的类
![](https://www.cnblogs.com/images/cnblogs_com/magicam/Blog08.jpg)
暂时不写代码,将此类库与安装项目关联起来先,在"Web应用程序文件夹"下的任意文件夹里点击右键添加一个"项目输出"
![](https://www.cnblogs.com/images/cnblogs_com/magicam/Blog09.jpg)
默认会选中当前的DBInstall项目,不用改任何选项直接点确定,会在文件夹中新增一个"主输出来自DBInstall(活动)"的文件项
![](https://www.cnblogs.com/images/cnblogs_com/magicam/Blog10.jpg)
接下来在项目自定义操作的"安装"项上点右键添加自定义操作,选择刚刚新增"主输出来自DBInstall(活动)"的文件夹,选中确定添加到安装项内
![](https://www.cnblogs.com/images/cnblogs_com/magicam/Blog11.jpg)
添加完毕后设置"主输出来自DBInstall(活动)"的CustomActionData属性的值为 /dbname=[CUSTOMTEXTA1] /server=[CUSTOMTEXTA2] /user=[CUSTOMTEXTA3] /pwd=[CUSTOMTEXTA4] /targetdir="[TARGETDIR]\" ,通过该格式化字符串接收用户输入传递给安装程序的自定义数据,前4个传入值是"文本框(A)"中相关联的TextBox的值,第5个参数"TARGETDIR"返回的是Web应用程序安装路径
![](https://www.cnblogs.com/images/cnblogs_com/magicam/Blog12.jpg)
现在,可以去写安装Sql数据库的代码,在继承于System.Configuration.Install.Installer的InstallDb类中重载Install方法,在该方法内编写安装过程中需要执行的代码
先编写个执行SQL语句的方法,后面编写安装数据库的代码时需要用到,传入的参数分别是 connStr - 数据库链接字符串,DatabaseName - 链接的数据库名称,Sql - 待执行的SQL语句
1 private void ExecuteSql(string connStr, string DatabaseName, string Sql) {
2 SqlConnection conn = new SqlConnection(connStr);
3 SqlCommand cmd = new SqlCommand(Sql, conn);
4
5 conn.Open();
6 conn.ChangeDatabase(DatabaseName);
7 try {
8 cmd.ExecuteNonQuery();
9 }
10 finally {
11 conn.Close();
12 }
13 }
2 SqlConnection conn = new SqlConnection(connStr);
3 SqlCommand cmd = new SqlCommand(Sql, conn);
4
5 conn.Open();
6 conn.ChangeDatabase(DatabaseName);
7 try {
8 cmd.ExecuteNonQuery();
9 }
10 finally {
11 conn.Close();
12 }
13 }
在研究过程中,我尝试了3种安装数据库的方法,下面将一一讲解一下
第一种方法是通过附加数据库的方式来实现安装数据库,首先将需要安装的初始化数据库从Sql Server中分离,将该数据库的mdf和ldf文件添加到"Web应用程序文件夹"中,这里是放在DataBase文件夹中的,在Install方法中添加以下代码
1 string connStr = string.Format("data source={0};user id={1};password={2};persist security info=false;packet size=4096", this.Context.Parameters["server"], this.Context.Parameters["user"], this.Context.Parameters["pwd"]);
2 string strSql = "EXEC sp_attach_db @dbname = N'" + this.Context.Parameters["dbname"] + "',"
3 + "@filename1 = N'" + this.Context.Parameters["targetdir"] + "DataBase\\DemoData.mdf',"
4 + "@filename2 = N'" + this.Context.Parameters["targetdir"] + "DataBase\\DemoData_log.ldf'";
5 ExecuteSql(connStr, "master", strSql);
2 string strSql = "EXEC sp_attach_db @dbname = N'" + this.Context.Parameters["dbname"] + "',"
3 + "@filename1 = N'" + this.Context.Parameters["targetdir"] + "DataBase\\DemoData.mdf',"
4 + "@filename2 = N'" + this.Context.Parameters["targetdir"] + "DataBase\\DemoData_log.ldf'";
5 ExecuteSql(connStr, "master", strSql);
可以看到,通过this.Context.Parameters["KeyName"]的方法,可以获取由应用程序传递的自定义数据,获取数据库的相关信息拼成链接字符串,再拼一个附加数据库的SQL语句,用的是master库中的存储过程sp_attach_db,3个参数分别是数据库的名称,及mdf和ldf完整路径,因此要拼完整具体目录并加上文件名,这样,执行一下这个Sql语句即能将数据库附加到Sql Server,但是这样做有个问题,卸载安装时安装程序会删除mdf和ldf文件,如果数据库在使用中,则卸载出错,如果数据库未使用,则被删除,后果严重,因此,只能用脚本去安装数据库
第二种方法是从网上看来的,在进程中通过osql.exe去执行SQL脚本文件安装数据库
1 System.Diagnostics.Process sqlProcess = new System.Diagnostics.Process();
2 sqlProcess.StartInfo.FileName = "osql.exe";
3 sqlProcess.StartInfo.Arguments = string.Format(" -U {0} -P {1} -d {2} -i {3}/db.sql", this.Context.Parameters["user"], this.Context.Parameters["pwd"], this.Context.Parameters["dbname"], this.Context.Parameters["targetdir"]);
4 sqlProcess.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
5 sqlProcess.Start();
6 sqlProcess.WaitForExit(); //等待执行
2 sqlProcess.StartInfo.FileName = "osql.exe";
3 sqlProcess.StartInfo.Arguments = string.Format(" -U {0} -P {1} -d {2} -i {3}/db.sql", this.Context.Parameters["user"], this.Context.Parameters["pwd"], this.Context.Parameters["dbname"], this.Context.Parameters["targetdir"]);
4 sqlProcess.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
5 sqlProcess.Start();
6 sqlProcess.WaitForExit(); //等待执行
此种方法需要开一个进程,性能比较高,并且将进程的WindowStyle设置为Hidden,用户就看不到执行时的Dos窗体了,最后,一定要加上等待执行完成的WaitForExit方法,否则,数据库还未安装完成时安装程序很可能就已经完成安装提示用户可以退出了
第三种方法是直接用先前所写的ExecuteSql方法执行安装脚本中的Sql语句
1 string connStr = string.Format("data source={0};user id={1};password={2};persist security info=false;packet size=4096", this.Context.Parameters["server"], this.Context.Parameters["user"], this.Context.Parameters["pwd"]);
2 ExecuteSql(connStr, "master", "CREATE DATABASE " + this.Context.Parameters["dbname"]);
3
4 StringBuilder sb = this.GetSqlFile("Demo.sql");
5 ExecuteSql(connStr, this.Context.Parameters["dbname"], sb.ToString());
6
7 sb = this.GetSqlFile("Demo_Data.sql");
8 ExecuteSql(connStr, this.Context.Parameters["dbname"], sb.ToString());
2 ExecuteSql(connStr, "master", "CREATE DATABASE " + this.Context.Parameters["dbname"]);
3
4 StringBuilder sb = this.GetSqlFile("Demo.sql");
5 ExecuteSql(connStr, this.Context.Parameters["dbname"], sb.ToString());
6
7 sb = this.GetSqlFile("Demo_Data.sql");
8 ExecuteSql(connStr, this.Context.Parameters["dbname"], sb.ToString());
通过GetSqlFile方法获得脚本中的Sql,这里用了两个脚本文件,一个是创建数据库的,一个是创建默认数据的,两个脚本文件是可以合并成一个文件的,执行一次即可
1 // 获取Sql文件里的脚本
2 private StringBuilder GetSqlFile(string pFileName) {
3 StringBuilder sqlTemp = new StringBuilder();
4
5 sqlTemp.Append(File.ReadAllText(this.Context.Parameters["targetdir"] + "DataBase\\" + pFileName, System.Text.Encoding.GetEncoding("GB2312")));
6
7 return sqlTemp;
8 }
2 private StringBuilder GetSqlFile(string pFileName) {
3 StringBuilder sqlTemp = new StringBuilder();
4
5 sqlTemp.Append(File.ReadAllText(this.Context.Parameters["targetdir"] + "DataBase\\" + pFileName, System.Text.Encoding.GetEncoding("GB2312")));
6
7 return sqlTemp;
8 }
需要提醒的是,创建数据库的脚本文件中,需要将"GO","SET ANSI_NULLS ON","SET QUOTED_IDENTIFIER ON"去掉,否则通过SqlCommand执行会出错,另外,如果默认数据的Insert脚本中包含中文,读取Sql语句时要加上System.Text.Encoding.GetEncoding("GB2312")这个参数,否则中文变乱码,默认数据的Insert脚本可以在网上找到导出所有数据Insert语句的存储过程
3种安装数据库的方法中后两种比较实用,第一种方法虽然方便快捷,但是卸载时万一将数据库也误卸载,那就损失惨重了
接下来说两个小问题,安装好了肯定要配置Web.Config中的数据库链接字符串,网上有现成的修改App.Config和Web.Config的方法,和安装数据库时的方法一样,拼好数据库链接字符串,调用修改Web.Config的方法,通过键名找到相应的键,将键值改为新的数据库链接字符串即可
另一个问题是在不同系统环境下测试安装包时发现的,如果系统中有.NET 1.1和.NET 2.0两个环境,安装的虚拟站点默认使用1.1的.NET版本,如果系统先安装了.NET 2.0再安装IIS,那默认网站的ASP.NET版本选项是空的,没有选中2.0版本,因此安装的虚拟目录ASP.NET版本选项也是空的,问了不少网友,都说需要执行aspnet_regiis.exe -i去注册2.0版本的.NET,但是如果安装好了再让用户去做这些操作总觉得有些别扭,看了一下aspnet_regiis.exe的参数说明,发现可以对某个虚拟站点进行.NET版本注册,就写下面这段代码去注册当天安装的虚拟目录
1 string path = Environment.GetFolderPath(Environment.SpecialFolder.System).ToUpper().Replace("SYSTEM32", "") + @"Microsoft.NET\Framework\v2.0.50727\aspnet_regiis.exe";
2
3 System.Diagnostics.Process process = new System.Diagnostics.Process();
4 process.StartInfo.FileName = path;
5 process.StartInfo.Arguments = "-s W3SVC/1/ROOT/" + "虚拟站点名称";
6 process.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
7 process.Start();
8 process.WaitForExit();
2
3 System.Diagnostics.Process process = new System.Diagnostics.Process();
4 process.StartInfo.FileName = path;
5 process.StartInfo.Arguments = "-s W3SVC/1/ROOT/" + "虚拟站点名称";
6 process.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
7 process.Start();
8 process.WaitForExit();
第一行的代码是获取aspnet_regiis.exe的具体路径,然后通过一个进程去执行注册操作,这样,即使出现前两种情况,都不用担心没有将虚拟站点的ASP.NET版本注册正确Web应用程序无法运行了
这两部分代码可以建一个像安装数据库一样继承于System.Configuration.Install.Installer的自定义操作来执行,不过,方便起见,直接放在InstallDB类中就可以了