2010年5月24日

ASP.NET使用Request获取来源网址URL参数

Request对象功能是从客户端得到数据,常用的三种取得数据的方法是:Request.Form、 Request.QueryString,Request。其第三种是前两种的一个缩写,可以取代前两种情况。而前两种主要对应的Form提交时的两种不同的提交方法:分别是Post方法和Get方法。
        Request 对象的属性和方法比较多,常用的几个为:UserAgent 传回客户端浏览器的版本信息,UserHostAddress 传回远方客户端机器的主机IP 地址,UserHostName 传回远方客户端机器的DNS 名称,PhysicalApplicationPath 传回目前请求网页在Server 端的真实路径。

从浏览器获取数据
        利用Request方法,可以读取其他页面提交过来的数据。提交的数据有两种形式:一种是通过Form表单提交过来,另一种是通过超级链接后面的参数提交过来,两种方式都可以利用Request对象读取。


<%@ Page Language="C#"%>
<%
    string strUserName = Request["Name"];
    string strUserLove = Request["Love"];
%>
姓名:<%=strUserName%>
爱好:<%=strUserLove%>
<form action="" method="post">
<P> 姓名:<input type="TEXT" size="20" name="Name"></P>
<P> 兴趣:<input type="TEXT" size="20" name="Love"></P>
<P><input type="submit" value="提 交"></P>
</form>

得到客户端的信息
        利用Request对象内置的属性,可以得到一些客户端的信息,比如客户端浏览器版本和客户端地址等等

<%@ Page Language="C#"%>
客户端浏览器:<%=Request.UserAgent %>
客户端IP地址:<%=Request.UserHostAddress %>
当前文件服务端物理路径:<%=Request.PhysicalApplicationPath %>

Request 对象的属性

  UserLanguages 客户端主机所使用的语言

  UserHostName 客户端主机的DNS名称

  userHostArrress 客户端主机的IP地址

  UserAgent 客户端浏览器版本

  Url 当前要求的URL

  TotalBytes 当前输入的容量大小

  ServerVariables 网页的Server变量

  RequestType 客户端网页的传送方式(Get/Post)

  RawUrl 当前页面的U『RL

  QueryString 浏览器地址栏后的参数

  PhysicalPath 当前网页在服务器端的实际路径

  PhysicalApplicationPath 当前在服务器端执行的程序的实际路径

  Pathq 当前网页的相对地址

  Params 返回QueryString、Form、Cookies、ServerVariables的全部集合     

   IsSecureConnection 目前联机的安全性

  IsAuthenticated 目前联机是否有效

  HttpMethod 目前客户端网页的传送方式(Get/Post)

  Headers 网页的标题集合

  Form 窗体变量

  Files 客户端上传的文件

  FilePath 当前执行网页的相对地址

  Cookies HttpCookieCollection对象集合

  ContentType 当前需求的MIME内容类型

  ContentEncoding 客户端浏览器的字符设置 -

  ConnectionlD 客户端所提出的网页浏览请求的联机的ID

  ClientCertificate 客户端安全认证信息

  Browser 客户端浏览器的信息

  AnnlicationPath 当前运行程序的服务器端虚拟目录

--------------------------http://www.bjsspp.com--------------------------

ASP.NET的UrlReferrer方法虽然可以获取来源网址,但最近在进行移动开发的时候,虽然UrlReferrer获取的包含中文参数的地址是正确的,但设置为Link的NavigateUrl属性时,点击链接返回后,来源网址获取的是乱码。所以使用该替代方法getParam:

   private string getParam(string strHref, string strName)
    {
        int intPos = strHref.IndexOf("?");
        if (intPos < 1)
            return "";

        string strRight = strHref.Substring(intPos + 1);

        string[] arrPram = Utils.SplitString(strRight, "&");//SplitString方法:将某字符串按特定字符或字符串分割为字符串数组
        for (int i = 0; i < arrPram.Length; i++)
        {
            string[] arrPramName = Utils.SplitString(arrPram[i], "=");
            if (arrPramName[0].ToLower() == strName.ToLower()) return arrPramName[1];
        }
        return "";
    }

如何使用:

protected void Page_Load(object sender, EventArgs e)
    {
        string url = Request.UrlReferrer.ToString();
        url = "SearchResult.aspx?sArea=" + Server.UrlEncode(getParam(url, "sArea")) + "&sKeyword=" + Server.UrlEncode(getParam(url, "sKeyword"));
        linkBack.NavigateUrl = url;
    }

-------------------------------------------疯狂_飞鱼 ------------------------------------------------------------

asp.net中Request对象 技术浅析 (转)

Request对象
当客户端发出请求执行asp.net程序时,CLR会将客户端的请求信息包含在Request对象中。这些请求信息包括请求报头,客户端的基本信息(如浏览器类型,浏览器版本号,用户所用的语言以及编码方式等),请求方法(如post,get),参数名,参数值等。
Request 对象的调用方法:Request.Collection["Variable"];
其中Collection包括四种集合:QueryString,Form,Cookies,ServerVariables。
1.QueryString集合收集的信息来源于请求url地址中"?"号后面的数据,这些数据称作url附加信息。例如,www.sina.com/show.asp?id=111
在此url中,QueryString收集到的信息是"show.asp?"后面的数据"id=111"。此时,取得参数"id"的参数值的语句为:Request.QueryString["id"];
QueryString主要用于收集http协议中get请求发送的数据,如果在一个请求事件中被请求的程序url地址出现了"?"号后的数据,则表示此次请求方式为get。get方法是http中的默认请求方法。
那么,如何发送get请求方法呢?
(1)<a href="show.aspx?id=111">显示ID为111的文章</a>
   (2)<form action="show.aspx" method="get">
     <input type="text" name="id" value="111">
     </form>
下面演示一个例子,这个例子中有两个程序,分别是form1.htm和GetInfo1.aspx,其中form1.htm发送get请求,GetInfo1.aspx接收get请求。
///////////////////////////////////////////////////
form1.htm
///////////////////////////////////////////////////
<HTML>
<HEAD>
<TITLE> 发送GET请求</TITLE>
</HEAD>
<BODY>
<center>
发送GET请求
<hr>
<form action="GetInfo1.aspx" method="get">
您的大名:<input type="text" name="nickname"><br>
<input type="submit" value="发送">
<form>
</center>
</BODY>
</HTML>
//////////////////////////////////////////
GetInfo1.aspx
//////////////////////////////////////////
<% @ Page Language="C#" %>
<html>
<head>
<title> 接收GET请求</title>
</head>
<body>
<center>
接收GET方法传来的值:
<hr>
<%
      string NickName = Request.QueryString["nickname"];<%--接收参数值--%>
      Response.Write("GET方法传来的nickname的值为:"+NickName);
%>
</center>
</body>
</html>
2.get方法是将传递的数据追加至url中。url地址长度是有限制的,因此使用get方法所能传递的数据也是有限的。一般地,get方法能够传递256字节的数据,在多数情况下,使用get方法传递的数据长度是远远不够的,这时便需要使用http的另外一种请求方式post,post方法可传递的数据的最大值为2MB。
post请求必须由form发出。而且,在使用post请求方法的时候,需要将"method"设置为"post"。asp.net使用Request.Form方法接收post方法传递的数据:Request.Form["Variable"];
接下来也举两个例子,来说明post方法的使用,类似于上面的例子,注意对比。
//////////////////////////////////////////////
form2.htm//发送post请求
//////////////////////////////////////////////
<HTML>
<HEAD>
<TITLE> 发送POST请求</TITLE>
</HEAD>
<BODY>
<center>
发送POST请求
<hr>
<form action="GetInfo2.aspx" method="post">
您的大名:<input type="text" name="nickname"><br>
<input type="submit" value="发送">
<form>
</center>
</BODY>
</HTML>
//////////////////////////////////////////////////
GetInfo2.aspx// 接收post请求
//////////////////////////////////////////////////
<% @ Page Language="C#" %>
<html>
<head>
<title> 接收POST请求</title>
</head>
<body>
<center>
接收POST方法传来的值:
<hr>
<%
      string NickName = Request.Form["nickname"];
      Response.Write("POST方法传来的nickname的值为:"+NickName);
%>
</center>
</body>
</html>
3.cookie的使用在后面再讲。
4.ServerVariable
ServerVariable(环境变量)包含了客户机和服务器的系统信息。获得环境变量值的方法是:Request.ServerVariables["Variable"];
Variable参数            含义
HTTP_USER_AGENT          获得用户使用的浏览器类型和版本
REMOTE_ADDR              获取用户的IP地址
REQUEST_METHOD          获取请求的方法
LOCAL_ADDR                获取服务器的IP地址
SERVER_NAME              获取服务器的主机名
PATH_INFO                  获取当前执行程序的虚拟路径
PATH_TRANSLATED          获取当前执行程序的绝对路径
CONTENT_LENGTH          获取请求程序所发送内容的字符总数
CONTENT_TYPE              获取请求的信息类型
GATEWAY_INTERFACE        获取网关接口
QUERY_STRING              获取url的附加信息
SCRIPT_NAME                获取当前程序的文件名(包含虚拟路径)
SERVER_PORT                获取服务器接受请求的端口
SERVER_PROTOCOL          获取服务器遵从的协议以及版本号
HTTP_ACCEPT_LANGUAGE    获取用户所使用的语言
最后举一个关于ServerVariable的例子,这个例子的结果也可作为日后查阅ServerVariable集合的原始资料。
/////////////////////////////////////////////////////////
<% @ Page Language="C#" %>
<% @ Import Namespace="System.Data" %>
<Script Language="C#" Runat="Server">
public void Page_Load(Object src,EventArgs e)
{
      //取得ServerVariables变量集合
      NameValueCollection ServerVariables = Request.ServerVariables;
    
      //产生一个数据集合,它的用法,我们后面再讨论
      DataTable dt = new DataTable();
      DataRow dr;

      dt.Columns.Add(new DataColumn("环境变量",typeof(string)));
      dt.Columns.Add(new DataColumn("变量值",typeof(string)));


      foreach(string SingleVariable in ServerVariables)
      {
            dr = dt.NewRow();
            dr[0] = SingleVariable;
            dr[1] = ServerVariables[SingleVariable].ToString();
            dt.Rows.Add(dr);
      }

      DataGrid1.DataSource = new DataView(dt);
      DataGrid1.DataBind();
}
</script>
<html>
<head>
<title></title>
</head>
<body>
<ASP:DataGrid id="DataGrid1" runat="server"
BorderColor="black"
BorderWidth="1"
GridLines="Both"
CellPadding="3"
CellSpacing="0"
Font-Name="Verdana"
Font-Size="8pt"
HeaderStyle-BackColor="#aaaadd"
AlternatingItemStyle-BackColor="#eeeeee"
/>
</body>
</html>
//////////////////////////////////////
5.Request的Browser对象
Request.Browser.Browser//检测浏览器的类型
Request.Browser.Version// 检测浏览器的版本
Request.Browser.ActiveXControls//检测浏览器是否支持ActiveX控件
Request.Browser.Cookies// 检测浏览器是否支持Cookies
Request.Browser.VBScript//检测浏览器是否支持VBScript
下面这个例子说明Browser所有能够访问的属性。
////////////////////////////////////////////////////////////
<html>
<head>
<script runat="server" language="c#">
   public void Page_Load(Object Source, EventArgs E)
   {
     HttpBrowserCapabilities bc= Request.Browser;

     Welcome.Text = "您好,您正在使用 " + bc.Browser + " v." + bc.Version + ",你的运行平台是 " + bc.Platform ;


     ActiveXControls.Text = bc.ActiveXControls.ToString();
     AOL.Text = bc.AOL.ToString();
     BackgroundSounds.Text = bc.BackgroundSounds.ToString();
     Beta.Text = bc.Beta.ToString();
     Browser.Text = bc.Browser.ToString();
     CDF.Text = bc.CDF.ToString();
     Cookies.Text = bc.Cookies.ToString();
     Crawler.Text = bc.Crawler.ToString();
     Frames.Text = bc.Frames.ToString();
     JavaApplets.Text = bc.JavaApplets.ToString();
     JavaScript.Text = bc.JavaScript.ToString();
     MajorVersion.Text = bc.MajorVersion.ToString();
     MinorVersion.Text = bc.MinorVersion.ToString();
     Platform.Text = bc.Platform.ToString();
     Tables.Text = bc.Tables.ToString();
     Type.Text = bc.Type.ToString();
     VBScript.Text = bc.VBScript.ToString();
     Version.Text = bc.Version.ToString();
     Win16.Text = bc.Win16.ToString();
     Win32.Text = bc.Win32.ToString();
   }
</script>
<style>
body{font-size:9pt}
td{font-size:9pt}
</style>
</head>
<body>
<form runat="server" method="post">
您的浏览器信息已经完全在我们的掌握中了 ^&^:<br>
<asp:Label runat="server" id="Welcome" Font-Bold="True" />
<table border="1" width="400" bordercolor="black">
<tr bgcolor="skyblue">
<td width="50%"><b>浏览器属性<b></td>
<td width="50%"><b>检测结果<b></td>
</tr>
<tr >
<td width="50%">ActiveXControls:</td>
<td width="50%"><asp:Label runat="server" id="ActiveXControls" /></td>
</tr>
<tr bgcolor="skyblue">
<td width="50%">AOL:</td>
<td width="50%"><asp:Label runat="server" id="AOL" /></td>
</tr>
<tr >
<td width="50%">BackgroundSounds:</td>
<td width="50%"><asp:Label runat="server" id="BackgroundSounds" /></td>
</tr>
<tr bgcolor="skyblue">
<td width="50%">Beta:</td>
<td width="50%"><asp:Label runat="server" id="Beta" /></td>
</tr>
<tr >
<td width="50%">Browser:</td>
<td width="50%"><asp:Label runat="server" id="Browser" /></td>
</tr>
<tr bgcolor="skyblue">
<td width="50%">CDF:</td>
<td width="50%"><asp:Label runat="server" id="CDF" /></td>
</tr>
<tr >
<td width="50%">Cookies:</td>
<td width="50%"><asp:Label runat="server" id="Cookies" /></td>
</tr>
<tr bgcolor="skyblue">
<td width="50%">Crawler:</td>
<td width="50%"><asp:Label runat="server" id="Crawler" /></td>
</tr>
<tr>
<td width="50%">Frames:</td>
<td width="50%"><asp:Label runat="server" id="Frames" /></td>
</tr>
<tr bgcolor="skyblue">
<td width="50%">JavaApplets:</td>
<td width="50%"><asp:Label runat="server" id="JavaApplets" /></td>
</tr>
<tr>
<td width="50%">JavaScript:</td>
<td width="50%"><asp:Label runat="server" id="JavaScript" /></td>
</tr>
<tr bgcolor="skyblue">
<td width="50%">MajorVersion:</td>
<td width="50%"><asp:Label runat="server" id="MajorVersion" /></td>
</tr>
<tr>
<td width="50%">MinorVersion:</td>
<td width="50%"><asp:Label runat="server" id="MinorVersion" /></td>
</tr>
<tr bgcolor="skyblue">
<td width="50%">Platform:</td>
<td width="50%"><asp:Label runat="server" id="Platform" /></td>
</tr>
<tr>
<td width="50%">Tables:</td>
<td width="50%"><asp:Label runat="server" id="Tables" /></td>
</tr>
<tr bgcolor="skyblue">
<td width="50%">Type:</td>
<td width="50%"><asp:Label runat="server" id="Type" /></td>
</tr>
<tr>
<td width="50%">VBScript:</td>
<td width="50%"><asp:Label runat="server" id="VBScript" /></td>
</tr>
<tr bgcolor="skyblue">
<td width="50%">Version:</td>
<td width="50%"><asp:Label runat="server" id="Version" /></td>
</tr>
<tr>
<td width="50%">Win16:</td>
<td width="50%"><asp:Label runat="server" id="Win16" /></td>
</tr>
<tr bgcolor="skyblue">
<td width="50%">Win32:</td>
<td width="50%"><asp:Label runat="server" id="Win32" /></td>
</tr>
</table>
</form>
</body>
</html>
//////////////////////////////////////////////////////////////////
6. 其它Request属性和方法
FilePath          取得当前请求的文件路径
HttpMethod      取得当前请求的方法
Files            关乎文件的上传,后面会讲解
Params          获得QueryString+Form+ServerVariable+Cookies的集合
TotalBytes      请求内容的大小
Url              获得url信息
UserHostAddress 取得用户的IP地址
UserHostName    取得用户的主机名
UserLanguages    取得用户所用语言

aspx页面事件执行顺序

aspx页面事件执行顺序
       
一、详细版
l 初始化
2 当页面被提交请求第一个方法永远是构造函数。您可以在构造函数里面初始一些自定义属性或对象,不过这时候因为页面还没有被完全初始化所以多少会有些限制。特别地,您需要使用 HttpContext对象。当前可以使用的对象包括QueryString, Form以及Cookies集合,还有Cache对象。注意:在构造函数里是不允许使用Session的。

2 下一个将执行的方法是AddParsedSubObject方法,这个方法将添加所有独立的控件并把页面组成一个控件集合树,这个方法经常被一些高级的页面模板解决方案(Page Template Solutions)重写以便添加页面内容到页面模板(Page Template)中一些特殊的控件中。这个方法递归应用到所有的页面控件及相应的的每个子控件,所有的控件都是在这个方法中开始最早的初始化。

2 页面类中下一个将执行的方法是DeterminePostBackMode。这个方法允许您修改 IsPostBack的值及相关的事件。如果您需要从数据库中加载ViewState这个方法将特别有用,因为ViewState只有在 IsPostBack为真的情况下才会进行恢复。返回空将会导致强制执行非回传,返回Request.Form则强制执行一个回传。除非在特殊情况下,否则并不建议去操作这个,因为这个还会影响其他的事件。

2 下一个将要执行的方法是OnInit方法,一般这是第一个真正被使用的方法。这个方法触发时,所有页面定义中的控件执行初始化,这意味着所有在页面中定义的值应用到相应的控件上。不过,ViewState和传回的值还不会应用到控件上,因此,任何被代码或用户改变的值还没有被恢复到控件上。这个方法通常是最好的创建、重创建动态控件的好地方。
l 恢复及加载
2 下一个方法,LoadPageStateFromPersistenceMedium只会在页面被回传的时候才会被执行。如果因为使用Session或自定义存储方式,您修改了后面将要提到的影响ViewState保存方式的方法SavePageStateToPersistenceMedium,则这个方法需要被重写。默认的实现中ViewState是一种Base64格式编码,并且被保存在页面的隐藏域中,您可以使用这篇文章中提及的方法修改 ViewState按以上两种方式保存。注意:这个方法并没有真正加载ViewState到页面或页面控件中。

2 当得到ViewState后,下一个方法LoadViewSate,将以递归的方式恢复ViewState到页面及各个页面控件或子控件中。这个方法执行后,每个控件都将恢复到上一次的状态,但是用户提交的数据还没有应用到控件上,因为他们不是ViewState的一部分。这个方法主要用于恢复您在其他事件中动态生成的控件的值,他们的值是您手动保存在ViewSate中,并且现在已经失效。

2 下一个方法是ProcessPostData,这个方法也同样是回传的时候才会被执行,并且不允许被重写,这个是页面基类的私有方法。这个方法通过匹配控件的名称恢复相应的用户提交的控件的值,到这一步意味着整个页面都已经被完全恢复了。唯一要记住的是所有动态控件的创建必须在这个方法之前。这个方法也是记录后面的改变事件的方法。

2 下一个方法是OnLoad方法,通常这是用得最多的方法,因为这个方法是页面生存期第一个恢复了所有值的地方。大多数代码根据判断IsPostBack来决定是否重新设置控件状态。您也可以在这个方法中调用Validate并且检查IsValid的值。也可以在这个方法中创建动态控件,并且该控件的所有的方法都会被执行以追上当前页面的状态包括ViewSate,不过不包括回传的值。
l 事件处理
2 下一个方法还是ProcessPostData,实际上就是前一个方法的另一次调用,它仍然是只在回传的时候执行并且由于是私有方法不可以被重写。如果您是第一次看页面的运行轨迹也许会觉得这个方法有些多余。但实际上这个方法是必要的因为在OnLoad中创建的动态控件也需要他们回传的值。任何在这以后创建的控件将可以得到他们的ViewState,但是不能再得到他们的回传的值,并且不会触发任何值改变事件(Change Event)。

2 下一个方法,RaiseChangedEvents,也是只在回传页面中执行,并且也因为是基类的私有方法所有不能被继承。在整个页面生存期中,是在这儿根据之前的ProcessPostData记录的控件的值和提交的值是否不同来触发值改变事件。您也许需要调用 Validate或者检查IsValid的值。这里并没有特别的说明多个值改变事件的执行先后顺序。

2 下一个方法,RaisePostBackEvent,同样是因为是基类的私有方法不能被继承,同样也是只在回传页面中执行。除非使用了AutoPostBack,不然这是实际提交表单事件执行的地方,特别是按钮或者其实使用javascript提交表单等。如果还没有被手动调用过并且使用了验证控件,那么Validate会被调用。注意IE中有个BUG有时会允许提交但却不触发任何事件。

2 下一个方法是OnPreRender,一般这是在客户端展现页面之前改变页面及其控件的最后一次机会。您也可以在这个方法里面创建动态控件,并且所有的方法都会被执行以追上当前页面的状态包括ViewSate,但是私有方法将不会被执行,这意味着不会有回传的值并且不会有事件触发。由于IE中的BUG,这是一个没有事件赶上PostBack的好地方。
l 保存及显示
2 下一个方法是 SaveViewState,不论是否是回传页面,均会递归的执行以保存页面及其所有控件的ViewState。ViewState基本上保存所有与定义在aspx中的原始值不同的值,不管是被代码还是用户所改变。注意控件值是根据他们在页面的控件树中的位置来保存的,所以如果动态控件后来加到了错误的位置将会导致混乱。

2 下一个方法是 SavePageStateToPersistenceMedium真正的保存页面的ViewSate。这个方法随同 LoadPageStateFromPersistenceMediumg 一起被重写以便保存ViewState到Session或其它自定义数据,而不是用隐藏域。这对于低带宽的用户来说是很有帮助的。并且对于移动设备来说,Session是默认设置。下面这篇文章描述了使用以上两种方式保存ViewState的具体细节。注意在Asp.net中有个 Bug:Asp.net要求必须提交__viewstate字段,即使是空的。

2 下一个方法是Render方法,该方法递归的创建并发送相应控件的html给浏览器。这个方法被一些页面模板方案重写以添加一些通用的页面头与脚而不使用服务器控件,他们总是有一些额外的东西。注意这儿的修改只能使用纯HTML,因为控件在这儿已经被生成了。您可以用 StringBuilder,StringWriter,HtmlTextWriter捕获相应的HTML输出。

2 最后的方法是OnUnload,这个方法会调用相应的Dispose方法。这个方法提供机会以便清空该页面中使用的非托管资源,如关闭打开的文件句柄,以前打开的数据库连接等。注意这个方法是在页面已经发送到客户端以后执行的,所以它只有影响服务器对象,并且它不会显示在页面的显示轨迹中。这就是页面的生存期,对于每一次请求都是这么运行的。


二、精简版

Page 执行中将按照如下顺序激活事件:Page.PreInit---->Page.Init---->Page.InitComplite---->Page.PreLoad---->Page.Load---->Page.LoadComplete---->Page.PreRender---->

Page.PreRenderComplete

如果页面从令一个页面继承,如BasePage:System.Web.UI.Page,在BasePage中做了一些扩展,如权限检查,而其他页面从BasePage继承,则BasePage和最终Page的事件激活顺序是:

UI.PreInit---->Page.PreInit---->UI.Init---->Page.Init---->UI.InitComplite---->Page.InitComplite---->UI.PreLoad---->Page.PreLoad---->UI.Load---->Page.Load---->UI.LoadComplete---->Page.LoadComplete---->UI.PreRender---->Page.PreRender---->UI.PreRenderComplete---->Page.PreRenderComplete

如果使用了 MasterPage,则MasterPage中的事件和ContentPage中的事件按照下面顺序激活:

ContentPage.PreInit

Master.Init

ContentPage.Init

ContentPage.InitComplite

ContentPage.PreLoad

ContentPage.Load

Master.Load

ContentPage.LoadComplete

ContentPage.PreRender

Master.PreRender

ContentPage.PreRenderComplete

更进一步,如果 ContentPage继承BasePage,那么,各事件的执行顺序将变成:

UI.PreInit

ContentPage.PreInit

Master.Init

UI.Init

ContentPage.Init

UI.InitComplite

ContentPage.InitComplite

UI.PreLoad

ContentPage.PreLoad

UI.Load

ContentPage.Load

Master.Load

UI.LoadComplete

ContentPage.LoadComplete

UI.PreRender

ContentPage.PreRender

Master.PreRender

UI.PreRenderComplete

ContentPage.PreRenderComplete


值显示在浏览器的地址栏中。如果是传递一个或多个安全性要求不高或是结构简单的数值时,可以使用这个方法。但是对于传递数组或对象的话,就不能用这个方法了。下面是一个例子:

a.aspx 的C#代码
private void Button1_Click(object sender, System.EventArgs e)
{
string s_url;
s_url = “b.aspx?name=” + Label1.Text;
Response.Redirect(s_url);
}
b.aspx 中C#代码
private void Page_Load(object sender, EventArgs e)
{
Label2.Text = Request.QueryString["name"];
}

2. 使用Application 对象变量
Application 对象的作用范围是整个全局,也就是说对所有用户都有效。其常用的方法用Lock和UnLock。
a.aspx的C#代码
private void Button1_Click(object sender, System.EventArgs e)
{
Application["name"] = xx.Text;
Server.Transfer(”b.aspx”);
}
b.aspx中C#代码
private void Page_Load(object sender, EventArgs e)
{
string name;
Application.Lock();
name = Application["name"].ToString();
Application.UnLock();
}

3. 使用Session变量
想必这个肯定是大家使用中最常见的用法了,其操作与Application类似,作用于用户个人,所以,过量的存储会导致服务器内存资源的耗尽。一个session只有20K
a.aspx的C#代码
private void Button1_Click(object sender, System.EventArgs e)
{
Session["name"] =xx.Text;
}
b.aspx中C#代码
private void Page_Load(object sender, EventArgs e)
{

string name = Session["name"].ToString();
}

4. 使用Cookie对象变量

这个也是大家常使用的方法,与Session一样,其是什对每一个用户而言的,但是有个本质的区别,即Cookie是存放在客户端的,而session是存放在服务器端的。而且Cookie的使用要配合ASP.NET内置对象Request来使用。
a.aspx 的C#代码

private void Button1_Click(object sender, System.EventArgs e)
{
HttpCookie cookie_name = new HttpCookie(”name”);
cookie_name.Value = Label1.Text;
Reponse.AppendCookie(cookie_name);
Server.Transfer(”b.aspx”);
}
b.aspx 中C#代码
private void Page_Load(object sender, EventArgs e)
{
string name = Request.Cookie["name"].Value.ToString();
}

5. 使用Server.Transfer方法
这个才可以说是面象对象开发所使用的方法,其使用Server.Transfer方法把流程从当前页面引导到另一个页面中,新的页面使用前一个页面的应答流,所以这个方法是完全面象对象的,简洁有效。
a.aspx的C#代码
public string Name
{
get{ return Label1.Text;}
}
private void Button1_Click(object sender, System.EventArgs e)
{
Server.Transfer(”b.aspx”);
}
b.aspx 中C#代码
private void Page_Load(object sender, EventArgs e)
{
a newWeb; //实例a窗体
newWeb = (source)Context.Handler;
string name;
name = newWeb.Name;
}

6.Cross-Page Posting
定位至于源网页位于相同的应用程序的网页,可以读取源网页的值和公共属性,但是不要按浏览器中的重新整理和上一步,这样会照成无法预期的效果
aspx
<div>
名字: <asp:TextBox ID=”name” runat=”server”></asp:TextBox>
<asp:Button ID=”btn_name” runat=”server” PostBackUrl=”~/Cross-Page Posting/CrossPageTarget.aspx” Text=”传送” OnClick=”btn_name_Click” />
</div>
cs:
TextBox name = (TextBox)PreviousPage.FindControl(”name”);//非public 成员
Label1.Text = “欢迎你:” + name.Text;
previouspage本身属于page类,并且只有在来源网页和目标网页属于同一个 asp.net应用程序下的时候才能引用,如果网页不是跨网页公布的目标,或者是网页在不同的应用程序之中,就不会初始化previouspage属性,就不能使用previouspage来存取任何信息
获得网页的公共属性:
source.cs:
public string UserName
{
get{return aa.text;}
}
cross-page posting.aspx:
<%@ PreviousPageType VirtualPath=”~/Cross-Page Posting/Source.aspx”%>
cross-page posting.cs:
XX.text = previousopage.username;
//一个网页中只能指示一个 previousPage,不然出出现编译错误




关于c#.net页面传值方法的分析

在.net开发中,有这么几种网页的传值:querystring,session,cookie,application,Cross-Page Posting ,Server.Transfer

1. 使用QueryString变量
QueryString是一种非常简单的传值方式,他可以将传送的值显示在浏览器的地址栏中。如果是传递一个或多个安全性要求不高或是结构简单的数值时,可以使用这个方法。但是对于传递数组或对象的话,就不能用这个方法了。下面是一个例子:

a.aspx的C#代码
private void Button1_Click(object sender, System.EventArgs e)
{
string s_url;
s_url = “b.aspx?name=” + Label1.Text;
Response.Redirect(s_url);
}
b.aspx中C#代码
private void Page_Load(object sender, EventArgs e)
{
Label2.Text = Request.QueryString["name"];
}

2. 使用Application 对象变量
Application对象的作用范围是整个全局,也就是说对所有用户都有效。其常用的方法用Lock和UnLock。
a.aspx的C#代码
private void Button1_Click(object sender, System.EventArgs e)
{
Application["name"] = xx.Text;
Server.Transfer(”b.aspx”);
}
b.aspx中C#代码
private void Page_Load(object sender, EventArgs e)
{
string name;
Application.Lock();
name = Application["name"].ToString();
Application.UnLock();
}

3. 使用Session变量
想必这个肯定是大家使用中最常见的用法了,其操作与Application类似,作用于用户个人,所以,过量的存储会导致服务器内存资源的耗尽。一个session只有20K
a.aspx的C#代码
private void Button1_Click(object sender, System.EventArgs e)
{
Session["name"] =xx.Text;
}
b.aspx中C#代码
private void Page_Load(object sender, EventArgs e)
{

string name = Session["name"].ToString();
}

4. 使用Cookie对象变量

这个也是大家常使用的方法,与Session一样,其是什对每一个用户而言的,但是有个本质的区别,即Cookie是存放在客户端的,而session是存放在服务器端的。而且Cookie的使用要配合ASP.NET内置对象Request来使用。
a.aspx的C#代码

private void Button1_Click(object sender, System.EventArgs e)
{
HttpCookie cookie_name = new HttpCookie(”name”);
cookie_name.Value = Label1.Text;
Reponse.AppendCookie(cookie_name);
Server.Transfer(”b.aspx”);
}
b.aspx中C#代码
private void Page_Load(object sender, EventArgs e)
{
string name = Request.Cookie["name"].Value.ToString();
}

5. 使用Server.Transfer方法
这个才可以说是面象对象开发所使用的方法,其使用Server.Transfer方法把流程从当前页面引导到另一个页面中,新的页面使用前一个页面的应答流,所以这个方法是完全面象对象的,简洁有效。
a.aspx的C#代码
public string Name
{
get{ return Label1.Text;}
}
private void Button1_Click(object sender, System.EventArgs e)
{
Server.Transfer(”b.aspx”);
}
b.aspx中C#代码
private void Page_Load(object sender, EventArgs e)
{
a newWeb; //实例a窗体
newWeb = (source)Context.Handler;
string name;
name = newWeb.Name;
}

6.Cross-Page Posting
定位至于源网页位于相同的应用程序的网页,可以读取源网页的值和公共属性,但是不要按浏览器中的重新整理和上一步,这样会照成无法预期的效果
aspx
<div>
名字: <asp:TextBox ID=”name” runat=”server”></asp:TextBox>
<asp:Button ID=”btn_name” runat=”server” PostBackUrl=”~/Cross-Page Posting/CrossPageTarget.aspx” Text=”传送” OnClick=”btn_name_Click” />
</div>
cs:
TextBox name = (TextBox)PreviousPage.FindControl(”name”);//非public 成员
Label1.Text = “欢迎你:” + name.Text;
previouspage本身属于page类,并且只有在来源网页和目标网页属于同一个asp.net应用程序下的时候才能引用,如果网页不是跨网页公布的目标,或者是网页在不同的应用程序之中,就不会初始化previouspage属性,就不能使用previouspage来存取任何信息
获得网页的公共属性:
source.cs:
public string UserName
{
get{return aa.text;}
}
cross-page posting.aspx:
<%@ PreviousPageType VirtualPath=”~/Cross-Page Posting/Source.aspx”%>
cross-page posting.cs:
XX.text = previousopage.username;
//一个网页中只能指示一个previousPage,不然出出现编译错误



2010年5月22日

为什么串口程序在关闭串口时候会死锁

    第一篇文章我相信很多人不看都能做的出来,但是,用过微软SerialPort类的人,都遇到过这个尴尬,关闭串口的时候会让软件死锁。天哪,我可不是武断,算了。不要太绝对了。99.9%的人吧,都遇到过这个问题。我想只有一半的人真的解决了。另外一半的人就睁只眼闭只眼阿弥佗佛希望不要在客户那里出现这问题了。

    你看到我的文章,就放心吧,这问题有救了。我们先回顾一下上一篇中的代码

void comm_DataReceived(object sender, SerialDataReceivedEventArgs e)     
  1. {     
  2.     //先记录下来,避免某种原因,人为的原因,操作几次之间时间长,缓存不一致  
  3.     int n = comm.BytesToRead;  
  4.     //声明一个临时数组存储当前来的串口数据  
  5.     byte[] buf = new byte[n];     
  6.     //增加接收计数  
  7.     received_count += n;   
  8.     //读取缓冲数据    
  9.     comm.Read(buf, 0, n);     
  10.     //清除字符串构造器的内容  
  11.     builder.Clear();     
  12.     //因为要访问ui资源,所以需要使用invoke方式同步ui。     
  13.     this.Invoke((EventHandler)(delegate{...界面更新,略}));     
  14. }     
  15.     
  16. private void buttonOpenClose_Click(object sender, EventArgs e)     
  17. {     
  18.     //根据当前串口对象,来判断操作     
  19.     if (comm.IsOpen)     
  20.     {     
  21.         //打开时点击,则关闭串口     
  22.         comm.Close();//这里就是可能导致软件死掉的地方  
  23.     }     
  24.     else    
  25.     {...}    
  26. }  

    为什么会死锁呢,并发冲突。

    我们要了解一下SerialPort的实现和串口通讯机制,在你打开串口的时候,SerialPort会创建一个监听线程ListenThread,在这个线程中,等待注册的串口中断,当收到中断后,会调用DataReceived事件。调用完成后,继续进入循环等待,直到串口被关闭退出线程。

    我们的UI主线程如何做的呢,首先创建一个窗体,然后执行了Application.Run(窗体实例)。是这样把,这里的Application.Run就是创建了一个消息循环,循环的处理相关的消息。

    这里我们就有了2个线程,UI主线程、串口监听线程。那么你在DataReceived处理数据的时候,就需要线程同步,避免并发冲突,什么是并发冲突?并发冲突就是2个或多个并行(至少看上去像)的线程运行的时候,多个线程共同的操作某一线程的资源,在时序上同时或没有按我们的预计顺序操作,这样就可能导致数据混乱无序或是彼此等待完成死锁软件。

    而串口程序大多是后者。为什么呢,看看我们的例子中DataReceived做了什么?首先读取数据,然后就是调用this.Invoke方法更新UI了。这里Invoke的时候,监听线程将等待UI线程的标志,等到后,开始操作UI的资源,当操作完成之前,监听线程也就停在DataReceived方法的调用这里,如果这个时候。并发了关闭串口的操作会如何呢?SerialPort的Close方法,会首先尝试等待和监听线程一样的一个互斥体、临界区、或是事件(不确定.net用的哪种)。那这个同步对象什么时候释放呢?每次循环结束就释放,哦。循环为什么不结束呢?因为这一次的循环操作执行到DataReceived之后,执行了Invoke去更新界面了,那Invoke怎么又没有执行完成呢?看上去很简单的几行代码。虽然我没仔细研读过.net的Invoke原理,但我猜测是通过消息的方式来同步的,这也是为什么这么多的类,只有控件(窗体也是控件的一种,.net在概念上,颠覆了微软自己的概念,传统的win32编程,是说所有的控件都是个window,只是父窗体不同,表现形式不同,但都是基于系统消息队列的,.net出于更高的抽象,正好反过来了。呵呵)才有Invoke方法了。(委托自己的Invoke和这个不同)

    我猜测控件/窗体的Invoke是SendMessage方式实现的,那么发送消息后就会等待消息循环来处理消息了。如果你直接去关闭串口了。你点击按钮本身也会被转换成消息WM_CLICK,消息循环在处理按钮的WM_CLICK时候,调用你按钮的OnClick方法,进而触发调用你的ButtonClose_Click事件,这都是同步调用的,你的主线程,处理消息的过程,停在了这个Click事件,而你的Click事件又去调用了SerialPort的Close方法,Close方法又因为和串口监听线程的同步信号量关联在一起需要等待一次的while结束,而这个while循环中调用了DataReceived方法,这个方法中调用了Invoke,也就是发送了消息到消息队列等待结果,但消息循环正在处理你的关闭按钮事件等待退出。

    实在太复杂了,这个情况下,你想要真的关闭串口成功,就需要while中的DataReceived方法调用结束释放同步信号,就需要执行完Invoke,就需要执行消息循环,幸运的是,我们真的有办法执行消息循环来打破僵局。Application.DoEvents()。还好,不幸中的万幸。可是问题又来了,你能让Invoke结束,但你无法确定是否在你调用消息循环后,你的某一时刻不会再次并发,可能由于单cpu的串行操作模拟并行中,又把时间片先分给了优先级高的串口监听线程呢?是有可能的。所以,我们就需要一点方法来避免再次invoke窗体。优化后不会司机的例子如下,我们修改DataReceived方法,关闭方法,并定义2个标记Listening和Closing。

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.ComponentModel;  
  4. using System.Data;  
  5. using System.Drawing;  
  6. using System.Linq;  
  7. using System.Text;  
  8. using System.Windows.Forms;  
  9. using System.IO.Ports;  
  10. using System.Text.RegularExpressions;  
  11.   
  12. namespace SerialportSample  
  13. {  
  14.     public partial class SerialportSampleForm : Form  
  15.     {  
  16.         private SerialPort comm = new SerialPort();  
  17.         private StringBuilder builder = new StringBuilder();//避免在事件处理方法中反复的创建,定义到外面。  
  18.         private long received_count = 0;//接收计数  
  19.         private long send_count = 0;//发送计数  
  20.         private bool Listening = false;//是否没有执行完invoke相关操作  
  21.         private bool Closing = false;//是否正在关闭串口,执行Application.DoEvents,并阻止再次invoke  
  22.   
  23.         public SerialportSampleForm()  
  24.         {  
  25.             InitializeComponent();  
  26.         }  
  27.   
  28.         //窗体初始化  
  29.         private void Form1_Load(object sender, EventArgs e)  
  30.         {  
  31.             //初始化下拉串口名称列表框  
  32.             string[] ports = SerialPort.GetPortNames();  
  33.             Array.Sort(ports);  
  34.             comboPortName.Items.AddRange(ports);  
  35.             comboPortName.SelectedIndex = comboPortName.Items.Count > 0 ? 0 : -1;  
  36.             comboBaudrate.SelectedIndex = comboBaudrate.Items.IndexOf("9600");  
  37.   
  38.             //初始化SerialPort对象  
  39.             comm.NewLine = "\r\n";  
  40.             comm.RtsEnable = true;//根据实际情况吧。  
  41.   
  42.             //添加事件注册  
  43.             comm.DataReceived += comm_DataReceived;  
  44.         }  
  45.   
  46.         void comm_DataReceived(object sender, SerialDataReceivedEventArgs e)  
  47.         {  
  48.             if (Closing) return;//如果正在关闭,忽略操作,直接返回,尽快的完成串口监听线程的一次循环  
  49.             try  
  50.             {  
  51.                 Listening = true;//设置标记,说明我已经开始处理数据,一会儿要使用系统UI的。  
  52.                 int n = comm.BytesToRead;//先记录下来,避免某种原因,人为的原因,操作几次之间时间长,缓存不一致  
  53.                 byte[] buf = new byte[n];//声明一个临时数组存储当前来的串口数据  
  54.                 received_count += n;//增加接收计数  
  55.                 comm.Read(buf, 0, n);//读取缓冲数据  
  56.                 builder.Clear();//清除字符串构造器的内容  
  57.                 //因为要访问ui资源,所以需要使用invoke方式同步ui。  
  58.                 this.Invoke((EventHandler)(delegate  
  59.                 {  
  60.                     //判断是否是显示为16禁止  
  61.                     if (checkBoxHexView.Checked)  
  62.                     {  
  63.                         //依次的拼接出16进制字符串  
  64.                         foreach (byte b in buf)  
  65.                         {  
  66.                             builder.Append(b.ToString("X2") + " ");  
  67.                         }  
  68.                     }  
  69.                     else  
  70.                     {  
  71.                         //直接按ASCII规则转换成字符串  
  72.                         builder.Append(Encoding.ASCII.GetString(buf));  
  73.                     }  
  74.                     //追加的形式添加到文本框末端,并滚动到最后。  
  75.                     this.txGet.AppendText(builder.ToString());  
  76.                     //修改接收计数  
  77.                     labelGetCount.Text = "Get:" + received_count.ToString();  
  78.                 }));  
  79.             }  
  80.             finally  
  81.             {  
  82.                 Listening = false;//我用完了,ui可以关闭串口了。  
  83.             }  
  84.         }  
  85.   
  86.         private void buttonOpenClose_Click(object sender, EventArgs e)  
  87.         {  
  88.             //根据当前串口对象,来判断操作  
  89.             if (comm.IsOpen)  
  90.             {  
  91.                 Closing = true;  
  92.                 while (Listening) Application.DoEvents();  
  93.                 //打开时点击,则关闭串口  
  94.                 comm.Close();  
  95.             }  
  96.             else  
  97.             {  
  98.                 //关闭时点击,则设置好端口,波特率后打开  
  99.                 comm.PortName = comboPortName.Text;  
  100.                 comm.BaudRate = int.Parse(comboBaudrate.Text);  
  101.                 try  
  102.                 {  
  103.                     comm.Open();  
  104.                 }  
  105.                 catch(Exception ex)  
  106.                 {  
  107.                     //捕获到异常信息,创建一个新的comm对象,之前的不能用了。  
  108.                     comm = new SerialPort();  
  109.                     //现实异常信息给客户。  
  110.                     MessageBox.Show(ex.Message);  
  111.                 }  
  112.             }  
  113.             //设置按钮的状态  
  114.             buttonOpenClose.Text = comm.IsOpen ? "Close" : "Open";  
  115.             buttonSend.Enabled = comm.IsOpen;  
  116.         }  
  117.   
  118.         //动态的修改获取文本框是否支持自动换行。  
  119.         private void checkBoxNewlineGet_CheckedChanged(object sender, EventArgs e)  
  120.         {  
  121.             txGet.WordWrap = checkBoxNewlineGet.Checked;  
  122.         }  
  123.   
  124.         private void buttonSend_Click(object sender, EventArgs e)  
  125.         {  
  126.             //定义一个变量,记录发送了几个字节  
  127.             int n = 0;  
  128.             //16进制发送  
  129.             if (checkBoxHexSend.Checked)  
  130.             {  
  131.                 //我们不管规则了。如果写错了一些,我们允许的,只用正则得到有效的十六进制数  
  132.                 MatchCollection mc = Regex.Matches(txSend.Text, @"(?i)[\da-f]{2}");  
  133.                 List<byte> buf = new List<byte>();//填充到这个临时列表中  
  134.                 //依次添加到列表中  
  135.                 foreach (Match m in mc)  
  136.                 {  
  137.                     buf.Add(byte.Parse(m.Value));  
  138.                 }  
  139.                 //转换列表为数组后发送  
  140.                 comm.Write(buf.ToArray(), 0, buf.Count);  
  141.                 //记录发送的字节数  
  142.                 n = buf.Count;  
  143.             }  
  144.             else//ascii编码直接发送  
  145.             {  
  146.                 //包含换行符  
  147.                 if (checkBoxNewlineSend.Checked)  
  148.                 {  
  149.                     comm.WriteLine(txSend.Text);  
  150.                     n = txSend.Text.Length + 2;  
  151.                 }  
  152.                 else//不包含换行符  
  153.                 {  
  154.                     comm.Write(txSend.Text);  
  155.                     n = txSend.Text.Length;  
  156.                 }  
  157.             }  
  158.             send_count += n;//累加发送字节数  
  159.             labelSendCount.Text = "Send:" + send_count.ToString();//更新界面  
  160.         }  
  161.   
  162.         private void buttonReset_Click(object sender, EventArgs e)  
  163.         {  
  164.             //复位接受和发送的字节数计数器并更新界面。  
  165.             send_count = received_count = 0;  
  166.             labelGetCount.Text = "Get:0";  
  167.             labelSendCount.Text = "Send:0";  
  168.         }  
  169.     }  
  170. }  

至此,不会再出现关闭死锁问题了。

希望这篇文章能解你的燃眉之急,非常高兴能与读者分享我层遇到,大多数人都遇到的这个问题。如果说的不明白,欢迎讨论。

后续的有关通讯程序底层设计的文章会讲述一个具有丰富扩展性,但有设计简介的万能通讯库,支持网络、蓝牙、串口通讯、并口通讯。但不要指望我都实现出来了,我只是设计出这个框架。



2010年5月21日

看微软.NET各子技术领域之应用前景

从2002年发布.NET 1.0,历经8年发展,.NET发展到了4.0,已经成为一个庞大而复杂的软件开发与运行平台,其架构日益复杂,其应用领域也在不断地扩展,包容了“一堆”的子技术领域。在.NET 4.0即将发布之际,回顾一下已发布的各项.NET技术,看看哪些技术用得很火,哪些被打入冷宫,再猜猜.NET 4.0中可能会有哪些技术会得到“青睐”,是件有意思的事。
1 桌面应用程序开发技术( Windows Form和WPF)。

  在.NET桌面应用程序开发领域,Windows Form是“前辈”,相比以前的老祖宗MFC,其开发效率高得多,即使比一向以“高效率”著称的VB、Delphi之类,也不逊色,因此在很长的一段时间内,Windows Form成为.NET 桌面领域的主流技术,而且有一大批各式各样的第3方控件,其功能可谓应有尽有,使用方便。

    Windows Form的问题是“千人一面”,要想做出“与众不同”的界面,真得费不少力气。

    .NET 3.0中出现的WPF,在界面设计和用户体验上比Windows Form要强得多,比如其强大的数据绑定、动画、依赖属性和路由事件机制,都非常棒。然而,WPF最头痛的是性能,另外,在需要快速开发原型的场景,WPF暂时还比不上Windows Form方便。

  .NET 4.0中,WPF在性能上有较大的改进,这点在使用WPF开发的Visual Studio 2010上体现极为明显,Visual Studio 2010 CTP和BETA1只能用“惨不忍睹”一词来形容,BETA2就有一个性能上的飞越,但还是不是地玩点“崩溃”、“挂死”的把戏,而当前的RC版本,我觉得其使用体验已经超越了VS 2008。

  我认为,WPF取代Windows Form是必然的。
2 数据存取技术

  (1)ADO.NET。这不用多说了,在实际开发中用得太多了,事实证明了它的成功。

(2)LINQ。

这也是个很大的领域,里面最牛的是LINQ to Object,我一用就喜欢上了。

LINQ to XML也很好,它把程序员从代码中解放出来,可以完成大部分XML存取功能,让大家很高兴有机会能和原先.NET所提供的“一堆”XML相关类说声“不见”。

LINQ to DataSet。作为一个ADO.NET技术的补充,这是一个无足轻重的小卒子,在开发中可以用,也可以直接忽略。

LINQ to SQL和ADO.NET实体框架。这两个技术功能重叠,基本上让人怀疑其中有一个是不是“没有存在的必要”,所以曾有“LINQ to SQL已死”的传言。当然,后来微软公司表态说仍然会继续开发LINQ to SQL的后续版本,争论平息。

但我个人觉得,在实际开发中还是使用ADO.NET实体框架更合适。LINQ to SQL有的功能它都有,而且用起来更为灵活,难得的是它的使用并不比LINQ to SQL复杂多少。

ADO.NET实体框架还延伸到了其它的技术领域,是一项重要的基础数据存取技术。

因此, ADO.NET实体框架 vs LINQ to SQL,前者胜出。

         (3)WCF Data Service。

         这是一项非常值得关注的技术,原先叫ADO.NET Data Service,它体现了“数据是一种服务”的思想,让数据可以通过HTTP请求直接获取,它设计了一套URI模式,可以完成投影、选择、分页等功能,用起来方便灵活。

         我觉得在SOA大行于世的分布式系统时代,WCF Data Service应该会得到应用。

         但这一技术问题在于性能。由于数据需要走互联网,所以如果网速很慢的话,基于此技术搭建的应用程序其用户体验将“惨不忍睹”。而且,互联网服务安全问题非常关键,保证基于WCF Data Service技术搭建的应用程序数据安全,想必将成为开发者最费脑筋的地方。

         (4)WCF RIA Service。

         这个技术与Silverlight密不可分。我还没有系统地了解这一技术领域,不予评说。
3 Web开发技术

         这一领域,没说的,ASP.NET中的Web Form是当之无愧的主流。经过多年的发展,Web Form已高度成熟。VS 2008中加入的AJAX系列组件,如ScriptManger、UpdataPanel之类,再配合一堆的应用了AJAX技术的控件,让Web Form更是如虎添翼。基于这种成熟技术开发Web网站,不管是用户还是开发企业,都比较放心。

         从.NET 3.5 SP1开始,Web领域多了些新东西。

         (1)ASP.NET MVC。MVC这一设计模式已有多年的历史,也有很多的成熟的框架,但在.NET“官方”平台上,却是个新加入的“成员”,并不算成熟,我觉得其应用前景要看看再说。我不知道业界是否已有基于此技术开发的实际项目,有这方面项目经验的朋友,不妨谈谈自己的看法。

         (2)ASP.NET Dynamic Data。这是一个看上去很酷的技术。当使用它来创建网站时,Visual Studio 2010会帮你创建一个DynamicData文件夹,里面放了数十个模板文件,构建了一个网站的“脚手架”,几乎不用编码,就可以生成一个全功能的“CRUD”数据驱动网站。

         它的设计思想很好:底层使用ADO.NET实体框架或LINQ  to SQL构造数据模型,通过提取数据模型中的元数据,动态选择合适的模板生成网页。这就避免了真实项目中不得不为每个数据存取任务设计不同网页的负担,而且这一技术提供了很多的方式去允许你定制网站。

         我当初刚一接触时,也很兴奋,这是个好东西啊!但后来我改变了看法,这一技术的问题在于它过于“自动化”了,而且需要包容数十个文件,让其与现有的ASP.NET网站集成相当不便,配置起来麻烦。

         我个人认为,在现有.NET Web开发技术应用现状之下,任何一个与现有的ASP.NET网站(以Web Form+AJAX为主体技术)集成麻烦的技术,都很难有“美好”的前途。很不幸,ASP.NET Dynamic Data是这样的例子,ASP.NET MVC也有同样的问题,但没有ASP.NET Dynamic Data严重,而且ASP.NET MVC架构清晰,还是比较易于维护。

         (3)Silverlight。这实际上是另一种Web应用架构的代表技术,其立足点在于充分利用客户端的计算资源,可以大大地降低对服务端的依赖,而且易于构造良好的用户体验,我个人认为其发展大有可观。是一个需要重点关注的技术。
4 插件技术

         .NET 4.0引入了一个“Managed Extensibility Framework(MEF)”,我在此郑重推荐!

         MEF通过简单地给代码附加“[Import]”和“[Export]”标记,我们就可以清晰地表明组件之间的“服务消费”与“服务提供”关系,MEF在底层使用反射动态地完成组件识别、装配工作。从而使得开发基于插件架构的应用系统变得简单。够酷的技术!

         另外,请忘记.NET 3.5所引入的“MAF(Managed Add-in Framework )”吧,MAF引入了一个复杂的宿主与插件间的通讯管道架构,仅仅是创建一个最简单的SayHello宿主和插件,你也必须创建多达8个项目!

         最要命的是MAF设计者“想”得过多,设计了复杂的接口和类继承体系,而且选择让插件运行于与宿主不同的应用程序域中,这就使得插件与宿主之间的通讯变得复杂。个人认为,这些实在不是一个好的设计决策。

         我估计,MAF会“无疾而终”。
5 WCF和.NET Remoting

         其实这是一个不需要讨论的问题,有了WCF,我还要Remoting干什么?   因为前者包容后者的所有功能,而且还提供了更多。

         WCF的问题是微软企图用一个框架解决所有的问题,因此其架构非常复杂,任何一名想探究其底层运行机理的人,都必须要有足够的心理准备和耐心。

         我们可以看到WCF向其它领域的渗透,比如前面的WCF Data Service,还有Workflow Service(将工作流发布为WCF服务),看来微软是将“宝”押在WCF上了,凡是带有“服务”字样的,微软都有把它改造为WCF服务的冲动。

         因此,WCF是不得不学习和掌握的技术。
6 多线程与并行计算

         关于并行计算,我已经写过不少文章了,废话少说,在多核时代,我认为.NET并行计算中的任务并行库和并行LINQ,会得到较多的应用。
7 工作流

         这个技术,我看是微软自己把事弄砸了。工作流从.NET 3.0开始引入,到.NET 3.5已经比较完善了,也有了一些实际的应用。但.NET 4.0就来了个另起炉灶,WF4与WF3.5相比,简直是另一个产品,而且WF4的BETA1和BETA2相比,居然在对象模型上也有大的改动,RC版本中的WF4我还没看,不知又有什么变动,应该不会再变了吧?!

         对于这样一个“变色龙”,谁用谁胆大。
8 函数式编程语言F#

         函数式编程很有趣,VS 2010中F#成为.NET正式成员。F#中的许多特性,比如不可更改(immutable)的数据结构,声明性编程风格,强大的类型推断,所有东西都是表达式等,都让习惯了面向对象风格的程序员感到新奇。

         我个人觉得,F#如果用于开发多线程并行计算程序,会有较高的开发效率,而函数式编程的特点,也会使它在科学计算中有较好的表现。但用于开发CRUD之类的MIS系统,至少目前还是免谈吧。
9 云计算

         Visual Studio 2010集成了云计算开发的项目模板。

         云计算是一个说不完的话题。微软在这方面投入巨大。它精心打造了Azure这个云计算平台。了解Azure的最佳方法是看“DAVID CHAPPELL”的文章《INTRODUCING WINDOWS AZURE》,这篇文章可以在微软网站上找到。

         虽然我个人认可云计算是一个大的发展方向,但对于中国,这个技术是一道远方的亮丽风景,仅供观赏。因为国内还没有一个成熟的云计算平台,而微软的Azure目前又没有开放中国大陆的云计算购买服务,加上中国又有特殊的国情,所以一切都只是空中楼阁。

         云计算真正应用于国内,诸位请继续等待吧。

2010年5月19日

报表转pdf,直接在网页上显示

Rendering the output of a ReportViewer control (SQL Reporting Services) directly to PDF is fairly easy. It involves simply rendering the Report Viewer output to a byte array, and then pushing it to a MemoryStream object and writing to the Output Stream. Code below:

Dim warnings As Warning() = Nothing

Dim streamids As String() = Nothing

Dim mimeType As String = Nothing

Dim encoding As String = Nothing

Dim extension As String = Nothing

Dim bytes As Byte()

bytes = ReportViewer1.ServerReport.Render("PDF", Nothing, mimeType, encoding, extension, streamids, warnings)

Dim
ms As New System.IO.MemoryStream(bytes)

Response.ContentType = "Application/pdf" 

Response.BinaryWrite(ms.ToArray())

Response.End()



2010年5月17日

SQL 2000 与 2005比较

SQL 2000 与 2005比较

SQL Server 2005 相对它的前版本SQL Server 2000所做的重大改进或新增功能。 
      升级理由一:数据分区
只有到了2005 版本SQL Server才拥有了真正的表和索引数据分区技术。这个技术一下子使SQL Server数据库从“青壮年”成长为成熟的企业级数据库产品,是一个里程碑性质的标志。数据分区技术极大加强了表的可伸缩性和可管理性SQL Server2005 功能,使得SQLServer 处理海量数据的能力有了质的飞跃,是我认为最值得升级的一个理由。

      升级理由二:可编程性
      CLR 集成
       SQL Server 2005的可编程性是值得升级的第二个重要理由。从来没有哪一个版本能像SQL Server 2005 这样带来这么多编程方面的变革。说老实话,在我知道的瞬间我是惊呆了。有些变化是革命性的。如CLR(Common Language Runtime,公共语言运行时)集成。你可以轻松利用.NET语言的优势如其面向对象的封装、继承和多态特性,编写出那些需要对数据进行复杂数值计算或逻辑的代码,如字符串处理,数据加密算法,XML数据操作等等。你现在需要的仅仅是考虑什么时候使用T-SQL 语言,什么时候使用CLR。我猜测那些SQL Server软件开发商几乎会立即升级到SQLServer 2005 享受数据库编程的便捷。
       T-SQL 语言增强
SQL Server 2005 中的T-SQL语言有了非常大的改进。其中笔者最为称道的是现在可以使用和C++或C#类似的TRYCATCH结构对T-SQL 进行错误处理了,大大简化了T-SQL错误处理编程。估计很多T-SQL语言使用者可能就为了这个TRY-CATCH 结构而迫不及待地升级到SQL Server 2005。

      升级理由三:安全
SQL Server 2005 的安全功能是我认为值得升级的第三个理由。SQL Server 2005 的安全达到了前所未有的强大水平,有着比以前版本更清晰的安全模型即主体,安全对象和权限。
如果你需要保护数据库中的敏感数据,那么SQL Server2005 中的数据加密功能绝对值得考虑。以前不止一次有客户问我如何加密数据库中的某些数据,是否可以使用一些内部不公开的函数如PWDENCRYPT加密数据。我的回答是使用Windows的EFS(加密文件系统)功能加密数据库文件或在应用程序层对数据加密后再存储。现在用户期盼已久的数据加密功能终于在SQL Server 2005 中得到实现,那些有机密数据需要保护的用户值得高兴了。SQL Server 2005不是简单的提供一些加密函数,而是把市场上已经成熟的数据安全技术引进到数据库中,有一个清晰的加密层次结构。SQL Server 2005 支持证书(certificate),非对称密钥和对称密钥算法,一是防止敏感数据被泄漏,二是防止数据被篡改。对称密钥支持RC4,RC2,TripleDES 和AES算法,而非对称密钥使用RSA 算法。证书其实就是非对称密钥中公钥的容器。密钥管理是安全中比较弱的部分。SQL Server 2005 每一层都使用证书、非对称密钥和对称密钥的组合对它下面的一层进行加密,提高了密钥安全性。出于性能考虑,一般不用加密强度大的非对称密钥或证书直接加密数据,而是使用对称密钥加密数据获得较快的性能,然后使用证书或非对称密钥加密对称密钥。

      升级理由四:快照隔离
你还在为系统出现的阻塞(blocking)或死锁(deadlock)现象苦恼吗?快试试SQL Server 2005 中的快照隔离吧。通过行版本(row versioning)控制技术,SQL Server 2005 除了原来支持的四种事务隔离级别(脏读、提交读、可重复读、可串行读)外新增了一个快照(SNAPSHOT)隔离级别,有可能使阻塞或死锁成为历史。SQL Server在TEMPDB中存放不同版本的数据行,select 语句读取这些不同版本的行,读操作不阻塞写数据,写操作也不阻塞读操作,这样那些由于读/ 写争用导致的大量死锁的系统将从中获得无穷益处。如果你的系统复杂难优化,那么升级到SQL Server 2005 试试快照隔离级别,也许会有意想不到的效果。
SQL Server 2005中的快照隔离可细分为两种即READ_COMMITTED_SNAPSHOT和ALLOW_SNAPSHOT_ISOLATION。建议大家多使用前者,因为已提交读隔离可用于大多数现有应用程序,而不需要进行任何更改,其占用的TEMPDB空间也少。可以预见如果使用快照隔离级别,那么需要特别关注TEMPDB的大小和性能。你也许需要把TEMPDB放在有足够空间的单独磁盘上以提高性能。
考虑到快照隔离在避免阻塞和死锁方面的作用,我把它作为升级的第四个理由。

      升级理由五:数据库镜像
对于那些要求高可用性的用户来说,数据库镜像也许是考虑升级的唯一理由。SQL Server 2005的前版本在高可用性方面提供了故障转移群集(Failover Cluster)和Log shipping方案。群集方案的一个好处是在一台机器发生问题时它可以提供极快的故障转移能力,在备份服务器上联机数据库,应用程序只需重新连接即可。群集方案的一个缺点是数据库放在共享盘上,有单点失效这个缺点,一旦共享盘失败将导致整个系统崩溃。所以群集方案一般都要结合严紧的备份方案一起使用。而logshipping系统有一个时间上的延迟,且如果日志备份很大,传送速度也是个问题。SQL Server 2005引入的数据库镜像可作为故障转移群集或Log shipping 的替代或补充方案来提高数据库的高可用性。镜像的主要优点是它比前两者更容易管理,没有群集的单点失效缺点,也没有log shipping 的时间延迟。镜像服务器可以放在很远的地方,提高了作为备份服务器的高可用性。
  
       升级理由六:商务智能BI 增强
SQL Server 2005 对已经有或打算开发基于SQL Server 的商务智能方案的用户吸引力极大。SQL Server 2005中有关商务智能方面的增强很多,是升级的很好理由。首先是传统的DTS(Data Transformation Services)被新的IS(Integration Services)代替。SQL Server 2000 中的DTS用来在不同服务器之间转移数据,但对于复杂重复的工作流DTS倍感吃力。IS重新改写了DTS的数据流引擎,引入提取、转换和加载(ETL)数据的新编程体系,将数据流与控制流分开,开发能力大大加强,包部署、管理和性能方面也比DTS上了一个数量级。笔者看来,DTS终于从原来的小打小闹成长为成熟的IS 数据集成服务体系。
    分析服务(Analysis Services)在SQL Server 2005 中也有很多改进。原来没有profiler想跟踪分析服务里面的语句非常痛苦。现在2005 终于支持profiler了。Profiler对性能调优和排查错误将非常有用。分析服务2005 真正具备了实时分析能力,新增加了四种数据挖掘算法,也支持.NET语言进行开发(如存储过程等)。至于报表服务,2005 版本中添加了报表生成器和模型设计器这两个新工具,支持报表拖拉设计。2005 的报表改进如新的打印功能、多值参数等。设计过报表的人员会深深知道多值参数的妙处。
另外SQL Server2005 功能,无论是IS、报表服务等都可以在类似Visual Studio的环境中开发,任务完成不过鼠标拖拉之间,非常容易上手。

      升级理由七:全文搜索增强
相对前版本SQL Server 2005中性能提升最多的部分当数全文检索。SQL Server 2000 中的全文本检索和SQL Server 7.0中的差别不大,处于能用的水平。在SQL Server 2000中使用全文检索一个最大的痛苦是建立全文索引的性能不好,需要的时间太长,特别是在表很大的情况下。一个几千万行数据的表也许需要数个小时到数天时间才能完成全文索引的建立。SQL Server 2005全文检索在开发的时候就集中于三点:性能,集成和可扩展性。据开发小组人员的简单测试,原来在SQL Server 2000中建立全文索引需要14天的表,现在只需要几个小时!几乎有上百倍的性能提升,只能用“惊异”来形容。其相关的全文检索语句也有30%~50%甚至更高的性能提高。性能方面的提高得益于全新设计的全文检索引擎。其中关键的一点设计是全文检索引擎现在使用共享内存和SQL Server 进行数据大规模并发交互,而不是原来基于逐行的方式,使得性能上了好几个数量级。
除了性能,SQL Server 2005 中的全文索引的集成性也大大加强。在SQL Server 2000 中很难对全文检索进行备份。一旦有数据库恢复或移动,你得重新重建索引。对于几百个GB的数据库,重建索引几乎是不能接受的恶梦。现在终于可以和数据库一起备份和恢复全文索引了。你不再需要在恢复数据库后重建全文索引了!恶梦终于成为历史。除了可以备份外,你也可以方便的改变全文索引的磁盘位置。你甚至可以在一个热备机器上把全文索引建立好,然后copy 这个索引到生产服务器上使用。

      升级理由八:可用性功能增强
索引联机操作。除了数据库镜像,SQL Server 2005 中可用性还有很多其他提高。索引现在可以使用ONLINE关键字进行在线建立或重建或删除了。它的技术要点是在内存里面动态生成索引的另一个副本从而不影响原来查询的进行。一旦索引副本完成操作即替代原来索引成为当前索引。我认为索引联机操作的意义是很大的,因为很多数据库系统都有定期调整或维护索引方面的需求。有了2005 你无需担心业务的正常运行而大胆的对索引进行维护或修改。
页校验和。SQL Server 2005中的数据库页引入校验和增强了数据的可靠性。除了原来SQL Server 2000 中已有的TORN_PAGE_DETECTION 外,SQL Server 2005 新增实现了页的检验和(CHECKSUM)。你使用ALTER DATABASE语句的SET PAGE_VERIFY子句即可指定。它的原理是向磁盘中写入8K数据页面时,SQL Server计算整个8K页面内容的校验和并将该值存储在页头中。再次从磁盘中读取页时,SQL Server动态计算读取到的页面内容的校验和SQL Server2005 功能,并与存储在页头中的校验和值进行比较。如果不相等则意味着页面有物理损坏,需要检查IO硬件。另外设置检验和的另一个好处是还可以在备份和还原操作过程中使用RESTORE VERIFYONLY语句验证每一数据页的完整性从而确认备份文件没有物理损坏。
在线还原。在数据库的某一部分未恢复前,用户无法对该部分进行访问,但可以访问所有其他数据。SQL Server 2000中如果数据库在还原或recovery当中,用户不能访问数据库。这样如果数据库很大需要rollback或rollforward的事务很多的话,recovery的时间会出奇的长。SQL Server 2005 的在线还原功能使得数据库在很短的时间内变得可用。

      升级理由九:复制增强
SQL Server 2000 中的复制功能已经很好。我这里把复制作为升级的一个理由因为SQL Server 2005在原来的基础上又增添了不少的功能。如peer-to-peer对等复制,可以在参与者之间相互进行复制,这样你可以采用对等复制在复制参与者之间建立某种程度的负载平衡。合并复制现在支持通过HTTPS进行数据同步,可以方便建立基于INTERNET 的复制。发布表现在可以使用标准的T-SQL语句如Alter Table等进行结构修改然后被复制而不是仅仅局限于使用sp_repladdcolumn和sp_repldropcolumn存储过程。在SQL Server 2000 中,仅支持向其他数据库(如DB2或Oracle)发布数据,而在SQL Server 2005 中,可将Oracle 数据库直接复制到SQL Server。可以从备份中初始化事务性订阅而不是仅仅局限于从快照对复制进行初始化,等等……

      升级理由十:异步处理能力
SQL Server 2005 通过引入全新的Service Broker 提供了革命性的异步处理能力。Service Broker提供了一个功能强大的异步编程模型。它为数据库应用程序增加了可靠、可扩展、分布式异步功能异步编程,允许程序仅仅在资源可用时才去执行占用大量资源的任务,以此来缩短响应时间,提高吞吐量。在我看来,Broker的最大好处一是异步执行能力,提高了可伸缩性,二是可靠执行,三是集成于数据库中,备份数据库就备份了broker 的消息队列。SQL Server 2005 中的查询通知就是基于Service Broker的应用。你可以使用查询通知功能来发送一个命令到SQL Server请求在查询结果发生变化时接收SQL Server的通知。这样就可以只有在程序以前检索的结果发生变化时,才需要重新查询数据库。一个可以预见的应用是在使用缓存的Web 站点中。Web站点首先发送语句到数据库服务器,获得数据,缓存到本地,然后只有在收到查询通知的时候才清理缓存,重新查询数据。这个机制避免了重复轮询SQL Server,大大减轻了服务器的负载,也提高了Web 站点的伸缩性。
因为SQL Server 2005 的Service Broker带来了数据库编程异步处理能力的革命,我把它作为升级的第十个理由。

      总结语
       上面列出的十大理由仅仅是基于个人的看法,并没有囊括SQL Server 2005 所有的功能。SQL Server 2005 还有其他很多非常优秀或重大的改进。比如支持通过HTTP SOAP协议直接访问数据库,增加XML数据类型,支持Xquery,使用新的SQL ServerManagement Studio 等等。有一点我必须提一下,就是现在可以调用sp_create_plan_guide来强制指定SQL Server总是使用某个执行计划运行语句,避免SQL Server动态生成不够优化的查询计划,实在太棒了。在笔者看来,SQL Server 2005 带来的好处远远大于升级导致的工作量,升级到SQL Server 2005 是迟早的事情。早升级早拥有,对SQL Server 2005,你准备好了吗?

 



2010年5月8日

IE无缝嵌入Coreldraw12 web调用coreldraw


WEB项目中,客户需要用到Coreldraw12来设计图片.

并且能需要将图片转换成JPG文件.CDR源文件,来存储,

并且需要统计出CDR文件中的各种字体的大小数量

于是利用VBA来控制 CORELDRAW的想法就在脑子里盘旋,

最大的难点在于WEB方式是要将文件存储到远程服务器上.

通过查询 coreldraw12的VBA资料发现.MICROSOFT对CORELDRAW的支持已经比较强大,

对于统计字数,另存为JPG文件等等都是小菜一叠. 最大的问题在与将文件存到远程服务器上.

因IE安全的设置,不用OCX控件来做事情,实在是无法操作客户端的文件.

最终解决方法是用VC写了个OCX控件,在用户接受了安装之后,来实现载入CORELDRAW的控制界面,进行设计

完了后,通过 VBSCRIPT触发存储功能,获得句柄,传入文件名等参数.一并存储到远程.同时数据库也记录相关信息.

搞定.

   下面就是界面. (PS唯一不足的就是coreldraw的工具条,有部分无法弄出来)