碰到一个小小的需求,就是要根据传入的锚(也就是url中#后面的东西啦)来显示不同的内容,记得以前写了的,不知道被我丢到哪去了,又要重新写一个,顺便把功能整理加强了一些,加入了取QueryString和Cookie的东西,老习惯,贴代码.
RequestHelper.js
//功能 : 在javascript中提供QueryString/Cookie/Anchor的访问.
/*使用 :
var Request = new RequestHelper();
var s = Request.QueryString["id"]; //取得url中的id参数.
var c = Request.Cookies["name"]; //取得id为name的cookie值.
var a = Request.Anchor; //取得url中定位的锚点名称.
*/
//更新 : 2008-05-31
RequestHelper.prototype.GetParams = function()
{
var result = {};
var loc = document.location.toString();
if(loc.indexOf("?") > -1)
{
var l = loc.lastIndexOf("#") > -1 ? loc.lastIndexOf("#") : loc.length;
var param_str = loc.substring(loc.indexOf("?")+1, l);
var params = param_str.split("&");
for(var x = 0; x < params.length; x++)
{
params[x] = params[x].split("=");
result[params[x][0]] = params[x][1];
}
}
return result;
}
RequestHelper.prototype.GetCookies = function()
{
var result = {};
var cookie = document.cookie;
if(cookie.length > 0)
{
var reg = /(^[a-zA-z0-9]+?|; [a-zA-z0-9]+?)=/g;
var c = cookie.match(reg);
if(c)
{
var n = 0;
for(var x = 0; x < c.length; x++)
{
n = (x < c.length - 1) ? cookie.indexOf(c[x + 1].toString()) : cookie.length;
var s = cookie.substring(cookie.indexOf(c[x].toString()),n);
s = s.split("=");
s[0] = s[0].replace(/^; / , "");
result[s[0]] = s[1];
}
}
}
return result;
}
RequestHelper.prototype.GetAnchor = function()
{
var Anchor;
var loc = document.location.toString()
if(loc.lastIndexOf("#") > -1)
{
Anchor = loc.substring(loc.lastIndexOf("#")+1);
}
return Anchor;
}
function RequestHelper()
{
this.QueryString = this.GetParams();
this.Cookies = this.GetCookies();
this.Anchor = this.GetAnchor();
}
经初步测试,没发现问题,但不保证没有任何bug,有用得上的,请随便复制粘贴,有更好的方法的,请各位多多指点,我只是小菜一个,大家就砖下留情了.
另,因cookie的名称不支持某些特殊符号,所以这里只取了数字和字母,大家注意了,如果有其它的办法,麻烦指点指点。呵呵!
一次难得的安装包制作经历,因为之前从没有制作过安装包,那就免不了遇到问题,在摸索和学习中获得了不少宝贵经验,在这里我将用图文并茂的形式详细描述一下流程及主要难点问题的解决方法,希望对需要的朋友有所帮助.
首先建一个Web应用程序的安装项目
建好项目后在该安装项目的文件系统中加入Web应用程序的所有文件,全选后拖到"Web应用程序文件夹"内即可,也可以在"Web应用程序文件夹"点击右键在菜单中选择添加文件
如果有多个Web应用程序需要放在同一个安装包内安装,可以在"目标计算机上的文件系统"上点右键添加"Web自定义文件夹",如图中的WebControls
在Web文件夹的属性里,可以设置一下默认页及虚拟目录名称,其他的属性可以无视,使用默认值就可以了
到此,一个Web应用程序的安装包就基本完成了,直接生成的安装程序就可以将安装到\Inetpub\wwwroot目录下成为一个虚拟站点了,如果你的站点没有数据库的话,基本可以打完收工了^_^
之后遇到的第一个问题就是如何安装数据库.在网上找了一些资料,虽然说的比较详细,但都没有图例,习惯了傻瓜教程的我花了不少时间去摸索,下面就图文说明一下
先设计用户录入SQL数据库信息的界面,这个VS.NET有现成的界面,稍微修改一下即可,非常简单,打开安装包项目的用户界面
在"启动"项里添加一个"文本框(A)"对话框
将"文本框(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语句
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 }
在研究过程中,我尝试了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);
可以看到,通过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(); //等待执行
此种方法需要开一个进程,性能比较高,并且将进程的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());
通过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 }
需要提醒的是,创建数据库的脚本文件中,需要将"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();
第一行的代码是获取aspnet_regiis.exe的具体路径,然后通过一个进程去执行注册操作,这样,即使出现前两种情况,都不用担心没有将虚拟站点的ASP.NET版本注册正确Web应用程序无法运行了
这两部分代码可以建一个像安装数据库一样继承于System.Configuration.Install.Installer的自定义操作来执行,不过,方便起见,直接放在InstallDB类中就可以了
安装包的制作是件很复杂的事,通过这次经历,只是学到了一点皮毛,在这里班门弄斧一下,希望对需要的朋友有所帮助^_^
==================================================
DBInstall.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration.Install;
using System.Data.SqlClient;
namespace DBInstall
{
[RunInstaller(true)]
public partial class InstallDb : Installer
{
public InstallDb()
{
InitializeComponent();
}
private void ExecuteSql(string connStr, string DatabaseName, string Sql)
{
SqlConnection conn = new SqlConnection(connStr);
SqlCommand cmd = new SqlCommand(Sql, conn);
conn.Open();
conn.ChangeDatabase(DatabaseName);
try
{
cmd.ExecuteNonQuery();
}
finally
{
conn.Close();
}
}
#region Install 从这里开始启动安装
public override void Install(System.Collections.IDictionary stateSaver)
{
base.Install(stateSaver);
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"]);
string strSql = "EXEC sp_attach_db @dbname = N'" + this.Context.Parameters["dbname"] + "',"
+ "@filename1 = N'" + this.Context.Parameters["targetdir"] + "DataBase\\Super.mdf',"
+ "@filename2 = N'" + this.Context.Parameters["targetdir"] + "DataBase\\Super_log.ldf'";
ExecuteSql(connStr, "master", strSql);
}
#endregion
#region Uninstall 删除
public override void Uninstall(System.Collections.IDictionary savedState)
{
//添加自定义的卸载代码
if (savedState == null)
{
throw new ApplicationException("未能卸载!");
}
else
{
base.Uninstall(savedState);
}
}
#endregion
//
}
}
*--------------- 客户端表单通用验证checkForm(oForm) -----------------
* 本程序最初是由wanghr100(灰豆宝宝.net)的checkForm基础上进行修改的,增加了很多功能,如下:
* 1.对非ie的支持
* 2.增加了内置表达式和内置提示
* 3.增加了显示方式(弹出式和页面显示式)
* 4.增加了显示一条和显示全部
* 5.进行了封装(CLASS_CHECK)
* 6.支持外接函数或表达式(应用在密码一致)
* 7.简化了调用方式,所有操作只需要<script language='javascript' src='checkform.js'>,
然后在HTML里定义各标签验证格式
* 8.对IE增加了对键盘输入的限制(如:定义usage='int'时,输入框只能输入数字(非IE无效)
* 9.增加了对disabled的不验证
* 10.自定义报警方式(重写showMessageEx方法)
*-----------------------------申明信息-----------------------------
*
* 作者: ttyp
* 邮箱: ttyp@21cn.com
* 博客: http://www.cnblogs.com/ttyp/
* 声明: 对本程序可以任意复制传播,但请保留这些声明,对于内置的表达式有些没有做到很严格,如果你
有好的建议和意见,欢迎邮件和我联系或者上我的博客留言
* 简介:
本程序只需要对需要验证的标签设置三个属性:usage,exp,tip
usage : 内置格式或表达式或函数
exp : 正则表达式(注意如果指定了usage则忽略exp)
tip : 出错提示(如果是内置格式可以不要此属性,有缺省提示)
调用时只需要引用<script language='javascript' src='checkform.js'>,然后为每个标记
增加以上3个属性(不一定需要全部)
* 原作者: wanghr100(灰豆宝宝.net)
* email: wanghr100@126.com
*
*--------------- 客户端表单通用验证checkForm(oForm) -----------------
*/
////////////////////////////////////////////////////////////////////////////////
function CLASS_CHECK()
{
this.pass = true; //是否通过验证
this.showAll = true; //是否显示所有的验证错误
this.alert = false; //报警方式(默认alert报警)
this.message = ""; //错误内容
this.first = null; //在显示全部验证错误时的第一个错误控件(用于回到焦点)
//定义内置格式
var aUsage =
{
"int":"^([+-]?)\\d+$", //整数
"int+":"^([+]?)\\d+$", //正整数
"int-":"^-\\d+$", //负整数
"num":"^([+-]?)\\d*\\.?\\d+$", //数字
"num+":"^([+]?)\\d*\\.?\\d+$", //正数
"num-":"^-\\d*\\.?\\d+$", //负数
"float":"^([+-]?)\\d*\\.\\d+$", //浮点数
"float+":"^([+]?)\\d*\\.\\d+$", //正浮点数
"float-":"^-\\d*\\.\\d+$", //负浮点数
//邮件
"email":"^\\w+((-\\w+)|(\\.\\w+))*\\@[A-Za-z0-9]+((\\.|-)[A-Za-z0-9]+)*\\.[A-Za-z0-9]+$",
"color":"^#[a-fA-F0-9]{6}", //颜色
"url":"^http[s]?:\\/\\/([\\w-]+\\.)+[\\w-]+([\\w-./?%&=]*)?$", //联接
"chinese":"^[\\u4E00-\\u9FA5\\uF900-\\uFA2D]+$", //仅中文
"ascii":"^[\\x00-\\xFF]+$", //仅ACSII字符
"zipcode":"^\\d{6}$", //邮编
"mobile":"^0{0,1}13[0-9]{9}$", //手机
"ip4":"^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}.\\d{1,3}$", //ip地址
"notempty":"^\\S+$", //非空
"picture":"(.*)\\.(jpg|bmp|gif|ico|pcx|jpeg|tif|png|raw|tga)$", //图片
"rar":"(.*)\\.(rar|zip|7zip|tgz)$", //压缩文件
"date":"^\\d{4}(\\-|\\/|\.)\\d{1,2}\\1\\d{1,2}$" //日期
};
//缺省消息
var aMessage =
{
"int" :"请输入整数", //整数
"int+" :"请输入正整数", //正整数
"int-" :"请输入负整数", //负整数
"num" :"请输入数字", //数字
"num+" :"请输入正数", //正数
"num-" :"请输入负整数", //负数
"float" :"请输入浮点数", //浮点数
"float+":"请输入正浮点数", //正浮点数
"float-":"请输入负浮点数", //负浮点数
"email" :"请输入正确的邮箱地址", //邮件
"color" :"请输入正确的颜色", //颜色
"url" :"请输入正确的连接地址", //联接
"chinese":"请输入中文", //中文
"ascii" :"请输入ascii字符", //仅ACSII字符
"zipcode":"请输入正确的邮政编码", //邮编
"mobile":"请输入正确的手机号码", //手机
"ip4" :"请输入正确的IP地址", //ip地址
"notempty":"不能为空", //非空
"picture":"请选择图片", //图片
"rar" :"请输入压缩文件", //压缩文件
"date" :"请输入正确的日期" //日期
}
var me = this;
function checkForm(oForm)
{
me.pass = true;
me.message = "";
me.first = null;
var els = oForm.elements;
//遍历所有表元素
for(var i=0;i<els.length;i++)
{
//取得格式
var sUsage = els[i].getAttribute("Usage");
var sReg = "";
//如果设置Usage,则使用内置正则表达式,忽略Exp
if(typeof(sUsage)!="undefined"&&sUsage!=null)
{
//如果Usage在表达室里找到,则使用内置表达式,无则认为是表达式;表达式可以是函数;
if(aUsage[sUsage]!=null)
{
sReg = aUsage[sUsage];
}
else
{
try
{
if(eval(sUsage)==false)
{
me.pass = false;
if(me.first==null)
{
me.first = els[i];
}
addMessage(getMessage(els[i]));
if(me.showAll==false)
{
setFocus(els[i]);
break;
}
}
}
catch(e)
{
alert("表达式[" + sUsage +"]错误:" + e.description)
return false;
}
}
}
else
{
sReg = els[i].getAttribute("Exp");
}
if(typeof(sReg)!="undefined"&&sReg!=null)
{
//对于失效状态不验证
if(isDisabled(els[i])==true)
{
continue;
}
//取得表单的值,用通用取值函数
var sVal = getValue(els[i]);
//字符串->正则表达式,不区分大小写
var reg = new RegExp(sReg,"i");
if(!reg.test(sVal))
{
me.pass = false;
if(me.first==null)
{
me.first = els[i];
}
//alert(reg);
//验证不通过,弹出提示warning
var sTip = getMessage(els[i]);
if(sTip.length==0&&typeof(sUsage)!="undefined"&&sUsage!=null&&aMessage[sUsage]!=null)
{
sTip = aMessage[sUsage];
}
addMessage(sTip);
if(me.showAll==false)
{
//该表单元素取得焦点,用通用返回函数
setFocus(els[i]);
break;
}
}
}
}
if(me.pass==false)
{
showMessage();
if(me.first!=null&&me.showAll==true)
{
setFocus(me.first);
}
}
return me.pass;
}
/*
* 添加错误信息
*/
function addMessage(msg)
{
if(me.alert==true)
{
me.message += msg + "\n";
}
else
{
me.message += msg + "<br>";
}
}
/*
* 显示错误
*/
function getMessage(els)
{
var sTip = els.getAttribute("tip");
if(typeof(sTip)!="undefined"&&sTip!=null)
{
return sTip;
}
else
{
return "";
}
}
/*
* 显示错误
*/
function showMessage()
{
//外接显示错误函数
if(typeof(me.showMessageEx)=="function")
{
return me.showMessageEx(me.message);
}
if(me.alert==true)
{
alert(me.message);
}
else
{
var divTip;
divTip = document.getElementById("divErrorMessage");
try
{
if(typeof(divTip)=="undefined"||divTip==null)
{
divTip = document.createElement("div");
divTip.id = "divErrorMessage";
divTip.name = "divErrorMessage";
divTip.style.color = "red";
document.body.appendChild(divTip);
}
divTip.innerHTML = me.message;
}catch(e){}
}
}
/*
* 获得元素是否失效(失效的元素不做判断)
*/
function isDisabled(el)
{
//对于radio,checkbox元素,只要其中有一个非失效元素就验证
if(el.type=="radio"||el.type=="checkbox")
{
//取得第一个元素的name,搜索这个元素组
var tmpels = document.getElementsByName(el.name);
for(var i=0;i<tmpels.length;i++)
{
if(tmpels[i].disabled==false)
{
return false;
}
}
return true;
}
else
{
return el