标题: 利用WebService技术实现远程数据库存取
时间: 2004年6月30日 10:23:51
随着微软Visual Studo.Net Beta版的发布,由于Visual Studio.Net对XML以及Web服务的强大支持,利用Visual Studio.Net开发Web服务应用将会越来越多而且是非常的方便。本文以一个B2B电子商务网站为例,介绍利用web服务在不同站点间共享同一数据库的具体方法和步骤。本文中,客户端是指使用web服务的一方,服务器端是指提供web服务的另一方。 

问题的提出 

  该网站是一家(简称A)从事网上销售手机SIM卡的业务的电子商务网站。前不久,该网站与另一家网站(简称B)合作,共同开展网上销售联通手机SIM卡业务。由于都是使用的A网站的号码资源,存取的都是A网站的数据库,于是笔者利用webservice技术为另一家网站开发了网上售卡系统。 

各主要功能的模块和关键代码 

1. 数据库采用SQL SERVER2000,使用存储过程实现号码浏览的分页显示。代码如下: 
create procedure fenye 

@pagenow int, 
@pagesize int, 
@cityid int, 
@code char(3), 
@recordcount int output 

as 
set nocount on 

declare @allid int,@beginid int,@endid int,@pagebegin char(11),@pageend char(11) 

select @allid=count(*) from jinan where cityid=@cityid and (code like @code+'%') 
select @recordcount=@allid 

declare cur_fastread cursor scroll for 
SELECT code FROM jinan where cityid=@cityid and (code like @code+'%') order by code 

open cur_fastread 
select @beginid=(@pagenow-1)*@pagesize+1 
select @endid=@beginid+@pagesize-1 

fetch absolute @beginid from cur_fastread into @pagebegin 

if @endid>@allid 
fetch last from cur_fastread into @pageend 
else 
fetch absolute @endid from cur_fastread into @pageend 

set nocount off 

select code,cost,status from jinan join xuanhaofei on jinan.category=xuanhaofei.category and jinan.cityid=xuanhaofei.cityid 
where code between @pagebegin and @pageend order by code 

close cur_fastread 
deallocate cur_fastread 

GO 

2. 用Visual Studio.net创建webservice。在visual studo.net中,webservice文件的扩展名是.asmx。该文件放在A网站上,供其他网站调用。 
* 启动visual studio.net,选择new project。 
* 在左面版中选择visual c# projects,在右面版中选择ASP.NET WebService。 
* 单击ok按钮就生成了一个webservice项目。在项目中新建一个webservice文件,WebService1.asmx。该文件实现对数据库的存取,并对调用者输出一个字符串。 

下面是该文件的代码: 

WebService1.asmx.cs 

using System; 
using System.Collections; 
using System.ComponentModel; 
using System.Data; 
using System.Data.SqlClient; 
using System.Configuration; 
using System.Text; 
using System.Diagnostics; 
using System.Web; 
using System.Web.Services; 

namespace WebService1 

public class Service2 : System.Web.Services.WebService 

SqlConnection con; 

public Service2() 

//CODEGEN: This call is required by the ASP.NET Web Services Designer 
InitializeComponent(); 


[WebMethod] //[WebMethod]属性声明此方法作为web服务可以被远程使用者调用 
public string table(int pagenow,int cityid) 

int recordcount;//总号码数 
int page=0;//总页数 
int j=0; 

SqlDataReader d=GetCode(pagenow,cityid,out recordcount); 

if(recordcount%39==0) 

page=recordcount/39;//每页只显示39个号码 


else 

page=recordcount/39+1; 


StringBuilder str=new StringBuilder("<table border='1' width='100%' bordercolorlight='00008B' bordercolordark='#fffff0' cellspacing='0' cellpadding='0' height='24'><tr>"); 

for(int i=0;i<3;i++) 

str.Append("<td bgcolor='#f0f8ff' align='middle' height='0' valign='center'>"); 
str.Append("<p style='MARGIN-BOTTOM: 2px'><font size='2'>号 码</font></p></td>"); 
str.Append("<td bgcolor='#f0f8ff' align='middle' height='0' valign='center'>"); 
str.Append("<font size='2'>选号费</font></td><td bgcolor='#f0f8ff' align='middle' height='0' valign='center'> </td>"); 


str.Append("</tr><tr>"); 

while(d.Read()) 

str.Append("<td height='24' align='middle'><font size='2'>"); 
str.Append(d["code"].ToString()); 
str.Append("</td><td height='24' align='middle'><font size='2'> "); 
str.Append(d["cost"].ToString()); 
str.Append("</td>"); 

if((d["status"].ToString().TrimEnd())=="已预定") 

str.Append("<td height='24' align='middle'>"); 
str.Append("<input type='image' name='image' src='images/hand.gif'>"); 
str.Append("</td>"); 

else 

str.Append("<td height='24' align='middle'>"); 
str.Append("<input type='image' name='image' src='images/cart.jpg' onclick='javascript:addcart("); 
str.Append(d["code"].ToString()); 
str.Append(")' width='24' height='24'>"); 
str.Append("</td>"); 


j++; 
if(j%3==0) 

str.Append("</tr><tr>"); 



d.Close(); 
con.Close(); 

str.Append("</tr></table><br>"); 

if(pagenow==1) 

str.Append("<font color='#000080' size=2>首页 上一页</font> "); 

else 

str.Append("<font color='#000080' size=2><a href='javascript:first()'>首页</a> "); 
str.Append("<a href='javascript:previous("); 
str.Append(pagenow-1); 
str.Append(")'>上一页</a></font> "); 


if(pagenow==page) 

str.Append("<font color='#000080' size=2>下一页 尾页</font>"); 

else 

str.Append("<a href='javascript:next("); 
str.Append(pagenow+1); 
str.Append(")'><font color='#000080' size=2>下一页</a> <a href='javascript:last("); 
str.Append(page); 
str.Append(")'>尾页</a></font>"); 

str.Append("<font color='#000080' size=2> 页次:</font><strong><font color=red size=2>"); 
str.Append(pagenow); 
str.Append("</font><font color='#000080' size=2>/"); 
str.Append(page); 
str.Append("</strong>页</font>"); 
str.Append("<font color='#000080' size=2> 共<b>"); 
str.Append(recordcount); 
str.Append("</b>个号码 <b>39</b>个号码/页</font>"); 

return str.ToString(); 


private SqlDataReader GetCode(int pagenow,int cityid,out int recordcount) 

SqlDataReader dr=null; 
con=new SqlConnection("server=localhost;database=yitong;uid=sa;pwd="); 


SqlCommand cmd=new SqlCommand("fenye",con); 

cmd.CommandType=CommandType.StoredProcedure; 

cmd.Parameters.Add(new SqlParameter("@pagenow",SqlDbType.Int)); 
cmd.Parameters["@pagenow"].value=pagenow;//目前所在页面 

cmd.Parameters.Add(new SqlParameter("@pagesize",SqlDbType.Int)); 
cmd.Parameters["@pagesize"].value=39;//每页要显示的号码数 

cmd.Parameters.Add(new SqlParameter("@cityid",SqlDbType.Int)); 
cmd.Parameters["@cityid"].value=cityid;//城市代码 

cmd.Parameters.Add(new SqlParameter("@code",SqlDbType.Char,3)); 
cmd.Parameters["@code"].value="130";//只搜索联通的手机号码 

SqlParameter q; 
q=cmd.Parameters.Add(new SqlParameter("@recordcount",SqlDbType.Int)); 
q.Direction=ParameterDirection.Output; 

con.Open(); 

cmd.ExecuteNonQuery(); 
recordcount=(int)cmd.Parameters["@recordcount"].value;//返回的号码总数 

dr=cmd.ExecuteReader(); 

return dr; 




3. 客户端页面存放在B网站上。当客户浏览该网站时,通过该页面可以浏览、订购A网站数据库中号码。客户端页面使用微软的Webservice Behavior技术调用A上的web服务。WebService Behavior是微软在IE5.0中新增加的一项可以通过页面脚本使用web服务的技术。她使用SOAP协议与web服务通讯,可以动态更新页面的局部,而不刷新整个页面,较之通常使用的整页刷新的方法,它更快也更有效。要使用这项技术,须从微软网站上下载一个webservice.htc组件到客户端页面所在的目录下。 

客户端页面的代码如下: 
Client.htm 

<html> 
<head> 
<title></title> 
<script language=”javascript”> 
<!-- 
function window_onload() { 
//调用A提供的web服务 
service.useService(" http://IPofA/service1.asmx?WSDL";,"myselect"); 
//调用web服务的table方法,显示第一个页面 
service.myselect.callService(showCode,"table",1,city.value); 


function city_onchange() { 
service.service1.callService(showCode,"table",1,city.value); 


function addcart(id) 

url = "basket.asp?code=" + id ; 
window.navigate(url); 


function next(x) 

//显示下一页 
service.myselect.callService(showCode,"table",x,city.value) 


function first() 

//显示首页 
service.myselect.callService(showCode,"table",1,city.value); 


function previous(x) 

//显示上一页 
service.myselect.callService(showCode,"table",x,city.value); 


function last(x) 

//显示最后一页 
service.myselect.callService(showCode,"table",x,city.value); 


function showCode(result) 

//result保存调用web服务后返回的结果 
service.innerHTML=result.value; 


//--> 
</script> 
</head> 
<body onload="return window_onload()"> 
<select language="javascript" name="city" onchange="return city_onchange()"> 
<option value="531" selected> 山东济南</option> 
<option value="537"> 山东济宁</option> <option value="546"> 山东东营</option> 
</select> 
<div id="service" style="behavior:url(webservice.htc)"> 
</div> 
</body> 
</html> 
  可以看到,webservice behavior可以使一个静态页面通过脚本程序使用web服务,而且不用在客户端建立代理,只要拷贝一个webservice.htc组件就可以了。 
  利用Visual Studio.Net,你可以不必了解HTTP、XML、SOAP、WSDL等底层协议,同样能开发和使用Web服务,真得是好爽。
标题: C#如何读INI文件中的设置信息
时间: 2004年6月30日 10:22:52
把下面的代码改动一下,就可以在你的程序中使用,当然 
别忘记加上名字空间哦。 

以下内容为程序代码:
using System; 
using System.IO; 
using System.Runtime.InteropServices; 
using System.Text; 


namespace Sx_Mdi 


/// <summary> 
/// Summary description for Class1. 
/// </summary> 
public class IniFile 

//文件INI名称 
public string Path; 

////声明读写INI文件的API函数 
[DllImport("kernel32")] 

private static extern long WritePrivateProfileString(string section,string key,string val,string filePath); 


[DllImport("kernel32")] 

private static extern int GetPrivateProfileString(string section,string key,string def,StringBuilder retVal,int size,string filePath); 


//类的构造函数,传递INI文件名 
public IniFile(string inipath) 

// 
// TODO: Add constructor logic here 
// 
Path = inipath; 


//写INI文件 
public void IniWriteValue(string Section,string Key,string Value) 

WritePrivateProfileString(Section,Key,Value,this.Path); 



//读取INI文件指定 
public string IniReadValue(string Section,string Key) 

StringBuilder temp = new StringBuilder(255); 
int i = GetPrivateProfileString(Section,Key,"",temp,255,this.Path); 
return temp.ToString(); 







操作范例: 

public static SqlConnection MyConnection() 

string sPath; 
string ServerName,userId,sPwd,DataName; 

sPath = GetPath(); 
IniFile ini = new IniFile(sPath); 
ServerName = ini.IniReadValue ("Database","server"); 
userId = ini.IniReadValue ("Database","uid"); 
sPwd = ini.IniReadValue ("Database","pwd"); 
DataName = ini.IniReadValue ("Database","database"); 
string strSql = "server =" + ServerName+";uid ="+ userId +";pwd =;database ="+ DataName; 
    SqlConnection myConn=new SqlConnection(strSql); 
    return myConn; 
标题: 创建NET打包程序一例
时间: 2004年6月30日 10:22:13
创建 Windows 应用程序 

在“文件”菜单上指向“新建”,然后选择“项目”。 
在“新建项目”对话框中,选择“项目类型”窗格中的“Visual Basic 项目”,然后选择“模板”窗格中的“Windows 应用程序”。在“名称”框中,键入“我的记事本”。 
此项目被添加到解决方案资源管理器中,并且窗体设计器打开。 

在“工具箱”中选择“Windows 窗体”选项卡,并将“按钮”(Button) 控件拖到窗体中。 
双击 Button 控件为该按钮添加事件处理程序。在事件处理程序中添加下面的代码: 
Shell("Notepad.exe", AppWinStyle.NormalFocus)
这将启动 Notepad.exe 并将焦点对准它。 

在“生成”菜单上,选择“生成我的记事本”该应用程序。 
创建部署项目 

在“文件”菜单上指向“添加项目”,然后选择“新建项目”。 
在“添加新项目”对话框中,选择“项目类型”窗格中的“安装和部署项目”,然后选择“模板”窗格中的“安装项目”。在“名称”框中,键入“我的记事本安装程序”。 
项目被添加到解决方案资源管理器中,并且文件系统编辑器打开。 

在解决方案资源管理器中选择“我的记事本安装程序”项目。在“属性”窗口中,选择 ProductName 属性,并键入“我的记事本”。 
注意 ProductName 属性确定应用程序显示在文件夹名称以及“添加/删除程序”对话框中的名称。
将 Windows 应用程序添加到安装程序中 

在解决方案资源管理器中选择“我的记事本安装程序”项目。在“文件系统编辑器”中,选择“应用程序文件夹”节点。 
在“操作”菜单上,选择“添加”->“项目输出”。 
在“添加项目输出组”对话框中,从“项目”下拉列表中选择“我的记事本”。 
从列表中选择“主输出”组,并单击“确定”。 
在“生成”菜单上选择“生成我的记事本安装程序”。 
部署应用程序(基本安装程序)

如果正在执行一个完整的演练过程,可以跳过此步骤。 

在解决方案资源管理器中选择“我的记事本安装程序”项目。在“项目”菜单上选择“安装”。 
这将运行该安装程序并在开发计算机上安装“我的记事本”。 

其余步骤演示可选的部署功能。
为 Windows 应用程序创建快捷方式
此步骤将为您的应用程序创建一个快捷方式,安装过程中,会将该快捷方式放置到目标计算机的桌面上。 

在解决方案资源管理器中选择“我的记事本安装程序”项目。在“文件系统编辑器”中,选择“来自‘我的记事本’的主输出”节点。 
在“操作”菜单上,选择“创建 主输出来自我的记事本(活动)的快捷方式”。 
这将添加一个“主输出来自我的记事本(活动)的快捷方式”节点。 

重命名“主输出来自我的记事本(活动)的快捷方式”快捷方式。 
选择“主输出来自我的记事本(活动)的快捷方式”,并将其拖到左窗格的“用户桌面”文件夹中。 
为 Windows 应用程序创建文件关联

此步骤为“我的记事本”添加文件关联,以便双击 .vbn 文件时启动“我的记事本”应用程序。 

在解决方案资源管理器中选择“我的记事本安装程序”项目。在“视图”菜单上指向“编辑器”,然后选择“文件类型”。 
在“文件类型编辑器”中选择“目标计算机上的文件类型”节点。在“操作”菜单上,选择“添加文件类型”。 
将添加一个“新文档类型 #1”节点,而且该节点将打开,以便您重命名。 

将“新文档类型 #1”重命名为 Vbn.doc。 
在“属性”窗口中,将文件类型的 Extension 属性设置为 vbn。 
选择 Command 属性并单击“省略号”() 按钮。在“选择项目中的项”对话框中,定位到“应用程序文件夹”,并选择“来自‘我的记事本’的主输出”。 
为 Windows 应用程序添加注册表项

此步骤将一个注册表项以及相应的值添加到注册表中。运行时,可以从应用程序代码中引用此注册表项以检索每用户信息。 

在解决方案资源管理器中选择“我的记事本安装程序”项目。在“视图”菜单上,指向“编辑器”,并选择“注册表”。 
选择“HKEY_CURRENT_USER”节点并将其展开,然后展开“Software”节点,并选择“[Manufacturer]”节点。 
注意 “Manufacturer”节点两边带有括号,这表示它是一个属性。它将被输入的部署项目的 Manufacturer 属性值所替代。
在“操作”菜单上,选择“新建”->“键”。 
重命名 UserChoice 键。 
在“操作”菜单上,选择“新建”,再选择“字串值”。 
重命名 TextColor 值。 
在“属性”窗口中,选择 value 属性,并输入 Black。 
添加自定义安装对话框

此步骤添加并配置一个在安装期间显示的自定义用户界面对话框。 
在解决方案资源管理器中选择“我的记事本安装程序”项目。在“视图”菜单上指向“编辑器”,然后选择“用户界面”。 
在用户界面编辑器中,选择“安装”节点下的“启动”节点。 
在“操作”菜单上,选择“添加对话框”。 
在“添加对话框”对话框中,选择“复选框 (A)”。 
在“操作”菜单上,选择“上移”两次,将“复选框 (A)”对话框放置在“选择安装文件夹”对话框之上。 
在“属性”窗口中,将 BannerText 属性设置为“示例”。 
将 BodyText 属性设置为“‘安装示例文件’复选框控制是否安装示例文件。如果处于未选中状态,则不安装示例。” 
将 CheckBox1Label 属性设置为“要安装示例吗?”。 
将 Checkbox2Visible、Checkbox3Visible 和 Checkbox4Visible 属性设置为 false。这将隐藏其他复选框。 
添加 Samples 文件夹

此步骤创建一个 Samples 子文件夹,它将被安装到 Application 文件夹的下面。 

在解决方案资源管理器中选择“我的记事本安装程序”项目。在“视图”菜单上指向“编辑器”,然后选择“文件系统”。“应用程序文件夹”仍应被选中。 
从“操作”菜单上指向“添加”,然后选择“文件夹”。 
将“新建文件夹 #1”重命名为“示例”。 
为应用程序创建示例文件

此步骤创建两个简单的文本文件,如果用户在自定义对话框中选择“安装示例”选项,将安装这两个文本文件。 

使用记事本或其他文本编辑器创建一个包含文本“这是 rules.vbn”的文本文件,然后将其保存为 Rules.vbn。 
注意 若要防止记事本自动添加 .txt 扩展名,请从“文件类型”下拉列表中选择“所有文件”。
创建另一个包含文本“这是 memo.vbn”的文本文件,将其保存为 Memo.vbn。 
将示例添加到安装程序中

此步骤将示例文件添加到 Samples 文件夹中,并设置决定是否安装该文件的条件。 

在解决方案资源管理器中选择“我的记事本安装程序”项目。从“视图”菜单中指向“编辑器”,选择“文件系统”,然后选择“示例”文件夹。 
从“操作”菜单上指向“添加”,然后选择“文件”。将 Rules.vbn 和 Memo.vbn 文件添加到“示例”文件夹中。 
在文件系统编辑器中选择 Rules.vbn。 
在“属性”窗口中,将 Condition 属性设置为 CHECKBOXA1=1。运行安装程序时,只在自定义的复选框被选中时才安装 Rules.vbn 文件。 
在文件系统编辑器中选择 Memo.vbn 文件。 
在“属性”窗口中,将 Condition 属性设置为 CHECKBOXA1=1。运行安装程序时,只在自定义的复选框被选中时才安装 Memo.vbn 文件。 
添加启动条件来检查 Internet Explorer 版本

此步骤检查在目标计算机上是否安装了 Internet Explorer 5.0 或更高版本,如果未安装所需版本的 Internet Explorer,将停止安装过程。

注意 此步骤旨在阐述启动条件的概念;“我的记事本”应用程序实际上并不依赖于 Internet Explorer。
在解决方案资源管理器中选择“我的记事本安装程序”项目。在“视图”菜单上指向“编辑器”,然后选择“启动条件”。 
在启动条件编辑器中,选择“目标计算机上的要求”节点。 
在“操作”菜单上,选择“添加文件启动条件”。 
将在“搜索目标计算机”节点下面添加一个“搜索 File1”节点,而在“启动条件”节点下面添加一个“Condition1”节点。 

将“搜索 File1”重命名为“搜索 Internet Explorer”。 
在“属性”窗口中,将 FileName 属性设置为 Iexplore.exe,将 Folder 属性设置为 [ProgramFilesFolder],将 Depth 属性设置为 2,将 MinVersion 属性设置为 5.00。 
选择“Condition1”节点。 
将 Message 属性设置为“该程序需要 Microsoft Internet Explorer 5.0 或更高版本。请安装 Internet Explorer 并重新运行‘记事本’安装程序。” 
设置部署项目的可选属性

此步骤设置一个属性,以便在目标计算机上没有正确版本的 Windows 安装程序时,自动安装 Windows 安装引导程序文件。 

在解决方案资源管理器中选择“我的记事本安装程序”项目。在“视图”菜单上,选择“属性页”。 
在“我的记事本安装程序”属性页中,选择“引导程序”下拉列表,然后选择“Windows 安装引导程序”。 
在“生成”菜单上选择“生成我的记事本安装程序”。 
在开发计算机上安装“我的记事本” 

在解决方案资源管理器中选择“我的记事本安装程序”项目。在“项目”菜单上选择“安装”。 
这将运行该安装程序并在开发计算机上安装“我的记事本”。 

将“我的记事本”部署到其他计算机上 
在 Windows 资源管理器中,定位到项目目录并查找所生成的安装程序。默认路径是 \documents and settings\yourloginname\我的记事本安装程序\project configuration\我的记事本安装程序.msi,默认“项目配置”是“Debug”。 
将该目录中的 我的记事本安装程序.msi 文件以及其他所有文件和子目录复制到另一台计算机上。 
注意 若要在未连网的计算机上进行安装,请将文件复制到 CD-ROM 等传统媒体中。
在目标计算机上双击 Setup.exe 文件来运行安装程序。 
测试安装 
验证快捷方式是否安装到了桌面上以及是否能够正确启动“我的记事本”应用程序。 
使用“控制面板”中的“添加/删除程序”工具卸载该应用程序。 
提示 若要从开发计算机上卸载,则在“项目”菜单上选择“卸载”。
按上面所说的一步步做,应该不会有错
标题: c#的一些字符串的经验
时间: 2004年6月30日 10:21:39
//获得汉字的区位码
byte[] array = new byte[2];
array = System.Text.Encoding.Default.GetBytes("啊");

int i1 = (short)(array[0] - '\0');
int i2 = (short)(array[1] - '\0');

//unicode解码方式下的汉字码
array = System.Text.Encoding.Unicode.GetBytes("啊");
i1 = (short)(array[0] - '\0');
i2 = (short)(array[1] - '\0');

//unicode反解码为汉字
string str = "4a55";
string s1 = str.Substring(0,2);
string s2 = str.Substring(2,2);

int t1 = Convert.ToInt32(s1,16);
int t2 = Convert.ToInt32(s2,16);

array[0] = (byte)t1;
array[1] = (byte)t2;

string s = System.Text.Encoding.Unicode.GetString(array);

//default方式反解码为汉字
array[0] = (byte)196;
array[1] = (byte)207;
s = System.Text.Encoding.Default.GetString(array);

//取字符串长度
s = "iam方枪枪";
int len = s.Length;//will output as 6
byte[] sarr = System.Text.Encoding.Default.GetBytes(s);
len = sarr.Length;//will output as 3+3*2=9

//字符串相加
System.Text.StringBuilder sb = new System.Text.StringBuilder("");
sb.Append("i ");
sb.Append("am ");
sb.Append("方枪枪");


string fox;
fox.ToLower()转化成小写字母
fox.ToUpper()转化成大写字母
fox.Trim()删除前后空格
fox.Trim(trimChars)删除其它字符
fox.TrimStart()删除前空格
fox.TrimEnd()删除后空格
fox.PadLeft(10)增加左边空格,使字串达到某长度。
fox.PadRight(10)增加右边空格,使字串达到某长度。
fox.PadX(10,'-')增加其它字符,使字串达到某长度。X指:Left/Right
fox.Split(' ')将字串分解成数组

//格式化
string sf=string.Format("{0}年{1}月{2}日 {3}时{4}分",temp.Year,temp.Month,temp.Day,currTime.TruantTime.Hour,currTime.TruantTime.Minute);

//一些正则表达式
Internet地址表达式
http://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?
Email地址表达式
\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*
邮编
\d{6}
电话号码
(\(\d{3}\)|\d{3}-)?\d{8}

//分隔字符串
string total="aaa,bbb,ccc,dddd";
string[]strArray;
char[]charArray=new char[]{','};
strArray=total.Split(charArray);

//日期
DateTime t1 = new DateTime(2002,5,30);
DateTime t2 = new DateTime(2002,5,23);
TimeSpan diff=t1-t2;
MessageBox.Show(diff.Days.ToString(

System.DateTime.Now.ToString()

DateTime d1=Convert.ToDateTime("2003-01-01 18:20:01");
string s=d1.ToString("yyyy-MM-dd HH:mm:ss");

//目录
Environment.CurrentDirectory
Application.StartupPath

Web窗体回滚后只能记住ASP控件的值,而记不住变量的值。
标题: c#正则表达式的操作
时间: 2004年6月30日 10:21:04
using System.Text.RegularExpressions; 
string Text="The software ad,is MeTone, a,is very gaaad!,Your Need";
//一般
// string Pattern="is";
// MatchCollection Matches=Regex.Matches(Text,Pattern,RegexOptions.IgnoreCase);
// foreach(Match NextMatch in Matches)
// Console.WriteLine(NextMatch.Index);
//转义,以n开头的单词
// string Pattern=@"\bn";
// MatchCollection Matches=Regex.Matches(Text,Pattern,RegexOptions.IgnoreCase|RegexOptions.ExplicitCapture);
// foreach(Match NextMatch in Matches)
// Console.WriteLine(NextMatch.Index);
//转义,以e结尾的单词
// string Pattern=@"e\b";
// MatchCollection Matches=Regex.Matches(Text,Pattern,RegexOptions.IgnoreCase|RegexOptions.ExplicitCapture);
// foreach(Match NextMatch in Matches)
// Console.WriteLine(NextMatch.Index);
//转义,以M开头,以e结尾,中间是任何数量不为空的字符,\S表示不是空白的字符,*任何数量
// string Pattern=@"\bM\S*e\b";
// MatchCollection Matches=Regex.Matches(Text,Pattern,RegexOptions.IgnoreCase|RegexOptions.ExplicitCapture);
// foreach(Match NextMatch in Matches)
// Console.WriteLine(NextMatch.Index);
//转义,以T只能是总文本中的第一个字符
// string Pattern=@"^T";
// MatchCollection Matches=Regex.Matches(Text,Pattern,RegexOptions.IgnoreCase|RegexOptions.ExplicitCapture);
// foreach(Match NextMatch in Matches)
// Console.WriteLine(NextMatch.Index);
//转义,以d只能是总文本中的第一个字符
// string Pattern=@"d$";
// MatchCollection Matches=Regex.Matches(Text,Pattern,RegexOptions.IgnoreCase|RegexOptions.ExplicitCapture);
// foreach(Match NextMatch in Matches)
// Console.WriteLine(NextMatch.Index);
//转义,.是除以换行符\n以外的任何一个字符
// string Pattern=@"g.d";
// MatchCollection Matches=Regex.Matches(Text,Pattern,RegexOptions.IgnoreCase|RegexOptions.ExplicitCapture);
// foreach(Match NextMatch in Matches)
// Console.WriteLine(NextMatch.Index);
// Console.WriteLine(NextMatch.Index);
//转义,+可以重复一次或多次的前导字符
// string Pattern=@"ga+d";
// MatchCollection Matches=Regex.Matches(Text,Pattern,RegexOptions.IgnoreCase|RegexOptions.ExplicitCapture);
// foreach(Match NextMatch in Matches)
// Console.WriteLine(NextMatch.Index);
//转义,?可以重复零次或多次的前导字符
// string Pattern=@"ga+d";
// MatchCollection Matches=Regex.Matches(Text,Pattern,RegexOptions.IgnoreCase|RegexOptions.ExplicitCapture);
// foreach(Match NextMatch in Matches)
// Console.WriteLine(NextMatch.Index);
//转义,?可以重复零次或多次的前导字符
// string Pattern=@"\sa";
// MatchCollection Matches=Regex.Matches(Text,Pattern,RegexOptions.IgnoreCase|RegexOptions.ExplicitCapture);
// foreach(Match NextMatch in Matches)
// Console.WriteLine(NextMatch.Index);
//提取网址
Text="I'found the URL is http://www.emay.net.cn is very good";
string Pattern=@"\b(\S+)://(\S+)(?::(\S+))?\b";
MatchCollection Matches=Regex.Matches(Text,Pattern,RegexOptions.IgnoreCase|RegexOptions.ExplicitCapture);
foreach(Match NextMatch in Matches)
Console.WriteLine(NextMatch); 
标题: 在.NET中读写INI文件 ——兼谈正则表达式的应用
时间: 2004年6月30日 10:20:33

INI文件是Windows平台上的一种较常用的软件配置文件格式,Windows应用程序常常使用它来保存一些配置信息。它一般是由数个包含key-value对的Section组成,每个key-value对保存着一些软件配置信息。例如最典型的NT系列的启动配置文件boot.ini:

 


--------------------------------------------------------------------------------


[boot loader]

timeout=30

default=multi(0)disk(0)rdisk(0)partition(2)\WINDOWS

[operating systems]

multi(0)disk(0)rdisk(0)partition(2)\WINDOWS="Microsoft Windows XP Professional" /fastdetect

multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional" /fastdetect

 


--------------------------------------------------------------------------------


在这个文件中,方括号中的字符串是Section的名字,两个方括号之间的内容为一个Section。Section的内容是一些key-value对,每个key-value对占据一行,例如timeout=30就是一对key-value对,timeout是key,对应的value是30。Windows平台专门提供了一组API可以方便地操作INI文件,例如GetPrivateProfileSection()、GetPrivateProfileInt()等。

 

随着Windows系列操作系统的不断发展,INI文件的作用逐渐被注册表、XML格式的config文件等所取代,很少再用于系统配置,但我们仍可以在应用程序中使用它。在.NET平台上推荐使用的软件配置文件格式是基于XML的config文件,因此在.NET Framework中并没有提供对INI文件读写的特殊支持,使得我们有时在需要读写INI文件时不是很方便。本文将探讨如何使INI文件的读写在.NET平台上变得更加容易。当然,我们可以直接引入上述的API,但本文将不使用API,而是完全基于.NET Framework。

 

创建INI文件读写类

 

要在.NET平台上处理INI文件,很自然的想法就是创建一个专门的class来负责INI文件的读写工作,这个class暴露适当的接口供外部调用。一般的INI文件的尺寸很小,因此最简单的做法就是以文本的方式将整个文件读入一个string变量中。类定义如下:

 


--------------------------------------------------------------------------------

 

    public class FileIni

    {

        private string fileContents = null;

 

        public FileIni(string fileName)

        {

            if(File.Exists(fileName))

            {

                StreamReader r = File.OpenText(fileName);

                fileContents = r.ReadToEnd();

                r.Close();

            }

        }

    }

 


--------------------------------------------------------------------------------


接下来我们要提供一些方法来操作这个字符串,比如从中返回所有的Section Name、取得特定的key所对应的value等。我们可以使用字符串查找之类的方法来完成这些工作,但是.NET Framework为我们提供了更好的方法,那就是正则表达式。

 

正则表达式

 

所谓正则表达式是一种被设计用来优化字符串操作的语言。它使用一组元字符(Metacharacters)来实现强劲的字符串操作能力。这组元字符最早来自于对DOS文件系统中?和*的扩展。在DOS文件系统中,?和*分别被用来代替单个字符和字符群组,它们可以被认为是最早的元字符。正则表达式在它们的基础上不断扩充,形成了一套元字符集,能够表达非常复杂的字符串。

 

举例来说,网上注册时常常需要用户输入一个有效的Email地址。当用户输入一个字符串后,我们如何验证这个Email地址是否合法呢?使用下面这个正则表达式可以轻易地实现目的:

 


--------------------------------------------------------------------------------

 

@"^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$"

 

--------------------------------------------------------------------------------


关于这个正则比表达式的含义,在此不做过多解释,有兴趣的朋友可以参考相关的正则表达式资料。这个正则表达式虽不能保证用户输入的Email地址100%的真实有效,但至少可以保证用户输入的Email地址看上去是合法有效的。

 

.NET Framework中提供了一些使用正则表达式的类,这些类位于System.Text.RegularExpressions名字空间下。

 

使用正则表达式实现FileIni类的功能

 

现在我们可以使用正则表达式来实现FileIni类的相应功能了。为了返回INI文件中所有Section的名字,我们可以使用一个只读属性SectionNames来返回一个Section Name的字符串数组。

 


--------------------------------------------------------------------------------


        public string[] SectionNames

        {

            get

            {

                // Using regular expression to get all section names.

                string regexPattern = @"\[(?<SectionName>\w*)\]";

                Regex r = new Regex(regexPattern);  // Match "[anywords]"

                MatchCollection matches = r.Matches(fileContents);

                // Writing all section names to a string array.

                string[] results = new string[matches.Count];

                for(int i = 0; i < matches.Count; i++)

                {

                    results[i] = matches[i].Result("${SectionName}");

                }

 

                return results;

            }

        }

 

--------------------------------------------------------------------------------


在上面的代码中,我们使用一个正则表达式:@"\[(?<SectionName>\w*)\]",对源字符串进行一次匹配就取出了所有的Section Name。

 

为了取得特定Section下的特定的key的value,我们先要取得此Section下的所有内容,然后再从中取出特定key的value。

 


--------------------------------------------------------------------------------


        public string GetSectionString(string sectionName)

        {

            string regexPattern = @"(\[" + sectionName + @"\]"

                + @"(?<SectionString>.*)\[)";

            Regex r = new Regex(regexPattern, RegexOptions.Singleline);

            if(r.IsMatch(fileContents))

            {

                return r.Match(fileContents).Result("${SectionString}");

            }

 

            return string.Empty;

        }

 

 

--------------------------------------------------------------------------------


GetSectionString()根据特定的sectionName取得此Section的全部内容。假设sectionName为字符串boot loader,此时的正则表达式为@”(\[boot loader\](?<SetionString>.*)\[]”。得到Section下的所有内容后,我们再从其中得到我们想要的value值。

 


--------------------------------------------------------------------------------

 

        public string GetKeyString(string sectionName, string keyName)

        {

            string sectionString = this.GetSectionString(sectionName);

            string regexPattern = @"(" + keyName + @"=(?<value>.*)\r\n)";

            Regex r = new Regex(regexPattern);

            if(r.IsMatch(fileContents))

            {

                return r.Match(fileContents).Result("${value}");

            }

           

            return string.Empty;

        }

 


--------------------------------------------------------------------------------


在此基础上,可以得到更多的诸如GetKeyInt()之类的方法。至于写方法,利用Regex的Replace()方法也是很容易实现的,在此就不做过多的叙述了。

 

总结

 

本文着重演示了正则表达式在读写INI文件时的应用。所实现的INI文件读写类FileIni扩展性稍显不足,例如,这个类只能处理通用格式的INI文件,对于格式稍有变化的INI文件,此类中的正则表达式就需要修改了。总之,正则表达式是处理字符串的强大工具,掌握了它

对我们更高效地处理字符串是绝对有好处的。

标题: 用 API 作简繁体转换
时间: 2004年6月30日 10:20:00
输入Big5字符,返回Gb简体字符
    //---------------------------------------------------------------------------
    //函数输入Big5字符,返回Gb简体字符
    //---------------------------------------------------------------------------
    AnsiString __fastcall Big2Gb(AnsiString sBig)
    {
     char* pszBig5=NULL; //Big5编码的字符
     wchar_t* wszUnicode=NULL; //Unicode编码的字符
     char* pszGbt=NULL; //Gb编码的繁体字符
     char* pszGbs=NULL; //Gb编码的简体字符
     AnsiString sGb; //返回的字符串
     int iLen=0; //需要转换的字符数
    
     pszBig5=sBig.c_str(); //读入需要转换的字符参数
    
     //计算转换的字符数
     iLen=MultiByteToWideChar (950, 0, pszBig5, -1, NULL,0) ;
     //给wszUnicode分配内存
     wszUnicode=new wchar_t[iLen+1];
     //转换Big5码到Unicode码,使用了API函数MultiByteToWideChar
     MultiByteToWideChar (950, 0, pszBig5, -1, wszUnicode,iLen);
    
     //计算转换的字符数
     iLen=WideCharToMultiByte (936, 0, (PWSTR) wszUnicode, -1, NULL,0, NULL, NULL) ;
     //给pszGbt分配内存
     pszGbt=new char[iLen+1];
     //给pszGbs分配内存
     pszGbs=new char[iLen+1];
     //转换Unicode码到Gb码繁体,使用API函数WideCharToMultiByte
     WideCharToMultiByte (936, 0, (PWSTR) wszUnicode, -1, pszGbt,iLen, NULL, NULL) ;
    
     //转换Gb码繁体到Gb码简体,使用API函数LCMapString
     LCMapString(0x0804,LCMAP_SIMPLIFIED_CHINESE, pszGbt, -1, pszGbs, iLen);
    
     //返回Gb码简体字符
     sGb=pszGbs;
    
     //释放内存
     delete [] wszUnicode;
     delete [] pszGbt;
     delete [] pszGbs;
     
     return sGb;
    } 
2. 输入Gb字符,返回Big5字符
    //---------------------------------------------------------------------------
    //函数输入Gb字符,返回Big5字符
    //---------------------------------------------------------------------------
    AnsiString __fastcall Gb2Big(AnsiString sGb)
    {
     char* pszGbt=NULL; //Gb编码的繁体字符
     char* pszGbs=NULL; //Gb编码的简体字符
     wchar_t* wszUnicode=NULL; //Unicode编码的字符
     char* pszBig5=NULL; //Big5编码的字符
     AnsiString sBig5; //返回的字符串
     int iLen=0; //需要转换的字符数
    
     pszGbs=sGb.c_str(); //读入需要转换的字符参数
    
     //计算转换的字符数
     iLen=MultiByteToWideChar (936, 0, pszGbs, -1, NULL,0) ;
    
     //给pszGbt分配内存
     pszGbt=new char[iLen*2+1];
     //转换Gb码简体到Gb码繁体,使用API函数LCMapString
     LCMapString(0x0804,LCMAP_TRADITIONAL_CHINESE, pszGbs, -1, pszGbt, iLen*2);
    
     //给wszUnicode分配内存
     wszUnicode=new wchar_t[iLen+1];
     //转换Gb码到Unicode码,使用了API函数MultiByteToWideChar
     MultiByteToWideChar (936, 0, pszGbt, -1, wszUnicode,iLen);
    
     //计算转换的字符数
     iLen=WideCharToMultiByte (950, 0, (PWSTR) wszUnicode, -1, NULL,0, NULL, NULL) ;
     //给pszBig5分配内存
     pszBig5=new char[iLen+1];
     //转换Unicode码到Big5码,使用API函数WideCharToMultiByte
     WideCharToMultiByte (950, 0, (PWSTR) wszUnicode, -1, pszBig5,iLen, NULL, NULL) ;
    
     //返回Big5码字符
     sBig5=pszBig5;
    
     //释放内存
     delete [] wszUnicode;
     delete [] pszGbt;
     delete [] pszBig5;
    
     return sBig5;
    } 
标题: XML文档搜索使用小结
时间: 2004年6月30日 10:19:21
大家在.NET中处理XML文档的时候,经常会需要找到文档中的某个节点的数据。要找到某个节点,有许多种方法,在这里我就把几种常用的方法给大家总结一下。 
   
  首先,我们要做的是要把一个XML文档装入到一个XmlDocument对象中去。 
   
  先引用几个名字空间: 
   
  using System.Xml; 
  using System.Xml.Xsl; 
  using System.Xml.XPath; 
   
  这几个名字空间大家根据名字就知道它的意思了,我就不在这儿多说了。然后就是装入XML文件的代码,方法如下: 
   
  String xmlfile="c:/member.xml"; //其中的xmlfile是你要载入的XML文件的路径。 
  XmlDocument myDoc = new XmlDocument(); //定义一个XmlDocument对象。 
  myDoc.Load(xmlfile); 
   
   
  这样,我们就有一个叫myDoc的XML文档。我们现在就来找这个文档中的一些节点。我们先来看这个XML文件的内容。 
  <?xml version="1.0" encoding="UTF-8"?> 
  <members> 
   <member> 
   <name>Tim</name> 
   <hobby>reading</hobby> 
   <homepage>www.aspcool.com</homepage> 
   </member> 
   <member> 
   <name>Sandy</name> 
   <hobby>learning</hobby> 
   </member> 
   <member> 
   <name>Shally</name> 
   <hobby>tranlating</hobby> 
   </member> 
   <member> 
   <name>Christine</name> 
   <hobby>working</hobby> 
   </member> 
  </members> 
   
  我们现在可以用下面的方法找到name为tim的节点: 
  myDoc.ChildNodes.Item(1).ChildNodes.Item(0).FirstChild.InnerText 
   
  这个方法要求我们一层层向内找我们需要的数据,如果层次很多的话,做起来就会很费劲,也容易出错。幸好.NET给我们提供了另外一个方法SelectSingleNode和SelectNodes方法可以让我们直接找到所要的数据。比如,我们要找姓名为“Tim”的用户的hobby,我们可以用下面的方法: 
  myDoc.SelectSingleNode ("//member[name='Tim']").ChildNodes.Item(1).InnerText 
   
  其中//代表里面任意层的子节点。这样我们就可以很快的找到所要的东西。SelectSingleNode是找到一个单一的节点,SelectNodes可以找到许多节点。 
   
  在XML中寻找某个子节点,大家都知道怎么做了,我们现在在一个特殊的XML文件---XSL文件中去找一个子节点,这个应该怎么实现呢? 
   
  假设我现在有一个这样的XSL文件: 
  <?xml version="1.0" encoding="gb2312"?> 
  <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format"> 
   <xsl:preserve-space elements="codes"/> 
   <xsl:template match="/"> 
   <xsl:apply-templates/> 
   </xsl:template> 
   
   <xsl:template match="image"> 
   <table align="{@location}"> 
   <tr> 
   <td> 
   <img align="{@location}" alt="{text()}"> 
   <xsl:attribute name="src">../FTP_Magazine/FTP_Issue/<xsl:value-of select="@url"/></xsl:attribute> 
   </img> 
   </td> 
   </tr> 
   <tr> 
   <td> 
   <center> 
   <xsl:apply-templates/> 
   </center> 
   </td> 
   </tr> 
   </table> 
   </xsl:template> 
   
  </xsl:stylesheet> 
   
  我们在asp.net中有两个变量,我们需要XSL文件在Transform XML文件的时候采用这两个变量。我们该如何去做呢? 
   
  我所采取的方法是先把XSL文件作为XML Document装载进来,在使用之前,我们找到需要修改的节点,用我们的变量对其进行修改。这个时候我们查找这个节点的时候需要做些变动,代码如下: 
   
  XmlNamespaceManager nsmanager = new XmlNamespaceManager(xslDoc.NameTable); 
  nsmanager.AddNamespace("xsl", "http://www.w3.org/1999/XSL/Transform"); 
  xslDoc.SelectSingleNode("//xsl:attribute[@name='src']", nsmanager).InnerXml = 你所需要输给的变量 
   
  也就是说对于类似<xsl:attribute name="src">../FTP_Magazine/FTP_Issue/<xsl:value-of select="@url"/></xsl:attribute>这样的节点,在我们查找以前,我们需要定义一个XmlNamespaceManager,用它我们就可以找到我们所需要的节点。
标题: 在C#中操作XML
时间: 2004年6月30日 10:17:52
我用的是一种很笨的方法,但可以帮助初学者了解访问XML节点的过程。
 
已知有一个XML文件(bookstore.xml)如下:
<?xml version="1.0" encoding="gb2312"?>
<bookstore>
  <book genre="fantasy" ISBN="2-3631-4">
    <title>Oberon's Legacy</title>
    <author>Corets, Eva</author>
    <price>5.95</price>
  </book>
</bookstore>
 
1、往<bookstore>节点中插入一个<book>节点:
   XmlDocument xmlDoc=new XmlDocument();
   xmlDoc.Load("bookstore.xml");
   XmlNode root=xmlDoc.SelectSingleNode("bookstore");//查找<bookstore>
   XmlElement xe1=xmlDoc.CreateElement("book");//创建一个<book>节点
   xe1.SetAttribute("genre","李赞红");//设置该节点genre属性
   xe1.SetAttribute("ISBN","2-3631-4");//设置该节点ISBN属性
 
   XmlElement xesub1=xmlDoc.CreateElement("title");
   xesub1.InnerText="CS从入门到精通";//设置文本节点
   xe1.AppendChild(xesub1);//添加到<book>节点中
   XmlElement xesub2=xmlDoc.CreateElement("author");
   xesub2.InnerText="候捷";
   xe1.AppendChild(xesub2);
   XmlElement xesub3=xmlDoc.CreateElement("price");
   xesub3.InnerText="58.3";
   xe1.AppendChild(xesub3);
 
   root.AppendChild(xe1);//添加到<bookstore>节点中
   xmlDoc.Save("bookstore.xml");
//===============================================
结果为:
<?xml version="1.0" encoding="gb2312"?>
<bookstore>
  <book genre="fantasy" ISBN="2-3631-4">
    <title>Oberon's Legacy</title>
    <author>Corets, Eva</author>
    <price>5.95</price>
  </book>
  <book genre="李赞红" ISBN="2-3631-4">
    <title>CS从入门到精通</title>
    <author>候捷</author>
    <price>58.3</price>
  </book>
</bookstore>
 
2、修改节点:将genre属性值为“李赞红“的节点的genre值改为“update李赞红”,将该节点的子节点<author>的文本修改为“亚胜”。
    XmlNodeList nodeList=xmlDoc.SelectSingleNode("bookstore").ChildNodes;//获取bookstore节点的所有子节点
   foreach(XmlNode xn in nodeList)//遍历所有子节点
   {
    XmlElement xe=(XmlElement)xn;//将子节点类型转换为XmlElement类型
    if(xe.GetAttribute("genre")=="李赞红")//如果genre属性值为“李赞红”
    {
     xe.SetAttribute("genre","update李赞红");//则修改该属性为“update李赞红”
 
     XmlNodeList nls=xe.ChildNodes;//继续获取xe子节点的所有子节点
     foreach(XmlNode xn1 in nls)//遍历
     {
      XmlElement xe2=(XmlElement)xn1;//转换类型
      if(xe2.Name=="author")//如果找到
      {
       xe2.InnerText="亚胜";//则修改
       break;//找到退出来就可以了
      }
     }
     break;
    }
   }
 
   xmlDoc.Save("bookstore.xml");//保存。
//==================================================
最后结果为:
<?xml version="1.0" encoding="gb2312"?>
<bookstore>
  <book genre="fantasy" ISBN="2-3631-4">
    <title>Oberon's Legacy</title>
    <author>Corets, Eva</author>
    <price>5.95</price>
  </book>
  <book genre="update李赞红" ISBN="2-3631-4">
    <title>CS从入门到精通</title>
    <author>亚胜</author>
    <price>58.3</price>
  </book>
</bookstore>
 
3、删除 <book genre="fantasy" ISBN="2-3631-4">节点的genre属性,删除 <book genre="update李赞红" ISBN="2-3631-4">节点。
XmlNodeList xnl=xmlDoc.SelectSingleNode("bookstore").ChildNodes;
 
   foreach(XmlNode xn in xnl)
   {
    XmlElement xe=(XmlElement)xn;
    if(xe.GetAttribute("genre")=="fantasy")
    {
     xe.RemoveAttribute("genre");//删除genre属性
    }
    else if(xe.GetAttribute("genre")=="update李赞红")
    {
     xe.RemoveAll();//删除该节点的全部内容
    }
   }
   xmlDoc.Save("bookstore.xml");
//===========================================
最后结果为:
<?xml version="1.0" encoding="gb2312"?>
<bookstore>
  <book ISBN="2-3631-4">
    <title>Oberon's Legacy</title>
    <author>Corets, Eva</author>
    <price>5.95</price>
  </book>
  <book>
  </book>
</bookstore>
 
4、显示所有数据。
   XmlNode xn=xmlDoc.SelectSingleNode("bookstore");
 
   XmlNodeList xnl=xn.ChildNodes;
  
   foreach(XmlNode xnf in xnl)
   {
    XmlElement xe=(XmlElement)xnf;
    Console.WriteLine(xe.GetAttribute("genre"));//显示属性值
    Console.WriteLine(xe.GetAttribute("ISBN"));
 
    XmlNodeList xnf1=xe.ChildNodes;
    foreach(XmlNode xn2 in xnf1)
    {
     Console.WriteLine(xn2.InnerText);//显示子节点点文本
    }
   }
标题: 领悟Web设计模式
时间: 2004年6月17日 8:28:20
领悟Web设计模式
本文发表在《程序春秋》2004年1期
摘要
本文介绍了在.NET框架下应用Web设计模式改进WebForm程序设计的一些基本方法及要点。
关键字
设计模式,ASP.NET,WebForm,MVC,Page Controller,Front Controller,Page Cache
目录
? 引言
? 经典的WebForm架构
? 设计模式
? MVC模式下的WebForm
? Page Controller模式下的WebForm
? Front Controller模式下的WebForm
? Page Cache模式下的WebForm
? 参考资源
? 下载地址
? 作者信息
引言
记得微软刚刚推出ASP.NET时,给人的震撼是开发Web程序不再是编写传统的网页,而像是在构造应用程序,因而微软称之为WebForm。但是两年后的今天,有相当多的开发人员仍然延用写脚本程序的思路构建一个又一个的WebForm,而没有发挥出ASP.NET的优势,就此本文希望通过实例能够启发读者一些新的思路。
由于篇幅有限,本文不可能通过一个复杂的Web应用来向读者展示结合设计模式的WebForm,但是如果仅仅是一个小程序的确没有使用模式的必要。为了便于理解,希望您能把它想象成是一个大型系统中的小模块(如果代码是大型系统的一部分那么使用模式就变得非常重要)。
在本文的末尾给出了所有源程序的下载地址。
经典的WebForm架构
首先来看一个简单的应用,数据库设计如下图,Portal是Subject的父表,通过portalId进行一对多关联,程序需要根据portalId显示不同的Subject列表。
按照我们编写WebForm一般的习惯,首先在页面上拖放一个DropDownList、一个DataGrid、一个Button控件:
界面(webForm.aspx):
<form id="webForm" method="post" runat="server">
      <asp:DropDownList id="dropDownList" runat="server"></asp:DropDownList>
      <asp:Button id="button" runat="server" Text="Button"></asp:Button>
      <asp:DataGrid id="dataGrid" runat="server"></asp:DataGrid>
</form>
然后利用VS.NET代码隐藏功能编写的核心代码如下:
后置代码(webForm.aspx.cs):
//页面初始化事件
private void Page_Load(object sender, System.EventArgs e)
{
      if ( ! IsPostBack )
      {
            string SQL_SELECT_PORTAL = "SELECT * FROM PORTAL";
            //使用using确保释放数据库连接
            //连接字符串存放在Web.Config文件中便于修改
            using( SqlConnection conn = new SqlConnection( ConfigurationSettings.AppSettings["ConnectionString"] ) )
            {
                  SqlDataAdapter dataAdapter = new SqlDataAdapter( SQL_SELECT_PORTAL, conn );
                  DataSet dataSet = new DataSet();
                  dataAdapter.Fill( dataSet );
                  //设置下拉列表的数据源与文本域、值域
                  dropDownList.DataSource = dataSet;
                  dropDownList.DataTextField = "portalName";
                  dropDownList.DataValueField = "portalId";
                  dropDownList.DataBind();
            }
      }
}
//Button的Click事件
private void button_Click(object sender, System.EventArgs e)
{
      string SQL_SELECT_SUBJECT = "SELECT * FROM SUBJECT WHERE portalId = {0}";
      using( SqlConnection conn = new SqlConnection( ConfigurationSettings.AppSettings["ConnectionString"] ) )
      {
            //用下拉列表选择的值替换掉SQL语句中的待定字符{0}
            SqlDataAdapter dataAdapter = new SqlDataAdapter( string.Format( SQL_SELECT_SUBJECT, dropDownList.SelectedValue ), conn );
            DataSet dataSet = new DataSet();
            dataAdapter.Fill( dataSet );
            dataGrid.DataSource = dataSet;
            dataGrid.DataBind();
      }
}
执行结果如图所示,程序将根据下拉列表框选择的值绑定DataGrid,非常典型的一个WebForm架构,体现出ASP.NET事件驱动的思想,实现了界面与代码的分离。但是仔细看看可以从中发现几个问题:
? 对数据库操作的代码重复,重复代码是软件开发中绝对的“坏味道”,往往由于某些原因当你修改了一处代码,却忘记要更改另外一处相同的代码,从而给程序留下了Bug的隐患。
? 后置代码完全依赖于界面,在WebForm下界面的变化远远大于数据存储结构和访问的变化,当界面改变时您将不得不修改代码以适应新的页面,有可能将会重写整个后置代码。
? 后置代码不仅处理用户的输入而且还负责了数据的处理,如果需求发生变更,比如需要改变数据的处理方式,那么你将几乎重写整个后置代码。
一个优秀的设计需要每一个模块,每一种方法只专注于做一件事,这样的结构才清晰,易修改,毕竟项目的需求总是在不断变更的,“唯一不变的就是变化本身”,好的程序一定要为变化作出准备,避免“牵一发而动全身”,所以一定要想办法解决上述问题,下面让我们来看看设计模式。
设计模式
设计模式描述了一个不断重复出现的问题以及对该问题的核心解决方案,它是成功的构架、设计及实施方案,是经验的总结。设计模式的概念最早来自于西方建筑学,但最成功的案例首推中国古代的“三十六计”。
MVC模式下的WebForm
MVC模式是一个用于将用户界面逻辑与业务逻辑分离开来的基础设计模式,它将数据处理、界面以及用户的行为控制分为:Model-View-Controller。
? Model:负责当前应用的数据获取与变更及相关的业务逻辑
? View:负责显示信息
? Controller:负责收集转化用户的输入
View和Controller都依赖于Model,但是Model既不依赖于View,也不依赖于Controller,这是分离的主要优点之一,这样Model可以单独的建立和测试以便于代码复用,View和Controller只需要Model提供数据,它们不会知道、也不会关心数据是存储在SQL Server还是Oracle数据库中或者别的什么地方。
根据MVC模式的思想,可以将上面例子的后置代码拆分为Model和Controller,用专门的一个类来处理数据,后置代码作为Controller仅仅负责转化用户的输入,修改后的代码为:
Model(SQLHelper.cs):封装所有对数据库的操作。
private static string SQL_SELECT_PORTAL = "SELECT * FROM PORTAL";
private static string SQL_SELECT_SUBJECT = "SELECT * FROM SUBJECT WHERE portalId = {0}";
private static string SQL_CONNECTION_STRING = ConfigurationSettings.AppSettings["ConnectionString"];
public static DataSet GetPortal()
{
      return GetDataSet( SQL_SELECT_PORTAL );
}
public static DataSet GetSubject( string portalId )
{
      return GetDataSet( string.Format( SQL_SELECT_SUBJECT, portalId ) );
}
public static DataSet GetDataSet( string sql )
{
      using( SqlConnection conn = new SqlConnection( SQL_CONNECTION_STRING ) )
      {
            SqlDataAdapter dataAdapter = new SqlDataAdapter( sql, conn );
            DataSet dataSet = new DataSet();
            dataAdapter.Fill( dataSet );
            return dataSet; 
      }
}

Controller(webForm.aspx.cs):负责转化用户的输入
      private void Page_Load(object sender, System.EventArgs e)
      {
            if ( ! IsPostBack )
            {
                  //调用Model的方法获得数据源
                  dropDownList.DataSource = SQLHelper.GetPortal();
                  dropDownList.DataTextField = "portalName";
                  dropDownList.DataValueField = "portalId";
                  dropDownList.DataBind();
            }
      }
      private void button_Click(object sender, System.EventArgs e)
      {
            dataGrid.DataSource = SQLHelper.GetSubject( dropDownList.SelectedValue );
            dataGrid.DataBind();
      }
修改后的代码非常清晰,M-V-C各司其制,对任意模块的改写都不会引起其他模块的变更,类似于MFC中Doc/View结构。但是如果相同结构的程序很多,而我们又需要做一些统一的控制,如用户身份的判断,统一的界面风格等;或者您还希望Controller与Model分离的更彻底,在Controller中不涉及到Model层的代码。此时仅仅靠MVC模式就显得有点力不从心,那么就请看看下面的Page Controller模式。
Page Controller模式下的WebForm
MVC 模式主要关注Model与View之间的分离,而对于Controller的关注较少(在上面的MVC模式中我们仅仅只把Model和Controller分离开,并未对Controller进行更多的处理),但在基于WebForm的应用程序中,View和Controller本来就是分隔的(显示是在客户端浏览器中进行),而Controller是服务器端应用程序;同时不同用户操作可能会导致不同的Controller策略,应用程序必须根据上一页面以及用户触发的事件来执行不同的操作;还有大多数WebForm都需要统一的界面风格,如果不对此处理将可能产生重复代码,因此有必要对Controller进行更为仔细的划分。
Page Controller模式在MVC模式的基础上使用一个公共的页基类来统一处理诸如Http请求,界面风格等,如图:
传统的WebForm一般继承自System.Web.UI.Page类,而Page Controller的实现思想是所有的WebForm继承自定义页面基类,如图:
利用自定义页面基类,我们可以统一的接收页面请求、提取所有相关数据、调用对Model的所有更新以及向View转发请求,轻松实现统一的页面风格,而由它所派生的Controller的逻辑将变得更简单,更具体。
下面看一下Page Controller的具体实现:
Page Controller(BasePage.cs):
public class BasePage : System.Web.UI.Page
{
      private string _title;
      public string Title//页面标题,由子类负责指定
      {
            get
            {
                  return _title;
            }
            set
            {
                  _title = value;
            }
      }
      public DataSet GetPortalDataSource()
      {
            return SQLHelper.GetPortal();
      }
      public DataSet GetSubjectDataSource( string portalId )
      {
            return SQLHelper.GetSubject( portalId );
      }
      protected override void Render( HtmlTextWriter writer )
      {
            writer.Write( "<html><head><title>" + Title + "</title></head><body>" );//统一的页面头
            base.Render( writer );//子页面的输出
            writer.Write( @"<a href=""http://www.asp.net"">ASP.NET</a></body></html>" );//统一的页面尾
      }
}
现在它封装了Model的功能,实现了统一的页面标题和页尾,子类只须直接调用:
修改后的Controller(webForm.aspx.cs):
public class webForm : BasePage//继承页面基类
{
      private void Page_Load(object sender, System.EventArgs e)
      {
            Title = "Hello, World!";//指定页面标题
            if ( ! IsPostBack )
            {
                  dropDownList.DataSource = GetPortalDataSource();//调用基类的方法
                  dropDownList.DataTextField = "portalName";
                  dropDownList.DataValueField = "portalId";
                  dropDownList.DataBind();
            }
      }
      private void button_Click(object sender, System.EventArgs e)
      {
            dataGrid.DataSource = GetSubjectDataSource( dropDownList.SelectedValue );
            dataGrid.DataBind();
      }
}
从上可以看出BagePage Controller接管了大部分原来Controller的工作,使Controller变得更简单,更容易修改(为了便于讲解我没有把控件放在BasePage中,但是您完全可以那样做),但是随着应用复杂度的上升,用户需求的变化,我们很容易会将不同的页面类型分组成不同的基类,造成过深的继承树;又例如对于一个购物车程序,需要预定义好页面路径;对于向导程序来说路径是动态的(事先并不知道用户的选择)。
面对以上这些应用来说仅仅使用Page Controller还是不够的,接下来再看看Front Controller模式。
Front Controller模式下的WebForm
Page Controller的实现需要在基类中为页面的公共部分创建代码,但是随着时间的推移,需求会发生较大的改变,有时不得不增加非公用的代码,这样基类就会不断增大,您可能会创建更深的继承层次结构以删除条件逻辑,这样一来我们很难对它进行重构,因此需要更进一步对Page Controller进行研究。
Front Controller通过对所有请求的控制并传输解决了在Page Controller中存在的分散化处理的问题,它分为Handler和Command树两个部分,Handler处理所有公共的逻辑,接收HTTP Post或Get请求以及相关的参数并根据输入的参数选择正确的命令对象,然后将控制权传递到Command对象,由其完成后面的操作,在这里我们将使用到Command模式。
Command模式通过将请求本身变成一个对象可向未指定的应用对象提出请求,这个对象可被存储并像其他的对象一样被传递,此模式的关键是一个抽象的Command类,它定义了一个执行操作的接口,最简单的形式是一个抽象的Execute操作,具体的Command子类将接收者作为其一个实例变量,并实现Execute操作,指定接收者采取的动作,而接收者具有执行该请求所需的具体信息。
因为Front Controller模式要比上面两个模式复杂一些,我们再来看看例子的类图:
关于Handler的原理请查阅MSDN,在这就不多讲了,我们来看看Front Controller模式的具体实现:
首先在Web.Config里定义:
<!-- 指定对Dummy开头的aspx文件交由Handler处理 -->
<httpHandlers>
      <add verb="*" path="/WebPatterns/FrontController/Dummy*.aspx" type="WebPatterns.FrontController.Handler,WebPatterns"/>
</httpHandlers>
<!-- 指定名为FrontControllerMap的页面映射块,交由UrlMap类处理,程序将根据key找到对应的url作为最终的执行路径,您在这可以定义多个key与url的键值对 -->
<configSections>
      <section name="FrontControllerMap" type="WebPatterns.FrontController.UrlMap, WebPatterns"></section>
</configSections>
<FrontControllerMap>
      <entries>
            <entry key="/WebPatterns/FrontController/DummyWebForm.aspx" url="/WebPatterns/FrontController/ActWebForm.aspx" />
            。。。
      </entries>
</FrontControllerMap>
修改webForm.aspx.cs:
private void button_Click( object sender, System.EventArgs e )
{
      Response.Redirect( "DummyWebForm.aspx?requestParm=" + dropDownList.SelectedValue );
}
当程序执行到这里时将会根据Web.Config里的定义触发类Handler的ProcessRequest事件:
Handler.cs:
public class Handler : IHttpHandler
{
      public void ProcessRequest( HttpContext context )
      {
            Command command = CommandFactory.Make( context.Request.Params );
            command.Execute( context );
      }
      public bool IsReusable
      {
            get 
            { 
                  return true;
            }
      }
}
而它又会调用类CommandFactory的Make方法来处理接收到的参数并返回一个Command对象,紧接着它又会调用该Command对象的Execute方法把处理后参数提交到具体处理的页面。
public class CommandFactory
{
      public static Command Make( NameValueCollection parms )
      {
            string requestParm = parms["requestParm"];
            Command command = null;
            //根据输入参数得到不同的Command对象
            switch ( requestParm )
            {
                  case "1" :
                        command = new FirstPortal();
                        break;
                  case "2" :
                        command = new SecondPortal();
                        break;
                  default :
                        command = new FirstPortal();
                        break;
            }
            return command;
      }
}
public interface Command
{
      void Execute( HttpContext context );
}
public abstract class RedirectCommand : Command
{
      //获得Web.Config中定义的key和url键值对,UrlMap类详见下载包中的代码
      private UrlMap map = UrlMap.SoleInstance;
      protected abstract void OnExecute( HttpContext context );
      public void Execute( HttpContext context )
      {
            OnExecute( context );
            //根据key和url键值对提交到具体处理的页面
            string url = String.Format( "{0}?{1}", map.Map[ context.Request.Url.AbsolutePath ], context.Request.Url.Query );
            context.Server.Transfer( url );
      }
}
public class FirstPortal : RedirectCommand
{
      protected override void OnExecute( HttpContext context )
      {
            //在输入参数中加入项portalId以便页面处理
            context.Items["portalId"] = "1";
      }
}
public class SecondPortal : RedirectCommand
{
      protected override void OnExecute(HttpContext context)
      {
            context.Items["portalId"] = "2";
      }
}
最后在ActWebForm.aspx.cs中:
dataGrid.DataSource = GetSubjectDataSource( HttpContext.Current.Items["portalId"].ToString() );
dataGrid.DataBind();
上面的例子展示了如何通过Front Controller集中和处理所有的请求,它使用CommandFactory来确定要执行的具体操作,无论执行什么方法和对象,Handler只调用Command对象的Execute方法,您可以在不修改 Handler的情况下添加额外的命令。它允许让用户看不到实际的页面,当用户输入一个URL时,然后系统将根据web.config文件将它映射到特定的URL,这可以让程序员有更大的灵活性,还可以获得Page Controller实现中所没有的一个间接操作层。
对于相当复杂的Web应用我们才会采用Front Controller模式,它通常需要将页面内置的Controller替换为自定义的Handler,在Front Controllrer模式下我们甚至可以不需要页面,不过由于它本身实现比较复杂,可能会给业务逻辑的实现带来一些困扰。
以上两个Controller模式都是处理比较复杂的WebForm应用,相对于直接处理用户输入的应用来讲复杂度大大提高,性能也必然有所降低,为此我们最后来看一个可以大幅度提高程序性能的模式:Page Cache模式。
Page Cache模式下的WebForm
几乎所有的WebForm面临的都是访问很频繁,改动却很少的应用,对WebForm的访问者来说有相当多的内容是重复的,因此我们可以试着把WebForm或者某些相同的内容保存在服务器内存中一段时间以加快程序的响应速度。
这个模式实现起来很简单,只需在页面上加入:
<%@ OutputCache Duration="60" VaryByParam="none" %>,
这表示该页面会在60秒以后过期,也就是说在这60秒以内所有的来访者看到该页面的内容都是一样的,但是响应速度大大提高,就象静态的HTML页面一样。
也许您只是想保存部分的内容而不是想保存整个页面,那么我们回到MVC模式中的SQLHelper.cs,我对它进行了少许修改:
public static DataSet GetPortal()
{
      DataSet dataSet;
      if ( HttpContext.Current.Cache["SELECT_PORTAL_CACHE"] != null )
      {
            //如果数据存在于缓存中则直接取出
            dataSet = ( DataSet ) HttpContext.Current.Cache["SELECT_PORTAL_CACHE"];
      }
      else
      {
            //否则从数据库中取出并插入到缓存中,设定绝对过期时间为3分钟
            dataSet = GetDataSet( SQL_SELECT_PORTAL );
            HttpContext.Current.Cache.Insert( "SELECT_PORTAL_CACHE", dataSet, null, DateTime.Now.AddMinutes( 3 ), TimeSpan.Zero );
      }
      return dataSet;
}
      
在这里把SELECT_PORTAL_CACHE作为Cache的键,把GetDataSet( SQL_SELECT_PORTAL )取出的内容作为Cache的值。这样除了程序第1次调用时会进行数据库操作外,在Cache过期时间内都不会进行数据库操作,同样大大提高了程序的响应能力。
小结
自从.NET框架引入设计模式以后在很大程度上提高了其在企业级应用方面的实力,可以毫不夸张的说在企业级应用方面.NET已经赶上了Java的步伐并大有后来居上之势,本文通过一个实例的讲解向读者展示了在.NET框架下实现Web设计模式所需的一些基本知识,希望能起到一点抛砖引玉的作用。
参考资源