ASP.net难点解析

认识Asp.net 中相对路径与绝对路径

分类: 技术文档2010-01-1217:051490人阅读评论(1)收藏举报

好多人对相对路径与绝对路径老是混淆记不清楚,我从整理了一下,希望对大家的认识有帮助。

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

1.Request.ApplicationPath->当前应用的目录
Jsp中, ApplicationPath指的是当前的application(应用程序)的目录,ASP.NET中也是这个意思。
对应的--例如我的服务器上有两个web应用域名都是mockte.com 一个映射到目录mockte.com/1/ 另一个影射到 http://mockte.com/2/
那么mockte.com/1/就是第一个应用的ApplicationPath 同理 mockte.com/2/就是第二个应用的ApplicationPath

2.Request.FilePath->对应于iis的虚拟目录
如 URL http://mockte.com/1/index.html/pathinfo
FilePath = /1/index.html

3.Request.Path->当前请求的虚拟路径
Path 是 FilePath 和 PathInfo 尾部的串联。例如 URL http://mockte.com/1/index.html/pathinfo
那么Path = /1/index.html/pathinfo

4.Request.MapPath(stringurl)->将url映射为iis上的虚拟目录
这个目录都是相对于application的根目录的
于Server.MapPath相比,不会包含类似c:/这样的路径
可以理解为是相对路径(对比的Server.MapPath就是绝对路径)

5.Server.MapPath(stringurl)->将url映射为服务器上的物理路径
例如 http://mockte.com/1/index.html 假设你的应用程序在c:/iis/MySite中
那么就是 c:/iis/MySite/1/index.html

//本地路径转换成URL相对路径
private string urlconvertor(string imagesurl1)
{
string tmpRootDir =Server.MapPath(System.Web.HttpContext.Current.Request.ApplicationPath.ToString());//获取程序根目录
string imagesurl2 =imagesurl1.Replace(tmpRootDir, ""); //转换成相对路径
imagesurl2 =imagesurl2.Replace(@"/", @"/");
//imagesurl2 =imagesurl2.Replace(@"Aspx_Uc/", @"");
return imagesurl2;
}
//相对路径转换成服务器本地物理路径
private stringurlconvertorlocal(string imagesurl1)
{
string tmpRootDir =Server.MapPath(System.Web.HttpContext.Current.Request.ApplicationPath.ToString());//获取程序根目录
string imagesurl2 =tmpRootDir + imagesurl1.Replace(@"/", @"/"); //转换成绝对路径
return imagesurl2;
}

 

1.使用filePath="/Logs/abc.txt",被认为是根目录,即网页文件所在的盘符,默认的是C盘,则在这里这个路径被解释为"C:/Logs/abc.txt"

2.使用filePath="~/Logs/abc.txt",被认为是服务器的目录

3.使用filePath="./Logs/abc.txt",仍然是服务器目录下

 

 

〈%# 〉与〈%=〉的区别,显示数据与绑定数据

分类: 技术文档2010-01-1118:29368人阅读评论(0)收藏举报

有很长时间没来 CSDN 的博客了。今天在社区里看到一个网友提问关于 asp.net 中“<%# %>” 的问题,所以还是想把这篇文章发到自己的博客上。虽然不是非常很高深的问题,但是还是应该多加留意……


aspx页面中,<%= %> 与 <%# %> 的区别

关于这个问题,在多数的 ASP.NET 的教材中,都提到了一些。<%= %>与<%# %>的区别在于:绑定时机不同,<%# %>是在控件调用DataBind函数的时候才被确定。对于<%= %>,我想你应该不会陌生,在ASP时代,它就相当于 Response.Write。在ASP.NET时代也一样。暂时先不去理会教材中说的,也先把“绑定时机”放到一边。<%= %>与<%# %>的区别是:前者是输出,而后者是赋值!即:

<%="A" %> 相当于:Response.Write("A");

<%#"A" %> 相当于:变量="A";


先来看一个例子:


<html>

<body>

<asp:Button Text=<%# "Hello, the Internet!" %> RunAt="Server" ID="testButton"/>

</body>

</html>



这句话相当于赋值,把上面这个勾黑句子翻译成后台代码就是:testButton.Text="Hello,the Internet!";

第二个例子:

<html>

<body>

<%="Hello, the Internet!" %>

<%#"Hello, the Internet!" %>

</body>

</html>

<%="Hello, the Internet!" %> 就相当于:Response.Write("Hello,the Internet!");

那么第二个<%# %>怎么解释呢?如果按照我“赋值”的说法,它把值赋给谁了呢?

其实翻译成后台代码是这样的:(newSystem.Web.UI.LiteralControl()).Text="Hello, the Internet!";


一段文本,虽然它不被包含在任何开始结束标签之间,但是[font='Courier]ASP.NET[/font]也认为它是一个控件。一个[font='Courier]LiteralControl[/font]控件。所以,[font='Courier]<%# %>[/font]就是把值赋给[font='Courier]LiteralControl[/font]控件的Text属性。

我做过一个生成静态 HTML 代码的类,使用的手法是包装了 HtmlTextWriter这个类。并且我做了跟踪调试。跟踪的结果令我感到很意外。ASP.NET 会把所有的未标有“runat=server”属性的标记,统统看作是一个LiteralControl 控件。例如在上面那些 HTML 代码里,例如在文章开始处的那些 HTML 代码里,ASP.NET会认为是有三个服务器控件,分别是:LiteralControl、Button、LiteralCtontrol。第一个LiteralControl 的 Text 值为“<html><body>Hello,theInternet!”,而最后一个 LiteralControl 的 Text值是“</body></html>”。也就是说,<%= %> 应早于 <%#%>,先被“翻译”出来……

----------------------à>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

 

在.aspx的文件中经常会碰到如下的代码,如:

1、<%=%> 里面放的变量名,如:

<div>
<h1>Hello World</h1>
<p>Welcome to Beginning ASP.NET 3.5 on <%=
DateTime.Now.ToString() %>
</p>
</div>
输出结果为:

HelloWorld

Welcome to BeginningASP.NET 3.5 on 2009-11-10 15:53:08

2、 <%#%> 这里是数据的绑定

:<%# DataBinder.(Container.DataItem,"ClassName") %>

完整代码:<asp:DataList ID="ClassList" runat="server">
<ItemTemplate> <%# DataBinder.(Container.DataItem,"ClassName") %>
</ItemTemplate>
</asp:DataList></td>

3、<%@ %> 表示:引用

如在很多.aspx页面中,都可以看到如下的代码:

<%@ PageLanguage="C#" CodeFile="Default.aspx.cs"Inherits="_Default" %>

4、<%%>中间一般放函数或者方法,典型的asp程序写法。

例如:<trbgcolor="#ffffff">
<td height="20">
<div align="center">类别:</div>
</td>
<td height="9">&nbsp;
<%ST_getList();%>
</td>
</tr>

-----------------------------------------------------------__________________________________________________________________________________________________________________________

MVCModel数据验证

MVC Model数据验证已经实现了前后台统一验证,那为什么我又要重新实现一个呢,因为我懒,不想去写一个个Model,总结不方便的地方如下:

1. 一个form页面要写一个model,不能重用.

2. 验证和提示信息都写在特性上,书写麻烦,修改一个信息也要重新编译,当然你可以读取xml,但还是麻烦,当提示信息写在xml里,修改时要找,检查时不直观,多加一个验证还是要编译

关于Model验证可以参考: Asp.net MVC2学习笔记8-数据验证(前后台统一验证)

我想要的验证

微软的Model 验证需要在html页面写:

@Html.ValidationMessage("UserName")

也就是程序要对 那一个控件进行验证,既然写都写了,为什么不把所有验证信息都写在这呢,由此就产生了这篇文章的想法,我希望在页面这样写:

@VD1.Bind("UserName").NotEmpty("用户名不能为空"). MinLength(5).ToString()

这句的意思是:对UserName验证,不能为空,最小长度5.这样每个页面管好自己就行.优点如下:

1.不用多余的后台代码,有代码提示,点点就搞定.

2.html里的提示和记在xml效果一样,而且检看时直观.

3.很多时候一个重要的项目,你不敢更新整个项目,但更新一个页面,大家都轻松,反正这个我感受比较深.

4.提示信息可以不写,有默认的.

5.自己生成js可控性强,比如你想改成对话框显示信息,或者右下角提示,写几个重载都可以轻松搞定.

围绕着这个想法,就有了此类的实现,部分代码如下:

1. 验证项的接口

public interface IUniValidate

{

#region 验证接口

/// <summary>最后必需调用</summary>

MvcHtmlStringToString();

/// <summary>不能为空</summary>

IUniValidateNotEmpty(string message = "不能为空");

2. 验证项

public class UniValidate: IUniValidate

{

/// <summary>不能为空</summary>

public IUniValidate NotEmpty(stringmessage)

{

if(isNew)

{

AddJs("required","true", message);

ValidE += (HttpRequestBase request) =>

{

if(request[_inputName] == null || request[_inputName].Length == 0)AddErrStr(message);

returnchecked1;

};

}

returnthis;

}

之所以可以实现前后台统一验证,原理就在这,增加前台的js和后台验证所需的匿名方法,然后返回自己

3.验证Helper

public class UniValidateHelper

{

private static Dictionary<string, Dictionary<string, UniValidate>> all_valids = new Dictionary<string, Dictionary<string, UniValidate>>();

private static Dictionary<string, string> all_Js = new Dictionary<string, string>();

public bool CheckAll(Controllercontr)

{

boolres = valids.Count > 0;

foreach(varitem invalids)

{

lock(item.Value)

{

item.Value.checked1 = true;

item.Value.errStr = "";

if(item.Value.ValidE != null) res = item.Value.ValidE(contr.Request)&& res;

ErrStr += item.Value.errStr;

}

}

contr.ViewData["ErrStr"] = ErrStr;//asp.net下没有contr.ViewBag

returnres;

}

缓存验证项,验证相关的一此方法

调用及注意事项

1. html页面js引用,母板里引用即可

<script src="http://www.cnblogs.com/Scripts/jquery-1.4.4.js"type="text/javascript"></script>

<script src="http://www.cnblogs.com/Scripts/jquery.validate.js"type="text/javascript"></script>

<script src="http://www.cnblogs.com/Scripts/jquery.validate.messages_cn.js"type="text/javascript"></script>

<script src="http://www.cnblogs.com/Scripts/jquery.validate.extension.js"type="text/javascript"></script>

2. 新建验证对象

UniValidateHelper VD1 = new UniValidateHelper("Valid/Index", Request["refresh"] != null);

或者

UniValidateHelper VD1 = new UniValidateHelper(ViewContext.RouteData,Request["refresh"] != null);

3. 控件验证如

@VD1.Bind("UserName").NotEmpty("用户名不能为空").MinLength(5).ToString()

4. 服务端错误信息,母板里写一次就可,也可以不显示,因为只有很少的情况会绕过端验证,比如js实效,人为攻击等

@ViewData["ErrStr"]

5. 客户js输出,母板里写一次就可

@VD1.ValidEnd()

6. 服务端验证

UniValidateHelper vd = new UniValidateHelper(this.RouteData);

bool rs= vd.CheckAll(this);

最后

具体看源码,类非常简单,写的时候就以简单实用为原则,生成的js验证用的是jquery.validate插件,个人感觉还是非常强大的,当然你也可以自己修改使用其他的验证库,类还有很多不足,您有什么建议请留在回复里,你的参与也许会使这个类 更加完善及强大,虽然感觉已经很强大了,开个玩笑.

代码的风格可能不合你意,但请不要骂娘,你可以自己改进,至少比重新写省事

比如这样的注释/// <summary>不能为空</summary> 为了减少行数,为了折叠后还可以看的到

项目环境:vs2010,mvc3.0

    [源码下载]

支持的所有验证如下

ViewCode

public interfaceIUniValidate
    {
        /// <summary>最后必需调用</summary>
        MvcHtmlStringToString();
        /// <summary>不能为空</summary>
        IUniValidateNotEmpty(string message = "不能为空");
        /// <summary>是否为Email格式</summary>
        IUniValidateEmailAddress(string message = "必需为Email格式");
        /// <summary>正则验证</summary>
        IUniValidate Regex(stringregex, string message = "不正确");
        /// <summary>中文</summary>
        IUniValidateAllChinese(string message = "必需为中文");
        /// <summary>与另一个控件值是否相等</summary>
        IUniValidateEqualTo(string equalElement, stringmessage = "不相同");
        /// <summary>不能与另一个控件相等</summary>
        IUniValidateNotEqualTo(string equalElement, stringmessage = "不能相同");
        /// <summary>不能小于最小值</summary>
        IUniValidate Min(int num, stringmessage = "数字太小");
        /// <summary>不能大于最大值</summary>
        IUniValidate Max(int num, stringmessage = "数字太大");
        /// <summary>长度不能小于</summary>
        IUniValidateMinLength(int num, string message = "长度太短");
        /// <summary>长度不能大于</summary>
        IUniValidateMaxLength(int num, string message = "长度太长");
        /// <summary>验证长度在指定的区间内</summary>
        IUniValidate Length(int min, int max, stringmessage = "长度不正确");
        /// <summary>身份证号码验证</summary>
        IUniValidateIsIDCardNo(string message = "不是有效的身份证");
        /// <summary>远程验证 使用post方式 根据远程返回的Boolen值进行验证 true:表示可以提交,不显示错误提示false:表示不可以提交,显示错误提示</summary>
        IUniValidate Ajax(string url, stringmessage);
        /// <summary>日期验证</summary>
        IUniValidate Date(stringmessage = "日期不正确");
        /// <summary>网址验证</summary>
        IUniValidate Url(stringmessage = "网址不正确");
        /// <summary>Decimal型数字验证</summary>
        IUniValidateDecimal(string message = "不是有效的数字");
        /// <summary>检测是否为IP地址</summary>
        IUniValidate IsIP(stringmessage = "IP不正确");
        /// <summary>不包含Html代码</summary>
        IUniValidate NoHtml(stringmessage = "不能包含标示符");
        /// <summary>不能包含中文或全角字符验证</summary>
        IUniValidateNoChinese(string message = "不能是中文");
        /// <summary>判断是否是标准字符 如果包含非标准字符 报错</summary>
        IUniValidateIsLegalXmlString(string message = "xml格式错误");
        /// <summary>非 正则表达式,即不匹配此正则</summary>
        IUniValidateNotRegex(string regex, string message);
        /// <summary>Char最大长度</summary>
        IUniValidateCharCodeLength(int num, string message = "长度不正确");

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

ASP.NET前台代码绑定后台变量方法总结

经常会碰到在前台代码中要使用(或绑定)后台代码中变量值的问题。一般有<%= str%>和<%# str %>两种方式,这里简单总结一下。如有错误或异议之处,敬请各位指教。

一方面,这里所讲的前台即通常的.aspx文件,后台指的是与aspx相关联的CodeBehind,文件后缀名为.aspx.cs;另一方面,这里的绑定是指用户发出访问某一页面指令后,服务器端在执行过程中就已经将前台代码进行了赋值,而后生成html格式回传客户端显示,而并非已经显示到客户端后,然后通过其他方法(如ajax)去服务器端获取相应变量。

备注:上面说的两个文件是常见的代码隐藏(code-behind)模式,还有一种是代码嵌入(code-beside, inline)模式,那就是只存在aspx一个文件,而后台代码则写入此文件的<scripttype="text/javascript" runat="server"></script>之中(还有一些语法上区别),这对于本文讨论的问题略有影响,因为代码嵌入是声明性代码与C#/VB.NET代码都一起编译到一个类里面,而代码隐藏则将声明性代码与C#/VB.NET代码分开几次进行翻译/编译,因此前者是局部与局部(partial)的关系后者基类与派生类的关系,但这仅仅影响所能绑定变量的范围(与修饰符有关),下面会提到。以下均以代码隐藏模式为例。

一般来说,在前台代码的三种位置可能会用到(绑定)后台变量:

·        服务器端控件属性或HTML标签属性

·        JavaScript代码中

·        Html显示内容的位置(也就是开始标签与结束标签之间的内容,如<div>这里</div>(Html标签)或者<asp:LabelID="Label2" runat="server" Text="Label">这里</asp:Label>(服务器端控件),它作为占位符把变量显示于符号出现的位置)

对于第一种位置,有一些约束条件:

(1)一般的属性要求是字符串型或数值型(下面会提到有些服务器端属性支持属性为数据集合);

(2)并不是所有的属性都可以绑定变量,有些属性例如runat属性必须是"server"常量,即使绑定的字符串是server,也会导致分析器分析时出错;

(3)有一种属性,他要求属性值有约束(类型约束,比如服务器端控件要求TabIndex属性是short类型,或者字符串内容有约束),也应该在绑定时满足,否则依然可能编译时报错;

(4)还一种属性,虽然属性本身有约束,但即使绑定的变量不满足约束,也可以编译通过,比如input的checked属性,它只有checked字符串是合法的,但如果通过绑定获取到的字符串不是checked,那么这些属性将有自己内部处理机制,来保证可以正常使用;

(5)还要注意,即使对于同一类属性,服务器端和HTML的属性的处理机制也不同,同样是TabIndex(tabIndex),前者如果不满足,则分析器错误,后者则忽略这一问题。

对于第二种位置,一般只要绑定的后台变量和JavaScript中数据类型兼容即可。

对于第三种位置,如果绑定出现的位置不在服务器端控件内部,则没有约束条件,只要是常量字符串可以出现的位置,均可以绑定。但是对于置于服务器端控件内部,也就是上面那种<asp:LabelID="Label2" runat="server" Text="Label">这里</asp:Label>的方式,则有约束条件。通过总结,归纳为四类服务器端控件,如果绑定的代码出现在这些控件的开始和结束标签之间(这里所说的控件,是指如果绑定代码外有多层的嵌套控件包围,则是指包围绑定代码的最内层控件),有不同的显示结果:

(1)约束型控件:这类控件要求它的开始标签和结束标签中只能包含指定的子控件,因此如果在这里出现代码块,将编译错误。例如:

<asp:DataListrunat="server"></asp:DataList>,在它之间,要求必须嵌套<ItemTemplate></ItemTemplate>。

(2)非嵌套类控件:这类控件,不允许在内部嵌套其他控件或标签,只能是常量字符串,它会将开始标签和结束标签中常量字符串内容作为他的属性。例如上面提到的TextBox,它会将标签间内容作为它的Text属性值。

(3)嵌套类控件:这类控件,可以嵌套其他任意控件,也可以包含字符串,因此可以正常显示绑定代码块所表示的字符串内容。例如Label控件、Panel等。

(4)数据绑定类控件:这类控件是ASP.NET提供的服务器端控件,除了可以绑定普通的变量类型,也可以绑定一个数据集合(只能采取下面的第二种方式实现)。

关于是否加引号:在以上三个位置使用时,是否应该将<%= str%>或<%# str %>置于单引号或双引号中呢?对于在不同位置,处理的方式是不同的:(具体请在下面两种方式的具体介绍时,加以体会)

(1)对于第一种位置,由于JavaScript是弱类型的,如果绑定时加引号,显然就认为就当做字符串来处理,这始终是正确的;如果绑定时不加引号,它将认为这是个数值型的,那么如果获取的真是数值,当然可以,如果是非数值型,则将产生脚本错误,这即使对于JavaScript赋值常量时,也是同样的:

        var test1 = 123b;//运行时报错
        var test2=123;//正确,是数值型
        var test3="123b";//正确,字符串型

(2)对于第二种位置,经过测试,无论是对于服务器端控件属性还是HTML标签属性,加引号总是正确的;如果不加引号,则两种属性的处理方式不同:

·        对于服务器端控件属性,如果绑定的代码块不加引号,则编译时会提示“验证(ASP.NET):特性值前后必须加引号”的警告信息,但是生成为HTML后,对应生成的HTML属性已经被加上引号并获取了正确的绑定结果,因此加不加引号不会影响使用,但是建议对于规范的代码,还是加上为好;

·        对于HTML标签属性,如果不加引号,则编译时会提示“验证(XHTML 1.0 Transitional): 特性值前后必须加引号”的警告信息,并且生成为HTML属性也确实没有加上引号,那么虽然属性后面确实是没有加上引号的正确的绑定值,但是不一定能展示出想要看到的结果。比如对于input标签的value属性,如果绑定的字符串是" hello world fromvariable”,则在客户端的input显示出的内容实际上只是"hello”字符串,生效的属性值是一个被截断的字符串,它从属性后的一串字符串(若未加引号)的第一个非空字符开始,截止到下一个空字符的前一个字符为止(比如对于" hello world”,结果将是"hello”),因此,加上引号是必须的。

(3)对于第三种位置,加与不加引号,获取的值及其显示均不受影响。

因此建议,所有绑定表达式都加上引号,作为字符串获取,然后根据实际需求,用相应函数进行转换,得到所需要的类型。

另外,这里所说的后台变量是泛指的,包括如下:

·        成员变量

·        方法或属性的返回值

·        表达式,也就是所有后台能够执行的代码,运行后所得到的值(也就是直接将后台代码写在前台代码中,记得使用完全限定名或在后台中using相关namespace)

·         数据集合

后台变量有一些约束条件,需要满足:

(1)变量修饰符要求。变量是静态或者实例字段均可。对于代码隐藏模式的ASP.NET,以上的所述的变量必须为public或protected类型(因为是基类与派生类的关系),private或者internal都不行,而代码嵌入模式则任何修饰符的变量均可访问(一个类内部的关系)。

(2)变量类型要求。由于前台属性一般是字符串类型,而JavaScript基本类型也就是字符串型、数字型、布尔型,因此对应的变量应该也是这几种方式,其余类型如果不被支持(如复杂类型、数组、引用类型等),前台获取的就是调用了变量的ToString()方法所得到的字符串。因此,在绑定时,要根据情况看是否能进行隐式类型转换,必要时还要用相关函数来强制转换,以保证前台可以获得正确的值。当然,对于数据绑定类控件,它的有些属性可以为数据集合,但这时的绑定只能通过下面第二种方式才被支持。

以上是一些概念和基本约束,这些都是两种方式都应该满足的,下面具体介绍两种方式,来实现前台代码中(以下称为代码块)绑定后台变量的功能。

一. <%= str%>

此种方式其实是ASP 时代就支持的,ASP 通过包含在 < % 和 %>中的表达式将执行结果输出到客户浏览器 , 如:< % =test %>就是将变量test的值发送到客户浏览器中。在ASP.NET中,这个表达式依然可以使用,并可以出现在前台代码的上述三个位置,但是要注意,除了上述的一般性约束外,对于控件属性,还必须是绑定到服务器端控件的属性。另外,它只能绑定上面讲的前三种变量类型,不支持绑定数据集合。例子如下:

后台代码

public partial class WebForm2 : System.Web.UI.Page
   {
       public string GetVariableStr;//注意变量的修饰符
       protected void Page_Load(object sender, EventArgs e)
       {
           if (!IsPostBack)
           {
               GetVariableStr = "hello world from variable";
           }
       }
       protected string GetFunctionStr()//注意返回值的修饰符
       {
           return "hello world from Function";
       }
   }

前台代码

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script type="text/javascript">
        function fun() {
 
            var str = '<%= DateTime.Now %>';
            //前台位置1,绑定的是第三种变量类型(也是第二种方式,?因为Now是个属性)
            alert(str);
        }
    </script>
</head>
<body οnlοad="fun()">
    <form id="form1" runat="server">   
        <div>
             <input type="text" value="<%= GetVariableStr %>" />
                                                  <%--前台位置2,绑定的是成员变量--%>
             "<%= GetFunctionStr() %>"
                                                  <%--前台位置3,绑定的是一个方法的返回值>--%>
        </div>
    </form>
</body>
</html>

一些错误的使用:

之所以说第一种绑定方式要用于非服务器端控件的属性,是因为如果应用于这些服务器端属性时,这些代码实际上不被解析。 比如:

 <asp:Label ID="Label1" runat="server" Text="<%= GetVariableStr %>"></asp:Label>
 <asp:TextBox ID="TextBox1" runat="server" Text="<%= GetVariableStr %>"></asp:TextBox>

则显示出来的Label1的文本是空,而TextBox中文本是"<%= GetVariableStr %>”,所以记住,对服务器端控件的属性加这样的代码块,将不被解析,而是将这一字符串直接作为属性值了,所以不是想要的结果。如果引号也不加上,将会编译错误,提示“服务器标记不能包含 <% ... %> 构造。”。

这里结合开篇提到的关于将绑定代码快置于“Html显示内容的位置”时,如果在服务器端控件内,那四类控件如何显示的问题。如下:

  <asp:Label ID="Label1" runat="server" >"<%= GetVariableStr %>"</asp:Label>
  <asp:TextBox ID="TextBox1" runat="server" >"<%= GetVariableStr %>"</asp:TextBox>

其中,Label1属于嵌套类控件,Label1确实显示了正确的结果,TextBox属于非嵌套类控件,TextBox如果用这种方式,将会产生编译错误,提示“此上下文中不支持代码块。”

二. <%# str %>

ASP.NET 引入了一种新的声明语法 <%# %>。该语法是在 .aspx 页中使用数据绑定的基础,所有数据绑定表达式都必须包含在这些字符中。这里从用法和适用范围等方面与第一种绑定方式进行区分。

从出现的位置来看,除了能出现在第一种代码块出现的所有位置外,他还可以出现在服务器端控件的属性中。

从绑定的变量类型上看,他还可以配合ASP.NET的数据绑定类控件,来绑定上述的第四种“变量”类型,就是数据集合(DropDownList,DataList,DataGrid,ListBox这些是数据绑定类控件,数据集合包括ArrayList(数组),Hashtable(哈稀表,DataView(数据视图),DataReader等)。

用法上看,在前台代码中除了在相应位置写上<%# %>外,在后台代码中,还需要使用DataBind()方法。以下是实例:

前台代码:

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script type="text/javascript">
        function fun() {
 
            var str = '<%# DateTime.Now %>';
 
            alert(str);
        }
    </script>
</head>
<body οnlοad="fun()">
    <form id="form1" runat="server">
    <div>
        <input type="text" value="<%# GetVariableStr %>" /><br />
        "<%# GetVariableStr %>"
        <asp:Label ID="Label1" runat="server" Text="<%# GetVariableStr %>"></asp:Label>
            <%--此种方式可以绑定服务器端控件的属性--%>
        <asp:DropDownList ID="DropDownList1" runat="server" DataSource='<%# arraylist %>'>
            <%-- 将集合绑定到数据绑定类控件,通过DataSource属性来实现,从而在下拉框看到集合中的内容--%>
        </asp:DropDownList>
        <asp:DataList ID="DataList1" runat="server" DataSource='<%# dt %>'>
            <%--  同上,绑定了DataTable数据集合?--%>
            <ItemTemplate>
                <table border="1" cellpadding="0" cellspacing="0">
                    <tr>
                      <td>
                       <asp:Label ID="Label2" runat="server" Text='<%# Bind("row0")%>'></asp:Label>
                       <%--由于绑定的数据集合具有多列,并且此数据绑定类控件支持模板,
                           因此需要在模板中指定需要绑定的列以及格式--%>
                      </td>
                      <td>
                       <%# Eval("row1")%>
                      </td>
                    </tr>
                </table>
            </ItemTemplate>
        </asp:DataList>
    </div>
    </form>
</body>
</html>

可以看出,这种方式在使用时,不但可以实现(取代)<%=... %>所满足的功能,还可以绑定服务器控件属性(如上面的Label1),也可以将集合类型绑定到支持的数据绑定类控件。在用法上,前台代码除了对数据绑定类控件绑定数据集合外有所差别,其他的使用上与第一种没区别。在绑定类控件的模板中,如何使用Eval、Bind、DataBinder.Eval等,不在此文讨论中,可以参考下面链接的参考文章。

后台代码

public partial class WebForm2 : System.Web.UI.Page
{
    public string GetVariableStr;
    public ArrayList arraylist;
    public DataTable dt;
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            GetVariableStr = "hello world from variable";
 
            arraylist = new ArrayList();
            arraylist.Add("选?项?1");
            arraylist.Add("选?项?2");
 
            dt = new DataTable();
            dt.Columns.Add("row0");
            dt.Columns.Add("row1");
            DataRow dr1 = dt.NewRow();
            dr1[0] = "1.1";
            dr1[1] = "1.2";
            DataRow dr2 = dt.NewRow();
            dr2[0] = "2.1";
            dr2[1] = "2.2";
            dt.Rows.Add(dr1);
            dt.Rows.Add(dr2);
 
            Page.DataBind();
            //DropDownList1.DataBind();
            //DataList1.DataBind();
        }
    }
}

在后台代码中,与第一种方式唯一不同的,就是需要调用DataBind方法。只有执行了相应控件的DataBind方法,前台代码中这些控件中使用<%# %>的绑定才会发生(并且控件内部的所有绑定也会发生,比如又嵌套了一个绑定后台数据的控件),否则得话将不会被赋值,而是默认空值。上面我们用的是Page的DataBind方法,那么整个页面所有绑定都会执行。当然,如果我们只执行DataList1或者DropDownList1的DataBind方法,那么只有相应控件的绑定才会发生。需要注意的是,这里说的需要执行DataBind包括了显示和隐式执行,有些数据绑定类控件,当它们通过 DataSourceID 属性绑定到数据源控件时,会通过隐式调用 DataBind 方法来执行绑定。这时就不必显示的再次调用了。

两者区别:

两种绑定方式上,他们的约束基本相同,都要求与属性匹配,出现在他们可以出现的位置。后者的使用位置更广泛,尤其是支持服务器端控件和绑定数据集合。后台代码方面,后者需要调用DataBind才能完成绑定,前者则没有这方面要求。这里主要区别一下两者在执行机制上的区别:<%=...%>是在程序执行时调用(应该是在页面的RenderControl事件过程中完成的,也就是通常情况下我们可以看到的后台代码都执行完毕后再去到前台代码中进行赋值绑定),而<%#... %>是在DataBind()方法之后被调用,一旦调用了DataBind(),则它对应的控件将绑定变量,因此,请注意:如果在DataBind()后再修改变量,那么绑定的就不是最新值了,这就需要在完成变量的赋值后,再去DataBind()。其实这两种方式,它的运行过程可以在VS中通过设置断点来看看,看两者的绑定赋值分别是在什么时候发生的。

尚存的疑问:

1.不知道为什么不能获取到internal修饰的变量?

答:此问题已经在下一篇文章《ASP.NET前台无法访问后台internal类型变量的问题》中分析并解决。(2010-10-25日更新)

补充1:回答关于9楼的提到的ASP.NET 4.0中的<%: xxxx%>(2010-11-4)

答:这种方式是ASP.NET4.0中新加入的绑定方式,常用于MVC中,但普通webform中也可使用。他的功能其实就是对绑定的值进行一下编码,因此,一下两者是等同的:

 <%= Server.HtmlEncode("<b>test</b>") %>
 <%: "<b>test</b>" %>;

补充2:关于各种百分号的功能区分(2010-11-4)

答:本文上面讲了两种百分号的用法,在加上补充1中的,一共讲了三种,其实还有一种百分号用法就是<% func(xx)%>,也就是百分号后不带任何符号,然后执行一段代码,一句话概括:

在百分号內 , 如果百分号后面不带任何符号(冒号、等号、井号) , 即表示要执行一段代码而已,此处不包含任何输出信息;若带符号,即表示执行此处的代码,并且将执行后返回的值绑定(或者显示)在此处。

参考文章:

--------------------------------------------------------------------------______________________________________________________________________________________________________________-

Cookie的深入理解

Cookie虽然是个很简单的东西,但它又是WEB开发中一个很重要的客户端数据来源,而且它可以实现扩展性很好的会话状态,所以我认为每个WEB开发人员都有必要对它有个清晰的认识。本文将对Cookie这个话题做一个全面的描述,也算是我对Cookie的认识总结。

Cookie 概述

Cookie是什么?Cookie 是一小段文本信息,伴随着用户请求和页面在 Web 服务器和浏览器之间传递。Cookie 包含每次用户访问站点时 Web 应用程序都可以读取的信息。

为什么需要Cookie? 因为HTTP协议是无状态的,对于一个浏览器发出的多次请求,WEB服务器无法区分是不是来源于同一个浏览器。所以,需要额外的数据用于维护会话。 Cookie 正是这样的一段随HTTP请求一起被传递的额外数据。

Cookie能做什么?Cookie只是一段文本,所以它只能保存字符串。而且浏览器对它有大小限制以及它会随着每次请求被发送到服务器,所以应该保证它不要太大。 Cookie的内容也是明文保存的,有些浏览器提供界面修改,所以,不适合保存重要的或者涉及隐私的内容。

Cookie 的限制。 大多数浏览器支持最大为 4096 字节的 Cookie。由于这限制了 Cookie 的大小,最好用 Cookie 来存储少量数据,或者存储用户 ID 之类的标识符。用户 ID 随后便可用于标识用户,以及从数据库或其他数据源中读取用户信息。浏览器还限制站点可以在用户计算机上存储的 Cookie 的数量。大多数浏览器只允许每个站点存储20 个 Cookie;如果试图存储更多 Cookie,则最旧的 Cookie 便会被丢弃。有些浏览器还会对它们将接受的来自所有站点的Cookie 总数作出绝对限制,通常为 300 个。

通过前面的内容,我们了解到Cookie是用于维持服务端会话状态的,通常由服务端写入,在后续请求中,供服务端读取。下面本文将按这个过程看看Cookie是如何从服务端写入,最后如何传到服务端以及如何读取的。

Cookie的写、读过程

在Asp.net中,读写Cookie是通过使用HttpCookie类来完成的,它的定义如下:

public sealed class HttpCookie

{

    // 获取或设置将此 Cookie 与其关联的域。默认值为当前域。

    publicstring Domain { get; set; }

    // 获取或设置此 Cookie 的过期日期和时间(在客户端)。

    publicDateTime Expires { get; set; }

    // 获取一个值,通过该值指示 Cookie 是否具有子键。

    publicbool HasKeys { get; }

    // 获取或设置一个值,该值指定 Cookie 是否可通过客户端脚本访问。

    // 如果 Cookie 具有 HttpOnly 属性且不能通过客户端脚本访问,则为 true;否则为 false。默认为false。

    public bool HttpOnly { get; set; }

    // 获取或设置 Cookie 的名称。

    publicstring Name { get; set; }

    // 获取或设置要与当前 Cookie 一起传输的虚拟路径。默认值为当前请求的路径。

    publicstring Path { get; set; }

    // 获取或设置一个值,该值指示是否使用安全套接字层(SSL)(即仅通过 HTTPS)传输 Cookie。

    publicbool Secure { get; set; }

    // 获取或设置单个 Cookie 值。默认值为空引用。

    publicstring Value { get; set; }

    // 获取单个 Cookie 对象所包含的键值对的集合。

    publicNameValueCollection Values { get; }

    // 获取System.Web.HttpCookie.Values 属性的快捷方式。

    publicstring this[string key] { get; set; }

}

Cookie写入浏览器的过程:我们可以使用如下代码在Asp.net项目中写一个Cookie 并发送到客户端的浏览器(为了简单我没有设置其它属性)。

HttpCookie cookie = new HttpCookie("MyCookieName", "string value");

Response.Cookies.Add(cookie);

我想很多人都写过类似的代码,但是,大家有没有想过:Cookie最后是如何发送到客户端的呢?我们打开Fiddler来看一下吧。

从上图,您应该能发现,我们在服务端写的Cookie,最后其实是通过HTTP的响应头这种途径发送到客户端的。每一个写入动作,都会产生一个【Set-Cookie】的响应头。
浏览器正是在每次获取请求的响应后,检查这些头来接收Cookie的。

Asp.net获取Cookie的过程:我们可以使用如下代码在Asp.net项目中读取一个Cookie

HttpCookie cookie = Request.Cookies["MyCookieName"];

if( cookie != null )

    labCookie1.Text= cookie.Value;

else

    labCookie1.Text = "未定义";

代码同样也很简单,还是类似的问题:大家有没有想过,Cookie是如何传到服务端的呢?我们还是继续使用Fiddler来寻找答案吧。

从图片中,我们可以发现,Cookie是放在请求头中,发送到服务端的。如果你一直刷新页面,就能发现,每次HTTP请求,Cookie都会被发送。当然了,浏览器也不是发送它所接收到的所有Cookie,它会检查当前要请求的域名以及目录,只要这二项目与Cookie对应的Domain和Path匹配,才会发送。对于Domain则是按照尾部匹配的原则进行的。
所以,我在访问 www.cnblogs.com 时,浏览器并不会将我在浏览www.163.com 所接收到的 Cookie 发出去。

删除Cookie:其实就是在写Cookie时,设置Expires为一个【早于现在时间的时间】。也就是:设置此Cookie已经过期,浏览器接收到这个Cookie时,便会删除它们。

HttpCookie cookie = new HttpCookie("MyCookieName", null);

cookie.Expires= newDateTime(1900, 1, 1);

Response.Cookies.Add(cookie);

使用Cookie保存复杂对象

前面的示例代码大致演示了Cookie的读写操作。不过,我们平时可能希望将更复杂的【自定义类型】通过Cookie来保存,那么又该如何操作呢?对于这个问题,我们定义一个类型来看看如何处理。

public class DisplaySettings

{

    publicint Style;

 

    publicint Size;

   

    publicoverride string ToString()

    {

        return string.Format("Style = {0}, Size = {1}", this.Style,this.Size);

    }   

}

上面的代码,我定义一个类型,用于保存用户在浏览页面时的显示设置。接下来,我将介绍二种方法在Cookie中保存并读取它们。

方法-1,经典做法。(注意前面给出的HttpCookie定义代码中的最后二个成员)

private void WriteCookie_2a()

{

    DisplaySettings setting= newDisplaySettings { Style = 1, Size = 24 };

 

    HttpCookie cookie = newHttpCookie("DisplaySettings1");

    cookie["Style"] = setting.Style.ToString();

    cookie["Size"] = setting.Size.ToString();

 

    Response.Cookies.Add(cookie);

}

 

private void ReadCookie_2a()

{

    HttpCookie cookie = Request.Cookies["DisplaySettings1"];

    if( cookie == null )

        labDisplaySettings1.Text= "未定义";

    else{

        DisplaySettings setting = new DisplaySettings();

        setting.Style= cookie["Style"].TryToInt();

        setting.Size= cookie["Size"].TryToInt();

        labDisplaySettings1.Text= setting.ToString();

    }

}

方法-2,将对象JSON序列化为字符串。

private void WriteCookie_2b()

{

    DisplaySettings setting= newDisplaySettings { Style = 2, Size = 48 };

 

    HttpCookie cookie = newHttpCookie("DisplaySettings2", setting.ToJson());

    Response.Cookies.Add(cookie);

}

 

private void ReadCookie_2b()

{

    HttpCookie cookie = Request.Cookies["DisplaySettings2"];

    if( cookie == null )

        labDisplaySettings2.Text= "未定义";

    else{

        DisplaySettings setting = cookie.Value.FromJson<DisplaySettings>();

        labDisplaySettings2.Text= setting.ToString();

    }

}

这段代码使用了我定义的二个扩展方法。点击此处展开

/// <summary>

/// 将一个对象序列化成 JSON 格式字符串

/// </summary>

/// <param name="obj"></param>

/// <returns></returns>

public static string ToJson(this object obj)

{

    if( obj == null )

        return string.Empty;

 

    JavaScriptSerializer jss = new JavaScriptSerializer();

    return jss.Serialize(obj);

}

 

/// <summary>

/// 从JSON字符串中反序列化对象

/// </summary>

/// <typeparam name="T"></typeparam>

/// <param name="cookie"></param>

/// <returns></returns>

public static T FromJson<T>(this string cookie)

{

    if( string.IsNullOrEmpty(cookie) )

        return default(T);

 

    JavaScriptSerializer jss = new JavaScriptSerializer();

    return jss.Deserialize<T>(cookie);

}

对于这二种方法,我个人更喜欢后者,因为它具有更好扩展性:如果类型增加了成员,不需要修改读写Cookie的代码。
不过,这种方式产生的有些字符,比如【双引号】,极少数浏览器(Opera)不支持,所以需要做UrlEncode或者Base64编码处理。
同理,对于第一种方法,遇到Value有【双引号】时,我们同样需要做UrlEncode或者Base64编码处理。

Js中读写Cookie

Cookie并非只能在服务端读写,在客户端的浏览器中也可以实现对它的读写访问。而且在JS中创建的Cookie对于服务端仍然有效(可见),接下来我们来看看在JS中如何写入Cookie,演示代码将创建一个按钮,并在点击按钮后写入Cookie

<input type="button"οnclick="WriteCookie();" value="WriteCookie"/>

 

<script type="text/javascript">

    function WriteCookie() {

        var cookie= "cookie_js=22222222; path=/";

        document.cookie = cookie;

    }   

</script>

在JS中写Cookie很简单,只要给document.cookie赋值一个Cookie字符串即可,至于格式,可以参考前面用Fiddle看到的结果。

再来看一下如何使用JS读取Cookie吧。请参考如下代码:

<input type="button"οnclick="ReadCookie();" value="ReadCookie"/>

 

<script type="text/javascript">

    function ReadCookie() {

        alert(document.cookie);

    }   

</script>

仍然是访问document.cookie,不过,这次我们得到却是全部的Cookie值,每个Key/Value项用分号分开,中间则用等号分开。 所以,如果您想在JS中读取Cookie,一定要按照这个规则来拆分并解析您要读取的Cookie项。鉴于这样的操作有些繁琐,我们可以jquery.cookie.js插件来轻松完成这个功能,有兴趣的朋友也可以看一下它是如何处理的。这个插件的代码比较少,这里就直接贴出, 点击此处展开

/**

 * Create a cookie with the givenname and value and other optional parameters.

 *

 * @example $.cookie('the_cookie','the_value');

 * @desc Set the value of a cookie.

 * @example $.cookie('the_cookie','the_value', {expires: 7, path: '/', domain: 'jquery.com', secure: true});

 * @desc Create a cookie with allavailable options.

 * @example $.cookie('the_cookie','the_value');

 * @desc Create a session cookie.

 * @example $.cookie('the_cookie',null);

 * @desc Delete a cookie by passingnull as value.

 *

 * @param String name The name ofthe cookie.

 * @param String value The value ofthe cookie.

 * @param Object options An objectliteral containing key/value pairs to provide optional cookie attributes.

 * @option Number|Date expiresEither an integer specifying the expiration date from now on in days or a Dateobject.

 *                             If a negative value is specified (e.g. a datein the past), the cookie will be deleted.

 *                             If set to null oromitted, the cookie will be a session cookie and will not be retained

 *                             when the the browserexits.

 * @option String path The value ofthe path atribute of the cookie (default: path of page that created thecookie).

 * @option String domain The valueof the domain attribute of the cookie (default: domain of page that created thecookie).

 * @option Boolean secure If true,the secure attribute of the cookie will be set and the cookie transmission will

 *                        require a secureprotocol (like HTTPS).

 * @type undefined

 *

 * @name $.cookie

 * @cat Plugins/Cookie

 * @author KlausHartl/klaus.hartl@stilbuero.de

 */

 

/**

 * Get the value of a cookie withthe given name.

 *

 * @example $.cookie('the_cookie');

 * @desc Get the value of a cookie.

 *

 * @param String name The name ofthe cookie.

 * @return The value of the cookie.

 * @type String

 *

 * @name $.cookie

 * @cat Plugins/Cookie

 * @author KlausHartl/klaus.hartl@stilbuero.de

 */

jQuery.cookie = function(name, value, options) {

    if (typeof value != 'undefined') { // name and valuegiven, set cookie

        options = options || {};

        if (value === null) {

            value = '';

            options.expires = -1;

        }

        var expires = '';

        if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)){

            var date;

            if (typeof options.expires == 'number') {

                date = new Date();

               date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000));

            } else {

                date = options.expires;

            }

            expires = '; expires=' + date.toUTCString();// use expiresattribute, max-age is not supported by IE

        }

        var path = options.path ? '; path=' + options.path : '';

        var domain = options.domain ? '; domain=' + options.domain : '';

        var secure = options.secure ? '; secure' : '';

        document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join('');

    } else { // only name given, get cookie

        var cookieValue = null;

        if (document.cookie && document.cookie != '') {

            var cookies = document.cookie.split(';');

            for (var i = 0; i < cookies.length; i++) {

                var cookie = jQuery.trim(cookies[i]);

                // Does this cookiestring begin with the name we want?

                if (cookie.substring(0, name.length + 1) == (name + '=')) {

                    cookieValue = decodeURIComponent(cookie.substring(name.length+ 1));

                    break;

                }

            }

        }

        return cookieValue;

    }

};

注意哦:前面我们看到了HttpCookie有个HttpOnly属性,如果它为true,那么JS是读不到那个Cookie的,也就是说:我们如果在服务端生成的Cookie不希望在JS中能被访问,可以在写Cookie时,设置这个属性。不过,通过一些工具,还是可以看到它们。

接下来,我们再来看看Asp.net中Cookie有哪些应用。

Cookie在Session中的应用

在Asp.net中,HttpContext, Page对象都有个Session的对象,我们可以使用它来方便地在服务端保存一些与会话相关的信息。
前面我们也提到过,HTTP协议是无状态的,对于一个浏览器发出的多次请求,WEB服务器无法区分是不是来源于同一个浏览器。 所以,为了实现会话,服务端需要一个会话标识ID能保存到浏览器,让它在后续的请求时都带上这个会话标识ID,以便让服务端知道某个请求属于哪个会话,这样便可以维护与会话相关的状态数据。由于Cookie对于用户来说,是个不可见的东西,而且每次请求都会传递到服务端,所以它就是很理想的会话标识ID的保存容器。在Asp.net中,默认也就是使用Cookie来保存这个ID的。注意:虽然Asp.net 2.0 也支持无Cookie的会话,但那种方式要修改URL,也有它的缺点,因此这种方法并没有广泛的使用。本文将不对这个话题做过多的分析,就此略过无Cookie会话这种方式。

我们来看看Session是如何使用Cookie来保存会话标识ID的,在默认的Asp.net配置中,Web.config有着如下定义:

<sessionState mode="InProc"cookieName="ASP.NET_SessionId" cookieless="UseCookies"></sessionState>

 

如果我们执行以下操作:

Session["Key1"] = DateTime.Now;

 

此时,我们可以使用一些浏览器提供的工具来查看一下现在的Cookie情况。

从图片上看,这个Cookie的名字就是我们在配置文件中指出的名称,我们可以修改一下配置文件:

<sessionState cookieName="SK"></sessionState>

 

再来执行上面的写Session的操作,然后看Cookie

我们可以看到:SK的Cookie出现了。说明:在截图时我把名称为"ASP.NET_SessionId"的Cookie删除了。

通过上面示例,我们可以得到结论,Session的实现是与Cookie有关的,服务端需要将会话标识ID保存到Cookie中。
这里再一次申明,除非你使用无Cookie的会话模式,否则Session是需要Cookie的支持。反过来,Cookie并不需要Session的支持。

Cookie在身份验证中的应用

我想很多人都在Asp.net的开发中使用过Form身份认证。对于一个用户请求,我们可以在服务端很方便地判断它是不是代表一个已登录用户。

this.labStatus.Text= (Request.IsAuthenticated ? "已登录" : "未登录");

 

那么,您有没有好奇过:Asp.net是如何识别一个请求是不是一个已登录用户发起的呢?说到这里,我们就要从用户登录说起了。为了实现登录及Form认证方式,我们需要如下配置:

<authentication mode="Forms">

    <forms name="UserStatus"></forms>

</authentication>

接下来,我们需要实现用户登录逻辑。具体实现方式有很多,不过,最终的调用都是差不多的,如下代码所示:

private void SetLogin()

{

    System.Web.Security.FormsAuthentication.SetAuthCookie("fish", false);

}

只要执行了以上代码,我们就可以看到,前面的判断【Request.IsAuthenticated】返回true,最终会显示"已登录"。为了探寻这个秘密,我们还是来看一下当前页面的Cookie情况。

果然,多出来一个Cookie,名称与我在配置文件中指定的名称相同。我们再来看看如果注销当前登录会是什么样子的:

private void SetLogout()

{

    System.Web.Security.FormsAuthentication.SignOut();

}

看到了吗,名为"UserStatus"的Cookie不见了。此时如果你再去观察【Request.IsAuthenticated】,可以发现它此时返回false。或者,您也可以再试一次,登录后,直接删除名为"UserStatus"的Cookie,也能发现登录状态将显示"未登录"。或许,您还是有点不清楚前面我调用【System.Web.Security.FormsAuthentication.SetAuthCookie("fish",false);】后,Asp.net做了些什么,回答这个问题其实很简单:自己用Reflector.exe去看一下Asp.net的实现吧。
这里为了更让您能信服登录与Cookie有关,我将直接创建一个Cookie看一下 Asp.net能不能认可我创建的Cookie,并认为登录有效。请看代码:

private void SetLogin()

{

    //System.Web.Security.FormsAuthentication.SetAuthCookie("fish",false);

 

    // 下面的代码和上面的代码在作用上是等效的。

    FormsAuthenticationTicket ticket= newFormsAuthenticationTicket(

        2, "fish", DateTime.Now,DateTime.Now.AddDays(30d), false, string.Empty);

    stringstr = FormsAuthentication.Encrypt(ticket);

 

    HttpCookie cookie = newHttpCookie(FormsAuthentication.FormsCookieName,str);

    Response.Cookies.Add(cookie);

}

如果执行这段代码,您将发现:【Request.IsAuthenticated】返回true,登录状态会显示"已登录"。
至此,我们可以得出一个结论: Form身份认证依赖Cookie,Asp.net就是每次检查我们在配置文件中指定的Cookie名称,并解密这个Cookie来判断当前请求用户的登录状态。

Cookie的安全状况

从以上图片,您应该能发现:浏览器能提供一些界面让用户清楚的观察我们在服务端写的Cookie,甚至有些浏览器还提供很方便的修改功能。如下图所示:

所以,我们在服务端写代码读取Cookie时,尤其是涉及类型转换、反序列化或者解密时,一定要注意这些操作都有可能会失败。而且上图也清楚的反映了一个事实:Cookie中的值都是“一目了然”的,任何人都能看到它们。所以,我们尽量不要直接在Cookie中保存一些重要的或者敏感的内容。如果我们确实需要使用Cookie保存一些重要的内容,但又不希望被他人看懂,我们可以使用一些加密的方法来保护这些内容。

1. 对于一些重要性不高的内容,我们可以使用Base64之类的简单处理方式来处理。

2. 对于重要性相对高一点的内容,我们可以利用.net提供的一些加密工具类,自己来设计加密方法来保护。不过,密码学与加密解密并不是很简单的算法,因此,自己设计的加密方式可能不会很安全。

3. 重要的内容,我们可以使用.net提供的FormsAuthenticationTicket,FormsAuthentication来加密。我认为这种方式还是比较安全的。毕竟前面我们也看过了,Asp.net的Form身份认证就是使用这种方式来加密用户登录的身份标识的,所以,如果这种方式不安全,也就意味着Asp.net的身份认证也不安全了。如果您使用这种方式来加密,那么请注意:它产生的加密后文本还是比较大的,前面我也提到过,每次请求时,浏览器都会带上与请求相匹配的所有Cookie,因此,这种Cookie会对传输性能产生一定的影响,所以,请小心使用,切记不可过多的使用。

这里要补充一下:去年曾经出现过【Padding Oracle Attack】这个话题,一些人甚至错误的认为是Asp.net加密方式不安全!如果您也是这样认为的,那么可以看一下这篇文章:浅谈这次ASP.NET的Padding Oracle Attack相关内容 ,以消除这个错误的认识。当然了,我们也可以从这个话题得到一些收获:解密失败时,不要给出过多的提示,就当没有这个Cookie存在。

如何在C#发请的请求中使用Cookie

前面我们一直在谈服务端与浏览器中使用Cookie,其实浏览器也是一个普通的应用程序,.netframework也提供一些类也能让我们直接发起HTTP请求,下面我们来看一下如何在C#发请的请求中使用Cookie ,其实也很简单,主要是使用了CookieContainer类,请看以下演示代码:

private static string SendHttpRequestGet(string url,Encoding encoding,

            CookieContainer cookieContainer)

    {

        if( string.IsNullOrEmpty(url))

            throw new ArgumentNullException("url");

 

        if(encoding == null )

            throw new ArgumentNullException("encoding");

 

        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);

        request.Method= "GET";

        request.CookieContainer= cookieContainer;

       

        using( WebResponse response = request.GetResponse()) {

            using( StreamReader reader = new StreamReader(response.GetResponseStream(),encoding) ) {

                return reader.ReadToEnd();

            }

        }

    }

 

    privatevoid SendHttpDEMO()

    {

        StringBuilder sb = new StringBuilder();

        CookieContainer cookieContainer = new CookieContainer();

 

        string url = "http://www.taobao.com";

        SendHttpRequestGet(url, Encoding.Default,cookieContainer);

 

        // 后面可以继续发起HTTP请求,此时将会包含上次从服务器写入的Cookie

        //SendHttpRequestGet("同域名下的其它URL", Encoding.Default,cookieContainer);

 

        // 至此,我们可以显示取得了哪些Cookie

        CookieCollection cookies = cookieContainer.GetCookies(new Uri(url));

        if(cookies != null ) {

            foreach(System.Net.Cookiecookie in cookies)

                sb.AppendLine(cookie.ToString());

        }

        txtCookies.Text= sb.ToString();

    }

重构与使用总结

在前面的Asp.net示例代码中,我一直使用.net提供的HttpCookie类来操作Cookie,是为了展示用原始的方式来使用Cookie,这些代码有点重复,也有点繁琐,为此,我提供了几个简单的方法可以更容易的使用Cookie,也算是对Cookie使用的一个总结。

/// <summary>

/// 用于方便使用Cookie的扩展工具类

/// </summary>

public static class CookieExtension

{

    // 我们可以为一些使用频率高的类型写专门的【读取】方法

 

    ///<summary>

    /// 从一个Cookie中读取字符串值。

    ///</summary>

    /// <paramname="cookie"></param>

    /// <returns></returns>

    publicstatic string GetString(this HttpCookie cookie)

    {

        if(cookie == null )

            return null;

 

        return cookie.Value;

    }

 

    ///<summary>

    /// 从一个Cookie中读取 Int 值。

    ///</summary>

    /// <paramname="cookie"></param>

    /// <paramname="defaultVal"></param>

    /// <returns></returns>

    publicstatic int ToInt(this HttpCookie cookie, int defaultVal)

    {

        if(cookie == null )

            return defaultVal;

 

        return cookie.Value.TryToInt(defaultVal);

    }

 

    ///<summary>

    /// 从一个Cookie中读取值并转成指定的类型

    ///</summary>

    /// <typeparamname="T"></typeparam>

    /// <paramname="cookie"></param>

    /// <returns></returns>

    publicstatic T ConverTo<T>(thisHttpCookie cookie)

    {

        if(cookie == null )

            return default(T);

 

        return (T)Convert.ChangeType(cookie.Value,typeof(T));

    }

 

    ///<summary>

    /// 从一个Cookie中读取【JSON字符串】值并反序列化成一个对象,用于读取复杂对象

    ///</summary>

    /// <typeparamname="T"></typeparam>

    /// <paramname="cookie"></param>

    /// <returns></returns>

    publicstatic T FromJson<T>(thisHttpCookie cookie)

    {

        if(cookie == null )

            return default(T);

 

        return cookie.Value.FromJson<T>();

    }

 

 

    ///<summary>

    /// 将一个对象写入到Cookie

    ///</summary>

    /// <paramname="obj"></param>

    /// <paramname="name"></param>

    /// <paramname="expries"></param>

    publicstatic void WriteCookie(this object obj, string name, DateTime? expries)

    {

        if(obj == null)

            throw new ArgumentNullException("obj");

 

        if( string.IsNullOrEmpty(name))

            throw new ArgumentNullException("name");

       

 

        HttpCookie cookie= newHttpCookie(name, obj.ToString());

 

        if(expries.HasValue )

            cookie.Expires= expries.Value;

 

        HttpContext.Current.Response.Cookies.Add(cookie);

    }

 

    ///<summary>

    /// 删除指定的Cookie

    ///</summary>

    /// <paramname="name"></param>

    publicstatic void DeleteCookie(string name)

    {

        if( string.IsNullOrEmpty(name))

            throw new ArgumentNullException("name");

 

        HttpCookie cookie= newHttpCookie(name);

 

        // 删除Cookie,其实就是设置一个【过期的日期】

        cookie.Expires = new DateTime(1900, 1, 1);

        HttpContext.Current.Response.Cookies.Add(cookie);

    }

}

 

更完整的代码可以从本文的示例代码中获得。(文章底部有下载地址)

使用方式:

public static class TestClass

{

    publicstatic void Write()

    {

        string str = "中国";

        int aa = 25;

        DisplaySettings setting = new DisplaySettings {Style = 3, Size = 50 };

        DateTime dt = newDateTime(2012, 1, 1, 12, 0, 0);

 

        str.WriteCookie("Key1", DateTime.Now.AddDays(1d));

        aa.WriteCookie("Key2", null);

        setting.ToJson().WriteCookie("Key3", null);

        dt.WriteCookie("Key4", null);

    }

 

    publicstatic void Read()

    {

        HttpRequest request = HttpContext.Current.Request;

 

        string str = request.Cookies["Key1"].GetString();

        int num = request.Cookies["Key2"].ToInt(0);

        DisplaySettings setting = request.Cookies["Key3"].FromJson<DisplaySettings>();

        DateTime dt = request.Cookies["Key4"].ConverTo<DateTime>();

    }   

}

 

注意哦:以上代码中都是直接使用字符串"Key"的形式,这种方式对于大一些的程序在后期可能会影响维护。
所以建议:将访问Cookie所使用的Key能有一个类来统一的定义,或者将读写操作包装成一些属性放在一个类中统一的管理。

public static class CookieValues

{

    // 建议把Cookie相关的参数放在一起,提供 get / set 属性(或者方法)来访问,以避免"key"到处乱写

 

    publicstatic string AAA

    {

        get { return HttpContext.Current.Request.Cookies["Key1"].GetString(); }

    }

    publicstatic int BBB

    {

        get { return HttpContext.Current.Request.Cookies["Key2"].ToInt(0); }

    }

    publicstatic DisplaySettings CCC

    {

        get { return HttpContext.Current.Request.Cookies["Key3"].FromJson<DisplaySettings>(); }

    }

    publicstatic DateTime DDD

    {

        get { return HttpContext.Current.Request.Cookies["Key4"].ConverTo<DateTime>(); }

    }

}

补充

根据一些朋友提供的反馈,这里再补充4个需要注意的地方:

1. 如果使用Form登录验证且希望使用Cookie方式时,建议设置cookieless="UseCookies",因为这个参数的默认值是:cookieless="UseDeviceProfile",Asp.net可能会误判。 dudu就吃过亏。

<authentication mode="Forms">

    <forms name="MyCookieName" cookieless="UseCookies"></forms>

</authentication>

2. Cookie有3个属性,一般我们可以不用设置,但它们的值可以在Web.config中指定默认值:

<httpCookies domain="www.123.com"httpOnlyCookies="true" requireSSL="false"/>

3. 虽然在写Cookie时,我们可以设置name, value之外的其它属性,但是在读取时,是读不到这些设置的。其实在我的示例代码中有体现,我前面也忘记了说明了。

4. HttpRequest.Cookies 与HttpResponse.Cookies 会有关系(很奇怪吧)。
以下代码演示了这个现象:

protected void Page_Load(object sender,EventArgs e)

{

    DateTime.Now.ToString().WriteCookie("t1", null);

 

    label1.Text= ShowAllCookies();

 

    Guid.NewGuid().ToString().WriteCookie("t2", null);

 

    // 如果去掉下面代码,将会看到2个t1

    Response.Cookies.Remove("t1");

    Response.Cookies.Remove("t2");

}

 

private string ShowAllCookies()

{

    StringBuilder sb = newStringBuilder();

 

    for( inti = 0; i < Request.Cookies.Count;i++ ) {

        HttpCookie cookie= Request.Cookies[i];

        sb.AppendFormat("{0}={1}<br />", cookie.Name,cookie.Value);

    }

 

    returnsb.ToString();

}

上面的试验代码将会一直显示 t1 的Cookie ,这里就不再贴图了。

好了,就到这里吧。本文的所有示例代码可以点击此处下载。

二天时间写此文,希望能给您一点收获。如果认为此文对您有帮助,别忘了支持一下哦。

_______________________________________________________________________________________________________________________________________________________________三层开发中容易犯的错误

前记:

相信大家对三层开发都已经耳熟能详,可是我却发现新公司的既有代码中有一些违背分层开发思想的东西,现在与大家分享这些错误,我们共勉之。

如果有人觉得对三层开发拿捏得不是太准,请参照李天平的文章:分层开发思想与小笼包,这篇文章用隐喻说明分层开发,是非常好的一篇文章。

正文:

1.界面层参与非界面逻辑,抢业务逻辑层的饭碗

什么是界面逻辑:

界面层应该有的逻辑就是显示的逻辑,例如根据逻辑结果显示某一个Panel不显示另外一个Panel,或者有一个数据集应该在界面上怎么呈现,这是界面层的逻辑

例子场景:

用户登录时首先验证用户输入的用户名是否有效,如果用户名有效,然后再验证用户输入的密码是否和用户名匹配,如果匹配则表示用户可以登录,增加用户的登录次数,然后将用户的信息写入Session中;否则返回错误。在这个过程中除了将用户信息写入Session这一步属于界面逻辑以外其他的操作都应该放在业务逻辑层。

错误代码示例:

private void buttonLogin_Click(object sender, EventArgsev)
{
string userName =textBoxUserName.Text;
string password =textPassword.Text;

if (Business.Account.Exists(userName))
{
bool success = Business.Account.Login(userName, password);
if (success)
{
Business.Account.AddLoginTime(userName);

Session["user"]= new User(userName,password);
Redirect("/");
}
else
{
this.labelMessage.Text ="登录失败。";
}
}
else
{
this.lableMessage.Text ="用户名不存在。";
}
}

分析:在上面的代码中一个UI层的一个事件中调用了三次rules层的方法:

Business.Account.Exists(userName)

Business.Account.Login(userName,password)

Business.Account.AddLoginTime(userName);

还附加有条件判断,这种方法在执行效果上面是没有什么错误的,可是却造成了逻辑前移;本来应该在逻辑层执行的判断放在了界面层,是不合适的。

2.数据访问层参与了大量的业务逻辑

这种现象经常出现在大量使用存储过程的系统中,将一大堆逻辑统统放在一个存储过程中实现了,乍一看可能很有效率,其实造成了系统结构的失调,给维护带来困难,数据访问层甚者数据库要抢逻辑层的饭碗了。

还以用户登录为例:

下面是业务逻辑层的登录方法:

//业务逻辑层的登录方法
public int Login(string userName, string password)
{
returnDataAccess.UserAccount.Login(userName, password);
}

 

下面是数据层的登录方法:

//数据访问层的登录方法
public int Login(string userName, string password)
{
SqlParameter[]parameters = new SqlParameter[]{
//
};
returnSqlHelper.ExecuteProcedure("Login",parameters);
}

 

下面是登录的存储过程:

CREATE PROC Login
@userName varchar(20),
@password varchar(20)
AS
IF NOT EXISTS(SELECT * FROM UserAccount WHERE UserName = @userName)
RETURN -1
IF NOT EXISTS(SELECT * FROM UserAccount WHERE UserName = @userName AND password = @password)
RETURN 1

UPDATE UserAccount
SET LoginTimes = LoginTimes + 1
WHERE UserName = @userName

RETURN 0

 

分析:从上面三段代码中我们可以很显然得看到登录的业务逻辑已经全部被后移到了数据库的存储过程中。这样使用的三层结构就失去了意义,逻辑层名存实亡了;而数据库的压力会越来越大;我们修改业务逻辑的时候不是到逻辑层修改,而是要到数据库中去修改了。

3.将界面层上的数据组件(如SqlDataSource)作为参数传递到业务逻辑层去赋值

这样做的坏处很明显,本来是界面层依赖于业务逻辑层的,现在业务逻辑层反过来去依赖界面层的类,需要逻辑层引用System.Web命名空间,显然是错误的。

4.为了省事儿,不是直接将参数传递到业务逻辑层,而是通过HttpContext直接获得界面层应该传递的参数

例子:在系统设计的初期没有记录用户登录的IP地址,而到了后期发现了这个问题,要求纪录用户IP了,为了不修改业务逻辑层方法的定义,也不用修改界面层的调用方法,于是便写出了下面的代码:

public int Login(string userName, string password)
{
string userIP =System.Web.HttpContext.Current.Request.UserHostAddress;
//follow is login steps
}

这一点犯的错误和3中的错误相同,导致层之间形成了依赖环。

5.将事务处理放在数据访问层来做

事务处理应该放在业务逻辑层处理,原因是

1)事务的划分是根据业务逻辑来定义的,通常一个事务就代表完成了一个完整的逻辑操作;

2)一个业务逻辑可能有几个数据操作,修改几个表中的数据,而通常数据层的类的划分是根据数据库表来划分的,如果把事务处理放在数据访问层,那么业务层的方法需要调用两个以上的数据层方法时,就会出现执行两个事务的情况,显然这是不合理的。

总结:

1.在三层结构的划分中我们应该使三层各负其责,谁也不能越权,谁也不能懒惰,通常情况下,逻辑层应该在满足各负其责的条件下,尽可能的厚。

2.三层结构中的依赖关系很明确,界面层依赖于逻辑层,逻辑层依赖于数据访问层,不能互相依赖,而形成依赖环。

-------------________________________________________________________________________________________________________________________________________________________为什么不能只用前台js验证[必须结合后台验证]

自从9月份在同事推荐下在某团购网买了一份火锅的套餐后,就迷上了,几乎每天必去浏览一遍,看看有什么又便宜又好吃的。

元旦期间当然也不例外,1号那天上午,看到了XXX团购网的“VIP会员0元领红包”活动,0元?我最喜欢了,虽然参与过

很多次0元抽奖的活动,一次也没中,但是人总是有一种信念相信自己的运气的。于是果断进去注册,点击购买,进入了购物车

再点击确认订单,恩?怎么alert这么一句“本活动只限VIP会员参与”?我第一反应是去看页面源代码(由于该活动已经结束,

进不去购买页面了,所以在这里我只好用伪代码来表示):

//确认订单按钮的点击事件
function btn_click(){
     ajax获取当前用户的类型
    如果不是VIP,alert("本活动仅限VIP会员参与");
     否则form1.submit();
}

然后我在地址栏敲入:javascript:form1.submit(); 回车! 进入付款页面了,再点击确定,恩?购买成功!我获得了5元红包!

太给力了!!!我又新注册一个账号,重复上面的步骤,也成功获得了5元红包。

马上给客服留言说明此BUG,不过到今天还没回复我,呵呵。

这个漏洞的关键点是开发人员忘记了在form1.submit()的后台代码中判断当前用户是否VIP会员,只依赖于javascript的验证。

前台的验证顶啥用啊,完全可以绕过去,后台的验证才最重要!!

有了上午的收获,1号晚上我就继续找别的团购网的漏洞,果然被我找到一个更严重的。

该团购网也举行了一个元旦抽奖砸金蛋活动,也是免费参与,购买后发现得邀请好友参与活动才有砸金蛋的机会,邀请一个好友就多一

个机会,如图:

我一点击金蛋就alert一句“没有抽奖机会了快去邀请好友吧!”,恩,又是javascript?看看代码先:

这便是金蛋的点击事件,其中有一个用AJAX去访问的页面lottery1.php,而要传过去的chance变量应该就是当前用户拥有的砸蛋机会。

我试着直接访问lottery1.php?chance=1,返回error字符串,lottery1.php?chance=0也返回error,lottery1.php?chance=-1,也

返回error,难道没效果么?我刷新了一下砸金蛋的页面,哇!!

我传了-1过去导致溢出了?我试着砸了几个,每次都成功获得代金卷!!太给力了。接着试着用代金卷去下单,也能成功减免掉几块钱,

不过一张订单只能用一个代金卷,呵呵(当然测试用的订单我最后取消掉了,本人还没那么邪恶,哇咔咔)

马上联系客服,居然下班了,QQ不在线,电话打不通,只好留了个言。

接下来干嘛呢?砸蛋呗!42亿的金蛋呢,写了段JS自动砸!截止现在一共有3588个金蛋被砸开,其中至少有2000多个是我砸的,哇咔咔

得到了一大堆的代金卷:

整整185页,呵呵,蛮壮观的!!!

到了2号,我重新查看该团购网的代码时,发现了一个更严重的问题:

JS中有这么个方法

乍一看是跟钱有关的吧,传入用户ID和钱的数目,试试有什么效果。

用户ID怎么获得呢?别急,页面上有:

这个96204就是我当前帐户的ID了,访问了一下,返回“线下充值成功”,哇,这么给力?充值页面都不加权限验证的?

查看了一下帐户余额,果然充值成功了:

哥有2万余额了,哇咔咔!!这个漏洞太致命了,立马给客服留言。刚留完言,他们的开发人员给我打电话了,和我讨论

砸金蛋的漏洞问题,正好将刚发现的漏洞一起告诉他。开发人员就是命苦啊,元旦期间,晚上10点多了,他还要改代码。

改完他说老板可能送点礼品给我,好期待啊,呵呵。

最后他把我的帐户余额清零了,我在心里呼喊:不~要~啊,我的2万元啊~~~~~~~~

总结一下:前台的验证都是不靠谱的,后台必要都要验证一遍;管理页面一定要加访问权限;传递到后台的数据一定要

进行合法性验证;不必要传递的参数就不传,比如那个砸蛋,我就想不明白为什么要把当前用户拥有的砸蛋机会传递到

后台,直接从数据库中读取不行么?用户ID不要以明文出现。另外还要防范XSS跨站脚本攻击(一般用判断主机头的方式)

______________________________________________________________________________________________________________________________________________________________

 

应用程序权限设计思路(二)

对于权限问题,我觉得需要抓住下面的这四个问题;

1、我们的软件里面有哪些功能?
2、哪些人可以访问到哪些功能结点?
3、访问到了页面后可以做哪些事情?(查询、添加、修改、删除、导出、打印等)
(原来的说法:详细权限的划分)
4、在同一个页面里哪些人可以看到那些信息
(原来的说法:资源的访问权限)

这是我的个人见解,是通过几个项目总结出来的,如果不全面、或者不正确的话,欢迎大家及时指正,共同努力、共同提高!

我设计了下面的这几个表,来解决这些问题。这里我只想表示表之间的关联,至于字段我只是写了几个主要的,字段的设计嘛,大家肯定各有各的方式,我想我写出来主要的就可以了。



我的英文比较差,还是直接用中文吧,这样更直接一些。

如果看图不太清楚的话,可以到这里下载 visio 格式的文件。http://www.cnblogs.com/jyk/archive/2008/04/25/1170979.html

先来看第一个问题,[项目—功能结点]和[项目—节点拥有的详细权限] 这两个表记录了项目里面有哪些功能结点和详细的功能,
[项目—功能结点] 功能节点,可以通过这个表来呈现左面的功能树。记录打开的页面和相关的信息。
[项目—节点拥有的详细权限]按钮组,一个功能节点(主要是列表页面)有哪些按钮,比如“添加”按钮,“修改”按钮等。记录按钮的名称、打开的页面和相关的信息。

这两个表是在设计阶段完成的,程序员可以根据这个来实现功能。

解决了第一个问题后,第二个问题就好办了,[项目—角色] 和 [角色拥有的功能结点]来实现。
[项目—角色] 记录项目里面有哪些角色。
[角色拥有的功能结点] 记录一个角色拥有哪些功能结点,功能结点里面有哪些具体的操作(添加、修改等)

不知道大家的项目的角色是在什么时候诞生的,是在设计的时候吗?还是程序做好之后由用户自己设计?我的做法是后者。因为客户比我们更清楚需要多少角色,需要什么样的角色,一个角色里有哪些功能更合适。

我们可以做一个维护程序,让客户自行添加。先在角色表里添加一个角色,然后选择角色可以看到的功能页面,最后选择在这个功能结点里可以做的操作(添加、修改等)。这样一个角色就诞生了。

角色有了之后,就可以给人员分配角色,或者是往角色里添加人员。这样就解决了哪些人可以访问哪些功能节点的问题。

其实在设计角色的时候就把第三个问题也搞定了。

【添加角色的截图】




操作也是比较方便的,当点击“计划和日志”前面的方框(打对号)的时候会自动展开下面的子结点和子子节点,然后这些节点都会被选中,后面的按钮也会被选中。
当选中“工作日志”的时候,上面的节点和后面的按钮也会被选中。

上面的信息全部来自数据库,也就是第一个图里的哪些表。


第四个问题还没有更好的解决方法,目前只能在程序里面硬编码。

__________________________________________________________________________________________________________________________________________________________________应用程序权限设计

我们在开发系统的时候,经常会遇到系统需要权限控制,而权限的控制程度不同有不同的设计方案。

1. 基于角色的权限设计

这种方案是最常见也是比较简单的方案,不过通常有这种设计已经够了,所以微软就设计出这种方案的通用做法,这种方案对于每一个操作不做控制,只是在程序中根据角色对是否具有操作的权限进行控制;这里我们就不做详述

2. 基于操作的权限设计

这种模式下每一个操作都在数据库中有记录,用户是否拥有该操作的权限也在数据库中有记录,结构如下:


但是如果直接使用上面的设计,会导致数据库中的UserAction这张表数据量非常大,所以我们需要进一步设计提高效率,请看方案3

3. 基于角色和操作的权限设计


如上图所示,我们在添加了Role,和RoleAction表,这样子就可以减少UserAction中的记录,并且使设计更灵活一点。

但是这种方案在用户需求的考验之下也可能显得不够灵活够用,例如当用户要求临时给某位普通员工某操作权限时,我们就需要新增加一种新的用户角色,但是这种用户角色是不必要的,因为它只是一种临时的角色,如果添加一种角色还需要在收回此普通员工权限时删除此角色,我们需要设计一种更合适的结构来满足用户对权限设置的要求。

4. 2,3组合的权限设计,其结构如下:


我们可以看到在上图中添加了UserAction表,使用此表来添加特殊用户的权限,改表中有一个字段HasPermission可以决定用户是否有某种操作的权限,改表中记录的权限的优先级要高于UserRole中记录的用户权限。这样在应用程序中我们就需要通过UserRole和UserAction两张表中的记录判断权限。

到这儿呢并不算完,有可能用户还会给出这样的需求:对于某一种action所操作的对象某一些记录会有权限,而对于其他的记录没有权限,比如说一个内容管理系统,对于某一些频道某个用户有修改的权限,而对于另外一些频道没有修改的权限,这时候我们需要设计更复杂的权限机制。

5. 对于同一种实体(资源)用户可以对一部分记录有权限,而对于另外一些记录没有权限的权限设计:


对于这样的需求我们就需要对每一种不同的资源创建一张权限表,在上图中对Content和Channel两种资源分别创建了UserActionContent和UserActionChannel表用来定义用户对某条记录是否有权限;这种设计是可以满足用户需求的但是不是很经济,UserActionChannel和UserActionContent中的记录会很多,而在实际的应用中并非需要记录所有的记录的权限信息,有时候可能只是一种规则,比如说对于根Channel什么级别的人有权限;这时候呢我们就可以定义些规则来判断用户权限,下面就是这种设计。

6. 涉及资源,权限和规则的权限设计


在这种设计下角色的概念已经没有了,只需要Rule在程序中的类中定义用户是否有操作某种对象的权限。

以上只是分析思路,如果有不对的地方,请大家指正。

______________________________________________________________________________________________________________________________________________________________________悟透JavaScript,对JavaScript的深入理解

做Web程序开发,HTML,CS,JS是必不可少的基础知识。JavaScript是一种基于对象和事件驱动并具有相对安全性的客户端脚本语言。同时也是一种广泛用于客户端Web开发的脚本语言,常用来给HTML网页添加动态功能,比如响应用户的各种操作。它最初由网景公司的Brendan Eich设计,是一种动态、弱类型、基于原型的语言,内置支持类。JavaScript是Sun公司的注册商标。[1] Ecma国际以JavaScript为基础制定了ECMAScript标准。JavaScript也可以用于其他场合,如服务器端编程。完整的JavaScript实现包含三个部分:ECMAScript,文档对象模型,字节顺序记号。本文地址 悟透JavaScript,对JavaScript的深入理解

引子

编程世界里只存在两种基本元素,一个是数据,一个是代码。编程世界就是在数据和代码千丝万缕的纠缠中呈现出无限的生机和活力。

数据天生就是文静的,总想保持自己固有的本色;而代码却天生活泼,总想改变这个世界。

你看,数据代码间的关系与物质能量间的关系有着惊人的相似。数据也是有惯性的,如果没有代码来施加外力,她总保持自己原来的状态。而代码就象能量,他存在的唯一目的,就是要努力改变数据原来的状态。在代码改变数据的同时,也会因为数据的抗拒而反过来影响或改变代码原有的趋势。甚至在某些情况下,数据可以转变为代码,而代码却又有可能被转变为数据,或许还存在一个类似E=MC2形式的数码转换方程呢。然而,就是在数据和代码间这种即矛盾又统一的运转中,总能体现出计算机世界的规律,这些规律正是我们编写的程序逻辑。

不过,由于不同程序员有着不同的世界观,这些数据和代码看起来也就不尽相同。于是,不同世界观的程序员们运用各自的方法论,推动着编程世界的进化和发展。

众所周知,当今最流行的编程思想莫过于面向对象编程的思想。为什么面向对象的思想能迅速风靡编程世界呢?因为面向对象的思想首次把数据和代码结合成统一体,并以一个简单的对象概念呈现给编程者。这一下子就将原来那些杂乱的算法与子程序,以及纠缠不清的复杂数据结构,划分成清晰而有序的对象结构,从而理清了数据与代码在我们心中那团乱麻般的结。我们又可以有一个更清晰的思维,在另一个思想高度上去探索更加浩瀚的编程世界了。

在五祖弘忍讲授完《对象真经》之后的一天,他对众弟子们说:“经已讲完,想必尔等应该有所感悟,请各自写个偈子来看”。大弟子神秀是被大家公认为悟性最高的师兄,他的偈子写道:“身是对象树,心如类般明。朝朝勤拂拭,莫让惹尘埃!”。此偈一出,立即引起师兄弟们的轰动,大家都说写得太好了。只有火头僧慧能看后,轻轻地叹了口气,又随手在墙上写道:“对象本无根,类型亦无形。本来无一物,何处惹尘埃?”。然后摇了摇头,扬长而去。大家看了慧能的偈子都说:“写的什么乱七八糟的啊,看不懂”。师父弘忍看了神秀的诗偈也点头称赞,再看慧能的诗偈之后默然摇头。就在当天夜里,弘忍却悄悄把慧能叫到自己的禅房,将珍藏多年的软件真经传授于他,然后让他趁着月色连夜逃走...

后来,慧能果然不负师父厚望,在南方开创了禅宗另一个广阔的天空。而慧能当年带走的软件真经中就有一本是《JavaScript真经》!

回归简单

要理解JavaScript,你得首先放下对象和类的概念,回到数据和代码的本原。前面说过,编程世界只有数据和代码两种基本元素,而这两种元素又有着纠缠不清的关系。JavaScript就是把数据和代码都简化到最原始的程度。

JavaScript中的数据很简洁的。简单数据只有 undefined, null, boolean, number和string这五种,而复杂数据只有一种,即object。这就好比中国古典的朴素唯物思想,把世界最基本的元素归为金木水火土,其他复杂的物质都是由这五种基本元素组成。

JavaScript中的代码只体现为一种形式,就是function。

注意:以上单词都是小写的,不要和Number, String, Object, Function等JavaScript内置函数混淆了。要知道,JavaScript语言是区分大小写的呀!

任何一个JavaScript的标识、常量、变量和参数都只是unfined,null, bool, number, string, object 和function类型中的一种,也就typeof返回值表明的类型。除此之外没有其他类型了。

先说说简单数据类型吧。

undefined: 代表一切未知的事物,啥都没有,无法想象,代码也就更无法去处理了。
注意:typeof(undefined) 返回也是undefined。
可以将undefined赋值给任何变量或属性,但并不意味了清除了该变量,反而会因此多了一个属性。

null: 有那么一个概念,但没有东西。无中似有,有中还无。虽难以想象,但已经可以用代码来处理了。
注意:typeof(null)返回object,但null并非object,具有null值的变量也并非object。

boolean: 是就是,非就非,没有疑义。对就对,错就错,绝对明确。既能被代码处理,也可以控制代码的流程。

number: 线性的事物,大小和次序分明,多而不乱。便于代码进行批量处理,也控制代码的迭代和循环等。
注意:typeof(NaN)和typeof(Infinity)都返回number 。
NaN参与任何数值计算的结构都是NaN,而且 NaN != NaN 。
Infinity / Infinity = NaN 。

string: 面向人类的理性事物,而不是机器信号。人机信息沟通,代码据此理解人的意图等等,都靠它了。

简单类型都不是对象,JavaScript没有将对象化的能力赋予这些简单类型。直接被赋予简单类型常量值的标识符、变量和参数都不是一个对象。

所谓“对象化”,就是可以将数据和代码组织成复杂结构的能力。JavaScript中只有object类型和function类型提供了对象化的能力。

没有类

object就是对象的类型。在JavaScript中不管多么复杂的数据和代码,都可以组织成object形式的对象。

但JavaScript却没有“类”的概念!

对于许多面向对象的程序员来说,这恐怕是JavaScript中最难以理解的地方。是啊,几乎任何讲面向对象的书中,第一个要讲的就是“类”的概念,这可是面向对象的支柱。这突然没有了“类”,我们就象一下子没了精神支柱,感到六神无主。看来,要放下对象和类,达到“对象本无根,类型亦无形”的境界确实是件不容易的事情啊。

这样,我们先来看一段JavaScript程序:

var life = {};
for(life.age = 1;life.age <= 3; life.age++)
{
switch(life.age)
{
case 1: life.body ="卵细胞";
life.say = function(){alert(this.age+this.body)};
break;
case 2: life.tail ="尾巴";
life.gill = "腮";
life.body = "蝌蚪";
life.say = function(){alert(this.age+this.body+"-"+this.tail+","+this.gill)};
break;
case 3: delete life.tail;
delete life.gill;
life.legs = "四条腿";
life.lung = "肺";
life.body = "青蛙";
life.say = function(){alert(this.age+this.body+"-"+this.legs+","+this.lung)};
break;
};
life.say();
};


这段JavaScript程序一开始产生了一个生命对象life,life诞生时只是一个光溜溜的对象,没有任何属性和方法。在第一次生命过程中,它有了一个身体属性body,并有了一个say方法,看起来是一个“卵细胞”。在第二次生命过程中,它又长出了“尾巴”和“腮”,有了tail和gill属性,显然它是一个“蝌蚪”。在第三次生命过程中,它的tail和gill属性消失了,但又长出了“四条腿”和“肺”,有了legs和lung属性,从而最终变成了“青蛙”。如果,你的想像力丰富的话,或许还能让它变成英俊的“王子”,娶个美丽的“公主”什么的。不过,在看完这段程序之后,请你思考一个问题:

我们一定需要类吗?

还记得儿时那个“小蝌蚪找妈妈”的童话吗?也许就在昨天晚,你的孩子刚好是在这个美丽的童话中进入梦乡的吧。可爱的小蝌蚪也就是在其自身类型不断演化过程中,逐渐变成了和妈妈一样的“类”,从而找到了自己的妈妈。这个童话故事中蕴含的编程哲理就是:对象的“类”是从无到有,又不断演化,最终又消失于无形之中的...

“类”,的确可以帮助我们理解复杂的现实世界,这纷乱的现实世界也的确需要进行分类。但如果我们的思想被“类”束缚住了,“类”也就变成了“累”。想象一下,如果一个生命对象开始的时就被规定了固定的“类”,那么它还能演化吗?蝌蚪还能变成青蛙吗?还可以给孩子们讲小蝌蚪找妈妈的故事吗?

所以,JavaScript中没有“类”,类已化于无形,与对象融为一体。正是由于放下了“类”这个概念,JavaScript的对象才有了其他编程语言所没有的活力。

如果,此时你的内心深处开始有所感悟,那么你已经逐渐开始理解JavaScript的禅机了。

函数的魔力

接下来,我们再讨论一下JavaScript函数的魔力吧。

JavaScript的代码就只有function一种形式,function就是函数的类型。也许其他编程语言还有procedure或 method等代码概念,但在JavaScript里只有function一种形式。当我们写下一个函数的时候,只不过是建立了一个function类型的实体而已。请看下面的程序:

function myfunc()
{
alert("hello");
};

alert(typeof(myfunc));


这个代码运行之后可以看到typeof(myfunc)返回的是function。以上的函数写法我们称之为“定义式”的,如果我们将其改写成下面的“变量式”的,就更容易理解了:

var myfunc = function ()
{
alert("hello");
};

alert(typeof(myfunc));


这里明确定义了一个变量myfunc,它的初始值被赋予了一个function的实体。因此,typeof(myfunc)返回的也是function。其实,这两种函数的写法是等价的,除了一点细微差别,其内部实现完全相同。也就是说,我们写的这些JavaScript函数只是一个命了名的变量而已,其变量类型即为function,变量的值就是我们编写的函数代码体。

聪明的你或许立即会进一步的追问:既然函数只是变量,那么变量就可以被随意赋值并用到任意地方啰?

我们来看看下面的代码:

var myfunc = function ()
{
alert("hello");
};
myfunc(); //第一次调用myfunc,输出hello

myfunc = function ()
{
alert("yeah");
};
myfunc(); //第二次调用myfunc,将输出yeah


这个程序运行的结果告诉我们:答案是肯定的!在第一次调用函数之后,函数变量又被赋予了新的函数代码体,使得第二次调用该函数时,出现了不同的输出。

好了,我们又来把上面的代码改成第一种定义式的函数形式:

function myfunc ()
{
alert("hello");
};
myfunc(); //这里调用myfunc,输出yeah而不是hello

function myfunc ()
{
alert("yeah");
};
myfunc(); //这里调用myfunc,当然输出yeah


按理说,两个签名完全相同的函数,在其他编程语言中应该是非法的。但在JavaScript中,这没错。不过,程序运行之后却发现一个奇怪的现象:两次调用都只是最后那个函数里输出的值!显然第一个函数没有起到任何作用。这又是为什么呢?

原来,JavaScript执行引擎并非一行一行地分析和执行程序,而是一段一段地分析执行的。而且,在同一段程序的分析执行中,定义式的函数语句会被提取出来优先执行。函数定义执行完之后,才会按顺序执行其他语句代码。也就是说,在第一次调用myfunc之前,第一个函数语句定义的代码逻辑,已被第二个函数定义语句覆盖了。所以,两次都调用都是执行最后一个函数逻辑了。

如果把这个JavaScript代码分成两段,例如将它们写在一个html中,并用<script/>标签将其分成这样的两块:

<script>
function myfunc ()
{
alert("hello");
};
myfunc(); //这里调用myfunc,输出hello
</script>

<script>
function myfunc ()
{
alert("yeah");
};
myfunc(); //这里调用myfunc,输出yeah
</script>


这时,输出才是各自按顺序来的,也证明了JavaScript的确是一段段地执行的。

一段代码中的定义式函数语句会优先执行,这似乎有点象静态语言的编译概念。所以,这一特征也被有些人称为:JavaScript的“预编译”。

大多数情况下,我们也没有必要去纠缠这些细节问题。只要你记住一点:JavaScript里的代码也是一种数据,同样可以被任意赋值和修改的,而它的值就是代码的逻辑。只是,与一般数据不同的是,函数是可以被调用执行的。

不过,如果JavaScript函数仅仅只有这点道行的话,这与C++的函数指针,DELPHI的方法指针,C#的委托相比,又有啥稀奇嘛!然而,JavaScript函数的神奇之处还体现在另外两个方面:一是函数function类型本身也具有对象化的能力,二是函数function与对象 object超然的结合能力。

奇妙的对象

先来说说函数的对象化能力。

任何一个函数都可以为其动态地添加或去除属性,这些属性可以是简单类型,可以是对象,也可以是其他函数。也就是说,函数具有对象的全部特征,你完全可以把函数当对象来用。其实,函数就是对象,只不过比一般的对象多了一个括号“()”操作符,这个操作符用来执行函数的逻辑。即,函数本身还可以被调用,一般对象却不可以被调用,除此之外完全相同。请看下面的代码:

function Sing()
{
with(arguments.callee)
alert(author + ":" + poem);
};
Sing.author = "李白";
Sing.poem = "汉家秦地月,流影照明妃。一上玉关道,天涯去不归";
Sing();
Sing.author = "李战";
Sing.poem = "日出汉家天,月落阴山前。女儿琵琶怨,已唱三千年";
Sing();


在这段代码中,Sing函数被定义后,又给Sing函数动态地增加了author和poem属性。将author和poem属性设为不同的作者和诗句,在调用Sing()时就能显示出不同的结果。这个示例用一种诗情画意的方式,让我们理解了JavaScript函数就是对象的本质,也感受到了JavaScript语言的优美。

好了,以上的讲述,我们应该算理解了function类型的东西都是和object类型一样的东西,这种东西被我们称为“对象”。我们的确可以这样去看待这些“对象”,因为它们既有“属性”也有“方法”嘛。但下面的代码又会让我们产生新的疑惑:

var anObject = {}; //一个对象
anObject.aProperty = "Property of object"; //对象的一个属性
anObject.aMethod = function(){alert("Method of object")}; //对象的一个方法
//主要看下面:
alert(anObject["aProperty"]); //可以将对象当数组以属性名作为下标来访问属性
anObject["aMethod"](); //可以将对象当数组以方法名作为下标来调用方法
for( var s in anObject) //遍历对象的所有属性和方法进行迭代化处理
alert(s + " is a " + typeof(anObject[s]));


同样对于function类型的对象也是一样:

var aFunction = function() {}; //一个函数
aFunction.aProperty = "Property of function"; //函数的一个属性
aFunction.aMethod = function(){alert("Method of function")}; //函数的一个方法
//主要看下面:
alert(aFunction["aProperty"]); //可以将函数当数组以属性名作为下标来访问属性
aFunction["aMethod"](); //可以将函数当数组以方法名作为下标来调用方法
for( var s in aFunction) //遍历函数的所有属性和方法进行迭代化处理
alert(s + " is a " + typeof(aFunction[s]));


是的,对象和函数可以象数组一样,用属性名或方法名作为下标来访问并处理。那么,它到底应该算是数组呢,还是算对象?

我们知道,数组应该算是线性数据结构,线性数据结构一般有一定的规律,适合进行统一的批量迭代操作等,有点像波。而对象是离散数据结构,适合描述分散的和个性化的东西,有点像粒子。因此,我们也可以这样问:JavaScript里的对象到底是波还是粒子?

如果存在对象量子论,那么答案一定是:波粒二象性!

因此,JavaScript里的函数和对象既有对象的特征也有数组的特征。这里的数组被称为“字典”,一种可以任意伸缩的名称值对儿的集合。其实, object和function的内部实现就是一个字典结构,但这种字典结构却通过严谨而精巧的语法表现出了丰富的外观。正如量子力学在一些地方用粒子来解释和处理问题,而在另一些地方却用波来解释和处理问题。你也可以在需要的时候,自由选择用对象还是数组来解释和处理问题。只要善于把握JavaScript的这些奇妙特性,就可以编写出很多简洁而强大的代码来。

放下对象

我们再来看看function与object的超然结合吧。

在面向对象的编程世界里,数据与代码的有机结合就构成了对象的概念。自从有了对象,编程世界就被划分成两部分,一个是对象内的世界,一个是对象外的世界。对象天生具有自私的一面,外面的世界未经允许是不可访问对象内部的。对象也有大方的一面,它对外提供属性和方法,也为他人服务。不过,在这里我们要谈到一个有趣的问题,就是“对象的自我意识”。

什么?没听错吧?对象有自我意识?

可能对许多程序员来说,这的确是第一次听说。不过,请君看看C++、C#和Java的this,DELPHI的self,还有VB的me,或许你会恍然大悟!当然,也可能只是说句“不过如此”而已。

然而,就在对象将世界划分为内外两部分的同时,对象的“自我”也就随之产生。“自我意识”是生命的最基本特征!正是由于对象这种强大的生命力,才使得编程世界充满无限的生机和活力。

但对象的“自我意识”在带给我们快乐的同时也带来了痛苦和烦恼。我们给对象赋予了太多欲望,总希望它们能做更多的事情。然而,对象的自私使得它们互相争抢系统资源,对象的自负让对象变得复杂和臃肿,对象的自欺也往往带来挥之不去的错误和异常。我们为什么会有这么多的痛苦和烦恼呢?

为此,有一个人,在对象树下,整整想了九九八十一天,终于悟出了生命的痛苦来自于欲望,但究其欲望的根源是来自于自我意识。于是他放下了“自我”,在对象树下成了佛,从此他开始普度众生,传播真经。他的名字就叫释迦摩尼,而《JavaScript真经》正是他所传经书中的一本。

JavaScript中也有this,但这个this却与C++、C#或Java等语言的this不同。一般编程语言的this就是对象自己,而 JavaScript的this却并不一定!this可能是我,也可能是你,可能是他,反正是我中有你,你中有我,这就不能用原来的那个“自我”来理解 JavaScript这个this的含义了。为此,我们必须首先放下原来对象的那个“自我”。

我们来看下面的代码:

function WhoAmI() //定义一个函数WhoAmI
{
alert("I'm " + this.name + " of " + typeof(this));
};

WhoAmI(); //此时是this当前这段代码的全局对象,在浏览器中就是window对象,其name属性为空字符串。输出:I'm of object

var BillGates = {name:"Bill Gates"};
BillGates.WhoAmI = WhoAmI; //将函数WhoAmI作为BillGates的方法。
BillGates.WhoAmI(); //此时的this是BillGates。输出:I'm Bill Gates ofobject

var SteveJobs = {name:"Steve Jobs"};
SteveJobs.WhoAmI = WhoAmI; //将函数WhoAmI作为SteveJobs的方法。
SteveJobs.WhoAmI(); //此时的this是SteveJobs。输出:I'm Steve Jobs ofobject

WhoAmI.call(BillGates); //直接将BillGates作为this,调用WhoAmI。输出:I'm Bill Gates ofobject
WhoAmI.call(SteveJobs); //直接将SteveJobs作为this,调用WhoAmI。输出:I'm Steve Jobs ofobject

BillGates.WhoAmI.call(SteveJobs); //将SteveJobs作为this,却调用BillGates的WhoAmI方法。输出:I'm Steve Jobs ofobject
SteveJobs.WhoAmI.call(BillGates); //将BillGates作为this,却调用SteveJobs的WhoAmI方法。输出:I'm Bill Gates ofobject

WhoAmI.WhoAmI = WhoAmI; //将WhoAmI函数设置为自身的方法。
WhoAmI.name = "WhoAmI";
WhoAmI.WhoAmI(); //此时的this是WhoAmI函数自己。输出:I'm WhoAmI of function

({name: "nobody", WhoAmI: WhoAmI}).WhoAmI(); //临时创建一个匿名对象并设置属性后调用WhoAmI方法。输出:I'm nobody of object


从上面的代码可以看出,同一个函数可以从不同的角度来调用,this并不一定是函数本身所属的对象。this只是在任意对象和function元素结合时的一个概念,是种结合比起一般对象语言的默认结合更加灵活,显得更加超然和洒脱。

在JavaScript函数中,你只能把this看成当前要服务的“这个”对象。this是一个特殊的内置参数,根据this参数,您可以访问到“这个”对象的属性和方法,但却不能给this参数赋值。在一般对象语言中,方法体代码中的this可以省略的,成员默认都首先是“自己”的。但JavaScript却不同,由于不存在“自我”,当访问“这个”对象时,this不可省略!

JavaScript提供了传递this参数的多种形式和手段,其中,象BillGates.WhoAmI()和SteveJobs.WhoAmI()这种形式,是传递this参数最正规的形式,此时的this就是函数所属的对象本身。而大多数情况下,我们也几乎很少去采用那些借花仙佛的调用形式。但只我们要明白JavaScript的这个“自我”与其他编程语言的“自我”是不同的,这是一个放下了的“自我”,这就是JavaScript特有的世界观。

对象素描

已经说了许多了许多话题了,但有一个很基本的问题我们忘了讨论,那就是:怎样建立对象?

在前面的示例中,我们已经涉及到了对象的建立了。我们使用了一种被称为JavaScript Object Notation(缩写JSON)的形式,翻译为中文就是“JavaScript对象表示法”。

JSON为创建对象提供了非常简单的方法。例如,
创建一个没有任何属性的对象:

var o = {};


创建一个对象并设置属性及初始值:

var person = {name: "Angel", age: 18, married: false};


创建一个对象并设置属性和方法:

var speaker = {text: "Hello World", say: function(){alert(this.text)}};


创建一个更复杂的对象,嵌套其他对象和对象数组等:

var company =
{
name: "Microsoft",
product: "softwares",
chairman: {name: "Bill Gates", age: 53, Married: true},
employees: [{name: "Angel", age: 26, Married: false}, {name:"Hanson", age: 32, Marred: true}],
readme: function() {document.write(this.name + "product " + this.product);}
};


JSON的形式就是用大括“{}”号包括起来的项目列表,每一个项目间并用逗号“,”分隔,而项目就是用冒号“:”分隔的属性名和属性值。这是典型的字典表示形式,也再次表明了JavaScript里的对象就是字典结构。不管多么复杂的对象,都可以被一句JSON代码来创建并赋值。

其实,JSON就是JavaScript对象最好的序列化形式,它比XML更简洁也更省空间。对象可以作为一个JSON形式的字符串,在网络间自由传递和交换信息。而当需要将这个JSON字符串变成一个JavaScript对象时,只需要使用eval函数这个强大的数码转换引擎,就立即能得到一个JavaScript内存对象。正是由于JSON的这种简单朴素的天生丽质,才使得她在AJAX舞台上成为璀璨夺目的明星。

JavaScript就是这样,把面向对象那些看似复杂的东西,用及其简洁的形式表达出来。卸下对象浮华的浓妆,还对象一个眉目清晰!

构造对象

好了,接下我们来讨论一下对象的另一种创建方法。

除JSON外,在JavaScript中我们可以使用new操作符结合一个函数的形式来创建对象。例如:

function MyFunc() {}; //定义一个空函数
var anObj = new MyFunc(); //使用new操作符,借助MyFun函数,就创建了一个对象


JavaScript的这种创建对象的方式可真有意思,如何去理解这种写法呢?

其实,可以把上面的代码改写成这种等价形式:

function MyFunc(){};
var anObj = {}; //创建一个对象
MyFunc.call(anObj); //将anObj对象作为this指针调用MyFunc函数


我们就可以这样理解,JavaScript先用new操作符创建了一个对象,紧接着就将这个对象作为this参数调用了后面的函数。其实,JavaScript内部就是这么做的,而且任何函数都可以被这样调用!但从 “anObj = new MyFunc()” 这种形式,我们又看到一个熟悉的身影,C++和C#不就是这样创建对象的吗?原来,条条大路通灵山,殊途同归啊!

君看到此处也许会想,我们为什么不可以把这个MyFunc当作构造函数呢?恭喜你,答对了!JavaScript也是这么想的!请看下面的代码:

1 function Person(name) //带参数的构造函数
2 {
3 this.name = name; //将参数值赋给给this对象的属性
4 this.SayHello = function(){alert("Hello, I'm " + this.name);}; //给this对象定义一个SayHello方法。
5 };
6
7 function Employee(name,salary) //子构造函数
8 {
9 Person.call(this, name); //将this传给父构造函数
10 this.salary = salary; //设置一个this的salary属性
11 this.ShowMeTheMoney = function() {alert(this.name + "$" + this.salary);}; //添加ShowMeTheMoney方法。
12 };
13
14 var BillGates = new Person("BillGates"); //用Person构造函数创建BillGates对象
15 var SteveJobs = new Employee("SteveJobs", 1234); //用Empolyee构造函数创建SteveJobs对象
16
17 BillGates.SayHello(); //显示:I'm Bill Gates
18 SteveJobs.SayHello(); //显示:I'm Steve Jobs
19 SteveJobs.ShowMeTheMoney(); //显示:Steve Jobs $1234
20
21 alert(BillGates.constructor == Person); //显示:true
22 alert(SteveJobs.constructor == Employee); //显示:true
23
24 alert(BillGates.SayHello == SteveJobs.SayHello); //显示:false


这段代码表明,函数不但可以当作构造函数,而且还可以带参数,还可以为对象添加成员和方法。其中的第9行,Employee构造函数又将自己接收的this作为参数调用Person构造函数,这就是相当于调用基类的构造函数。第21、22行还表明这样一个意思:BillGates是由Person构造的,而SteveJobs是由Employee构造的。对象内置的constructor属性还指明了构造对象所用的具体函数!

其实,如果你愿意把函数当作“类”的话,她就是“类”,因为她本来就有“类”的那些特征。难道不是吗?她生出的儿子各个都有相同的特征,而且构造函数也与类同名嘛!

但要注意的是,用构造函数操作this对象创建出来的每一个对象,不但具有各自的成员数据,而且还具有各自的方法数据。换句话说,方法的代码体(体现函数逻辑的数据)在每一个对象中都存在一个副本。尽管每一个代码副本的逻辑是相同的,但对象们确实是各自保存了一份代码体。上例中的最后一句说明了这一实事,这也解释了JavaScript中的函数就是对象的概念。

同一类的对象各自有一份方法代码显然是一种浪费。在传统的对象语言中,方法函数并不象JavaScript那样是个对象概念。即使也有象函数指针、方法指针或委托那样的变化形式,但其实质也是对同一份代码的引用。一般的对象语言很难遇到这种情况。

不过,JavaScript语言有大的灵活性。我们可以先定义一份唯一的方法函数体,并在构造this对象时使用这唯一的函数对象作为其方法,就能共享方法逻辑。例如:

function SayHello() //先定义一份SayHello函数代码
{
alert("Hello, I'm " + this.name);
};

function Person(name) //带参数的构造函数
{
this.name = name; //将参数值赋给给this对象的属性
this.SayHello = SayHello;//给this对象SayHello方法赋值为前面那份SayHello代码。
};

var BillGates = new Person("BillGates"); //创建BillGates对象
var SteveJobs = new Person("SteveJobs"); //创建SteveJobs对象

alert(BillGates.SayHello == SteveJobs.SayHello); //显示:true


其中,最后一行的输出结果表明两个对象确实共享了一个函数对象。虽然,这段程序达到了共享了一份方法代码的目的,但却不怎么优雅。因为,定义SayHello方法时反映不出其与Person类的关系。“优雅”这个词用来形容代码,也不知道是谁先提出来的。不过,这个词反映了程序员已经从追求代码的正确、高效、可靠和易读等基础上,向着追求代码的美观感觉和艺术境界的层次发展,程序人生又多了些浪漫色彩。

显然,JavaScript早想到了这一问题,她的设计者们为此提供了一个有趣的prototype概念。

初看原型

prototype源自法语,软件界的标准翻译为“原型”,代表事物的初始形态,也含有模型和样板的意义。JavaScript中的prototype概念恰如其分地反映了这个词的内含,我们不能将其理解为C++的prototype那种预先声明的概念。

JavaScript的所有function类型的对象都有一个prototype属性。这个prototype属性本身又是一个object类型的对象,因此我们也可以给这个prototype对象添加任意的属性和方法。既然prototype是对象的“原型”,那么由该函数构造出来的对象应该都会具有这个“原型”的特性。事实上,在构造函数的prototype上定义的所有属性和方法,都是可以通过其构造的对象直接访问和调用的。也可以这么说,prototype提供了一群同类对象共享属性和方法的机制。

我们先来看看下面的代码:

function Person(name)
{
this.name = name; //设置对象属性,每个对象各自一份属性数据
};

Person.prototype.SayHello = function() //给Person函数的prototype添加SayHello方法。
{
alert("Hello, I'm " + this.name);
}

var BillGates = new Person("BillGates"); //创建BillGates对象
var SteveJobs = new Person("SteveJobs"); //创建SteveJobs对象

BillGates.SayHello(); //通过BillGates对象直接调用到SayHello方法
SteveJobs.SayHello(); //通过SteveJobs对象直接调用到SayHello方法

alert(BillGates.SayHello == SteveJobs.SayHello); //因为两个对象是共享prototype的SayHello,所以显示:true


程序运行的结果表明,构造函数的prototype上定义的方法确实可以通过对象直接调用到,而且代码是共享的。显然,把方法设置到prototype的写法显得优雅多了,尽管调用形式没有变,但逻辑上却体现了方法与类的关系,相对前面的写法,更容易理解和组织代码。

那么,对于多层次类型的构造函数情况又如何呢?

我们再来看下面的代码:

1 function Person(name) //基类构造函数
2 {
3 this.name = name;
4 };
5
6 Person.prototype.SayHello = function() //给基类构造函数的prototype添加方法
7 {
8 alert("Hello, I'm " + this.name);
9 };
10
11 function Employee(name,salary) //子类构造函数
12 {
13 Person.call(this, name); //调用基类构造函数
14 this.salary = salary;
15 };
16
17 Employee.prototype = new Person(); //建一个基类的对象作为子类原型的原型,这里很有意思
18
19 Employee.prototype.ShowMeTheMoney = function() //给子类添构造函数的prototype添加方法
20 {
21 alert(this.name + " $" + this.salary);
22 };
23
24 var BillGates = new Person("BillGates"); //创建基类Person的BillGates对象
25 var SteveJobs = new Employee("SteveJobs", 1234); //创建子类Employee的SteveJobs对象
26
27 BillGates.SayHello(); //通过对象直接调用到prototype的方法
28 SteveJobs.SayHello(); //通过子类对象直接调用基类prototype的方法,关注!
29 SteveJobs.ShowMeTheMoney(); //通过子类对象直接调用子类prototype的方法
30
31 alert(BillGates.SayHello == SteveJobs.SayHello); //显示:true,表明prototype的方法是共享的


这段代码的第17行,构造了一个基类的对象,并将其设为子类构造函数的prototype,这是很有意思的。这样做的目的就是为了第28行,通过子类对象也可以直接调用基类prototype的方法。为什么可以这样呢?

原来,在JavaScript中,prototype不但能让对象共享自己财富,而且prototype还有寻根问祖的天性,从而使得先辈们的遗产可以代代相传。当从一个对象那里读取属性或调用方法时,如果该对象自身不存在这样的属性或方法,就会去自己关联的prototype对象那里寻找;如果prototype没有,又会去prototype自己关联的前辈prototype那里寻找,直到找到或追溯过程结束为止。

在JavaScript内部,对象的属性和方法追溯机制是通过所谓的prototype链来实现的。当用new操作符构造对象时,也会同时将构造函数的prototype对象指派给新创建的对象,成为该对象内置的原型对象。对象内置的原型对象应该是对外不可见的,尽管有些浏览器(如Firefox)可以让我们访问这个内置原型对象,但并不建议这样做。内置的原型对象本身也是对象,也有自己关联的原型对象,这样就形成了所谓的原型链。

在原型链的最末端,就是Object构造函数prototype属性指向的那一个原型对象。这个原型对象是所有对象的最老祖先,这个老祖宗实现了诸如toString等所有对象天生就该具有的方法。其他内置构造函数,如Function,Boolean, String, Date和RegExp等的prototype都是从这个老祖宗传承下来的,但他们各自又定义了自身的属性和方法,从而他们的子孙就表现出各自宗族的那些特征。

这不就是“继承”吗?是的,这就是“继承”,是JavaScript特有的“原型继承”。

“原型继承”是慈祥而又严厉的。原形对象将自己的属性和方法无私地贡献给孩子们使用,也并不强迫孩子们必须遵从,允许一些顽皮孩子按自己的兴趣和爱好独立行事。从这点上看,原型对象是一位慈祥的母亲。然而,任何一个孩子虽然可以我行我素,但却不能动原型对象既有的财产,因为那可能会影响到其他孩子的利益。从这一点上看,原型对象又象一位严厉的父亲。我们来看看下面的代码就可以理解这个意思了:

function Person(name)
{
this.name = name;
};

Person.prototype.company = "Microsoft"; //原型的属性

Person.prototype.SayHello = function() //原型的方法
{
alert("Hello, I'm " + this.name + " of " + this.company);
};

var BillGates = new Person("Bill Gates");
BillGates.SayHello(); //由于继承了原型的东西,规规矩矩输出:Hello,I'm Bill Gates

var SteveJobs = new Person("SteveJobs");
SteveJobs.company = "Apple"; //设置自己的company属性,掩盖了原型的company属性
SteveJobs.SayHello = function() //实现了自己的SayHello方法,掩盖了原型的SayHello方法
{
alert("Hi, " + this.name + " like " + this.company + ", ha ha ha ");
};

SteveJobs.SayHello(); //都是自己覆盖的属性和方法,输出:Hi, Steve Jobs likeApple, ha ha ha

BillGates.SayHello(); //SteveJobs的覆盖没有影响原型对象,BillGates还是按老样子输出


对象可以掩盖原型对象的那些属性和方法,一个构造函数原型对象也可以掩盖上层构造函数原型对象既有的属性和方法。这种掩盖其实只是在对象自己身上创建了新的属性和方法,只不过这些属性和方法与原型对象的那些同名而已。JavaScript就是用这简单的掩盖机制实现了对象的“多态”性,与静态对象语言的虚函数和重载(override)概念不谋而合。

然而,比静态对象语言更神奇的是,我们可以随时给原型对象动态添加新的属性和方法,从而动态地扩展基类的功能特性。这在静态对象语言中是很难想象的。我们来看下面的代码:

function Person(name)
{
this.name = name;
};

Person.prototype.SayHello = function() //建立对象前定义的方法
{
alert("Hello, I'm " + this.name);
};

var BillGates = new Person("BillGates"); //建立对象

BillGates.SayHello();

Person.prototype.Retire = function() //建立对象后再动态扩展原型的方法
{
alert("Poor " + this.name + ", bye bye!");
};

BillGates.Retire(); //动态扩展的方法即可被先前建立的对象立即调用


阿弥佗佛,原型继承竟然可以玩出有这样的法术!

原型扩展

想必君的悟性极高,可能你会这样想:如果在JavaScript内置的那些如Object和Function等函数的prototype上添加些新的方法和属性,是不是就能扩展JavaScript的功能呢?

那么,恭喜你,你得到了!

在AJAX技术迅猛发展的今天,许多成功的AJAX项目的JavaScript运行库都大量扩展了内置函数的prototype功能。比如微软的ASP.NET AJAX,就给这些内置函数及其prototype添加了大量的新特性,从而增强了JavaScript的功能。

我们来看一段摘自MicrosoftAjax.debug.js中的代码:

String.prototype.trim= function String$trim() {
if (arguments.length!== 0) throwError.parameterCount();
return this.replace(/^\s+|\s+$/g,'');
}


这段代码就是给内置String函数的prototype扩展了一个trim方法,于是所有的String类对象都有了trim方法了。有了这个扩展,今后要去除字符串两段的空白,就不用再分别处理了,因为任何字符串都有了这个扩展功能,只要调用即可,真的很方便。

当然,几乎很少有人去给Object的prototype添加方法,因为那会影响到所有的对象,除非在你的架构中这种方法的确是所有对象都需要的。

前两年,微软在设计AJAX类库的初期,用了一种被称为“闭包”(closure)的技术来模拟“类”。其大致模型如下:

function Person(firstName, lastName, age)
{
//私有变量:
var _firstName =firstName;
var _lastName =lastName;

//公共变量:
this.age = age;

//方法:
this.getName = function()
{
return(firstName + "" + lastName);
};
this.SayHello = function()
{
alert("Hello, I'm " + firstName + " " + lastName);
};
};

var BillGates = newPerson("Bill", "Gates", 53);
var SteveJobs = newPerson("Steve", "Jobs", 53);

BillGates.SayHello();
SteveJobs.SayHello();
alert(BillGates.getName() + " " + BillGates.age);
alert(BillGates.firstName); //这里不能访问到私有变量


很显然,这种模型的类描述特别象C#语言的描述形式,在一个构造函数里依次定义了私有成员、公共属性和可用的方法,显得非常优雅嘛。特别是“闭包”机制可以模拟对私有成员的保护机制,做得非常漂亮。

所谓的“闭包”,就是在构造函数体内定义另外的函数作为目标对象的方法函数,而这个对象的方法函数反过来引用外层外层函数体中的临时变量。这使得只要目标对象在生存期内始终能保持其方法,就能间接保持原构造函数体当时用到的临时变量值。尽管最开始的构造函数调用已经结束,临时变量的名称也都消失了,但在目标对象的方法内却始终能引用到该变量的值,而且该值只能通这种方法来访问。即使再次调用相同的构造函数,但只会生成新对象和方法,新的临时变量只是对应新的值,和上次那次调用的是各自独立的。的确很巧妙!

但是前面我们说过,给每一个对象设置一份方法是一种很大的浪费。还有,“闭包”这种间接保持变量值的机制,往往会给JavaSript的垃圾回收器制造难题。特别是遇到对象间复杂的循环引用时,垃圾回收的判断逻辑非常复杂。无独有偶,IE浏览器早期版本确实存在JavaSript垃圾回收方面的内存泄漏问题。再加上“闭包”模型在性能测试方面的表现不佳,微软最终放弃了“闭包”模型,而改用“原型”模型。正所谓“有得必有失”嘛。

原型模型需要一个构造函数来定义对象的成员,而方法却依附在该构造函数的原型上。大致写法如下:

//定义构造函数
function Person(name)
{
this.name = name; //在构造函数中定义成员
};

//方法定义到构造函数的prototype上
Person.prototype.SayHello = function()
{
alert("Hello, I'm " + this.name);
};

//子类构造函数
function Employee(name,salary)
{
Person.call(this, name); //调用上层构造函数
this.salary = salary; //扩展的成员
};

//子类构造函数首先需要用上层构造函数来建立prototype对象,实现继承的概念
Employee.prototype = new Person() //只需要其prototype的方法,此对象的成员没有任何意义!

//子类方法也定义到构造函数之上
Employee.prototype.ShowMeTheMoney = function()
{
alert(this.name + "$" + this.salary);
};

var BillGates = new Person("BillGates");
BillGates.SayHello();

var SteveJobs = new Employee("SteveJobs", 1234);
SteveJobs.SayHello();
SteveJobs.ShowMeTheMoney();

原型类模型虽然不能模拟真正的私有变量,而且也要分两部分来定义类,显得不怎么“优雅”。不过,对象间的方法是共享的,不会遇到垃圾回收问题,而且性能优于“闭包”模型。正所谓“有失必有得”嘛。

在原型模型中,为了实现类继承,必须首先将子类构造函数的prototype设置为一个父类的对象实例。创建这个父类对象实例的目的就是为了构成原型链,以起到共享上层原型方法作用。但创建这个实例对象时,上层构造函数也会给它设置对象成员,这些对象成员对于继承来说是没有意义的。虽然,我们也没有给构造函数传递参数,但确实创建了若干没有用的成员,尽管其值是undefined,这也是一种浪费啊。

唉!世界上没有完美的事情啊!

原型真谛

正当我们感概万分时,天空中一道红光闪过,祥云中出现了观音菩萨。只见她手持玉净瓶,轻拂翠柳枝,洒下几滴甘露,顿时让JavaScript又添新的灵气。

观音洒下的甘露在JavaScript的世界里凝结成块,成为了一种称为“语法甘露”的东西。这种语法甘露可以让我们编写的代码看起来更象对象语言。

要想知道这“语法甘露”为何物,就请君侧耳细听。

在理解这些语法甘露之前,我们需要重新再回顾一下JavaScript构造对象的过程。

我们已经知道,用 var anObject = new aFunction() 形式创建对象的过程实际上可以分为三步:第一步是建立一个新对象;第二步将该对象内置的原型对象设置为构造函数prototype引用的那个原型对象;第三步就是将该对象作为this参数调用构造函数,完成成员设置等初始化工作。对象建立之后,对象上的任何访问和操作都只与对象自身及其原型链上的那串对象有关,与构造函数再扯不上关系了。换句话说,构造函数只是在创建对象时起到介绍原型对象和初始化对象两个作用。

那么,我们能否自己定义一个对象来当作原型,并在这个原型上描述类,然后将这个原型设置给新创建的对象,将其当作对象的类呢?我们又能否将这个原型中的一个方法当作构造函数,去初始化新建的对象呢?例如,我们定义这样一个原型对象:

var Person = //定义一个对象来作为原型类
{
Create: function(name, age) //这个当构造函数
{
this.name = name;
this.age = age;
},
SayHello: function() //定义方法
{
alert("Hello, I'm " + this.name);
},
HowOld: function() //定义方法
{
alert(this.name + " is" + this.age + " yearsold.");
}
};

这个JSON形式的写法多么象一个C#的类啊!既有构造函数,又有各种方法。如果可以用某种形式来创建对象,并将对象的内置的原型设置为上面这个“类”对象,不就相当于创建该类的对象了吗?

但遗憾的是,我们几乎不能访问到对象内置的原型属性!尽管有些浏览器可以访问到对象的内置原型,但这样做的话就只能限定了用户必须使用那种浏览器。这也几乎不可行。

那么,我们可不可以通过一个函数对象来做媒介,利用该函数对象的prototype属性来中转这个原型,并用new操作符传递给新建的对象呢?

其实,象这样的代码就可以实现这一目标:

function anyfunc(){}; //定义一个函数躯壳
anyfunc.prototype = Person; //将原型对象放到中转站prototype
var BillGates = new anyfunc(); //新建对象的内置原型将是我们期望的原型对象

不过,这个anyfunc函数只是一个躯壳,在使用过这个躯壳之后它就成了多余的东西了,而且这和直接使用构造函数来创建对象也没啥不同,有点不爽。

可是,如果我们将这些代码写成一个通用函数,而那个函数躯壳也就成了函数内的函数,这个内部函数不就可以在外层函数退出作用域后自动消亡吗?而且,我们可以将原型对象作为通用函数的参数,让通用函数返回创建的对象。我们需要的就是下面这个形式:

function New(aClass, aParams) //通用创建函数
{
function new_() //定义临时的中转函数壳
{
aClass.Create.apply(this,aParams); //调用原型中定义的的构造函数,中转构造逻辑及构造参数
};
new_.prototype = aClass; //准备中转原型对象
return new new_(); //返回建立最终建立的对象
};

var Person = //定义的类
{
Create: function(name, age)
{
this.name = name;
this.age = age;
},
SayHello: function()
{
alert("Hello, I'm " + this.name);
},
HowOld: function()
{
alert(this.name + " is" + this.age + " yearsold.");
}
};

var BillGates =New(Person, ["Bill Gates", 53]); //调用通用函数创建对象,并以数组形式传递构造参数
BillGates.SayHello();
BillGates.HowOld();

alert(BillGates.constructor == Object); //输出:true

这里的通用函数New()就是一个“语法甘露”!这个语法甘露不但中转了原型对象,还中转了构造函数逻辑及构造参数。

有趣的是,每次创建完对象退出New函数作用域时,临时的new_函数对象会被自动释放。由于new_的prototype属性被设置为新的原型对象,其原来的原型对象和new_之间就已解开了引用链,临时函数及其原来的原型对象都会被正确回收了。上面代码的最后一句证明,新创建的对象的constructor属性返回的是Object函数。其实新建的对象自己及其原型里没有constructor属性,那返回的只是最顶层原型对象的构造函数,即Object。

有了New这个语法甘露,类的定义就很像C#那些静态对象语言的形式了,这样的代码显得多么文静而优雅啊!

当然,这个代码仅仅展示了“语法甘露”的概念。我们还需要多一些的语法甘露,才能实现用简洁而优雅的代码书写类层次及其继承关系。好了,我们再来看一个更丰富的示例吧:

//语法甘露:
var object = //定义小写的object基本类,用于实现最基础的方法等
{
isA: function(aType) //一个判断类与类之间以及对象与类之间关系的基础方法
{
var self = this;
while(self)
{
if (self == aType)
return true;
self = self.Type;
};
return false;
}
};

function Class(aBaseClass,aClassDefine) //创建类的函数,用于声明类及继承关系
{
function class_() //创建类的临时函数壳
{
this.Type = aBaseClass; //我们给每一个类约定一个Type属性,引用其继承的类
for(var member in aClassDefine)
this[member] =aClassDefine[member]; //复制类的全部定义到当前创建的类
};
class_.prototype = aBaseClass;
return new class_();
};

function New(aClass, aParams)//创建对象的函数,用于任意类的对象创建
{
function new_() //创建对象的临时函数壳
{
this.Type = aClass; //我们也给每一个对象约定一个Type属性,据此可以访问到对象所属的类
if (aClass.Create)
aClass.Create.apply(this,aParams); //我们约定所有类的构造函数都叫Create,这和DELPHI比较相似
};
new_.prototype = aClass;
return new new_();
};

//语法甘露的应用效果:
var Person =Class(object, //派生至object基本类
{
Create: function(name, age)
{
this.name = name;
this.age = age;
},
SayHello: function()
{
alert("Hello, I'm " + this.name + ", " + this.age + " years old.");
}
});

var Employee =Class(Person, //派生至Person类,是不是和一般对象语言很相似?
{
Create: function(name, age, salary)
{
Person.Create.call(this,name, age); //调用基类的构造函数
this.salary = salary;
},
ShowMeTheMoney: function()
{
alert(this.name + "$" + this.salary);
}
});

var BillGates =New(Person, ["Bill Gates", 53]);
var SteveJobs =New(Employee, ["Steve Jobs", 53, 1234]);
BillGates.SayHello();
SteveJobs.SayHello();
SteveJobs.ShowMeTheMoney();

var LittleBill =New(BillGates.Type, ["Little Bill", 6]); //根据BillGate的类型创建LittleBill
LittleBill.SayHello();

alert(BillGates.isA(Person)); //true
alert(BillGates.isA(Employee)); //false
alert(SteveJobs.isA(Person)); //true
alert(Person.isA(Employee)); //false
alert(Employee.isA(Person)); //true

“语法甘露”不用太多,只要那么一点点,就能改观整个代码的易读性和流畅性,从而让代码显得更优雅。有了这些语法甘露,JavaScript就很像一般对象语言了,写起代码了感觉也就爽多了!

令人高兴的是,受这些甘露滋养的JavaScript程序效率会更高。因为其原型对象里既没有了毫无用处的那些对象级的成员,而且还不存在constructor属性体,少了与构造函数间的牵连,但依旧保持了方法的共享性。这让JavaScript在追溯原型链和搜索属性及方法时,少费许多工夫啊。

我们就把这种形式称为“甘露模型”吧!其实,这种“甘露模型”的原型用法才是符合prototype概念的本意,才是的JavaScript原型的真谛!

想必微软那些设计AJAX架构的工程师看到这个甘露模型时,肯定后悔没有早点把AJAX部门从美国搬到咱中国的观音庙来,错过了观音菩萨的点化。当然,我们也只能是在代码的示例中,把Bill Gates当作对象玩玩,真要让他放弃上帝转而皈依我佛肯定是不容易的,机缘未到啊!如果哪天你在微软新出的AJAX类库中看到这种甘露模型,那才是真正的缘分!

编程的快乐

在软件工业迅猛发展的今天,各式各样的编程语言层出不穷,新语言的诞生,旧语言的演化,似乎已经让我们眼花缭乱。为了适应面向对象编程的潮流,JavaScript语言也在向完全面向对象的方向发展,新的JavaScript标准已经从语义上扩展了许多面向对象的新元素。与此相反的是,许多静态的对象语言也在向JavaScript的那种简洁而幽雅的方向发展。例如,新版本的C#语言就吸收了JSON那样的简洁表示法,以及一些其他形式的JavaScript特性。

我们应该看到,随着RIA(强互联应用)的发展和普及,AJAX技术也将逐渐淡出江湖,JavaScript也将最终消失或演化成其他形式的语言。但不管编程语言如何发展和演化,编程世界永远都会在“数据”与“代码”这千丝万缕的纠缠中保持着无限的生机。只要我们能看透这一点,我们就能很容易地学习和理解软件世界的各种新事物。不管是已熟悉的过程式编程,还是正在发展的函数式编程,以及未来量子纠缠态的大规模并行式编程,我们都有足够的法力来化解一切复杂的难题。

佛最后淡淡地说:只要我们放下那些表面的“类”,放下那些对象的“自我”,就能达到一种“对象本无根,类型亦无形”的境界,从而将自我融入到整个宇宙的生命轮循环中。我们将没有自我,也没有自私的欲望,你就是我,我就是你,你中有我,我中有你。这时,我们再看这生机勃勃的编程世界时,我们的内心将自然生起无限的慈爱之心,这种慈爱之心不是虚伪而是真诚的。关爱他人就是关爱自己,就是关爱这世界中的一切。那么,我们的心是永远快乐的,我们的程序是永远快乐的,我们的类是永远快乐的,我们的对象也是永远快乐的。这就是编程的极乐!

说到这里,在座的比丘都犹如醍醐灌顶,心中豁然开朗。看看左边这位早已喜不自禁,再看看右边那位也是心花怒放。

蓦然回首时,唯见君拈花微笑...

____________________________________________________________________________________________________________________________________________________________________________________________________________________________________________

分享我常使用的辅助软件

以前在园子里看过不少介绍我们开发常用的专业软件比如:新.net开发十大必备工具.NET 程序员十种必备工具。今天我不是想来介绍这些专业软件,我是想介绍一些开发时经常会用到的辅助软件,以帮助我们完成更好的开发。

我这边推荐的是我常用的辅助软件,可能并不是最好,但是我觉得确实最适合我用的!

一、浏览器

推荐:傲游浏览器http://www.maxthon.cn/

候选推荐:搜狗浏览器,火狐,chrome

作为程序员上网必备的工具,一个好的浏览器是我们获取资源的不可或缺的帮手,我这边推荐傲游并不是因为傲游有用强大的程序调试或着丰富的插件,而是傲游收藏及日常应用方面。

傲游的收藏是我觉得比较好的功能,只要注册个账号就可以把本地收藏同步到网络服务器上,在日常上网的过程中,我们经常会看到好的文章想收藏起来,虽然很多网站都提供了收藏功能,但是这个网站收藏是建立在你必须打开这个网站,登录上去才能看到,而且收藏的信息只能是这个网站的(好像有点废话了)。傲游的收藏可以同步,这样就保证了哪怕本机的收藏丢失,也可以通过帐户登录重新找回。

而且傲游的账户也不仅仅有同步收藏的功能,我们在日常使用的过程中总会有自己的浏览器习惯(常用网站,鼠标手势等),这些习惯如果登录了账户傲游都会帮你记住,我觉得是一个非常人性化的功能。同时傲游作为微软Win7推荐的浏览器,其功能也是可以保证的。

这边候选的还有搜狗浏览器,火狐,chrome,搜狗虽然说是安全性很高及浏览速度快,但是相比傲游也没多少出色的地方。至于火狐和chrome,这2款肯定是程序员必装的2款,但是我不推荐的原因是因为这2款浏览器本身的功能相比傲游并不强劲,需要靠各种各样的插件来辅助,但是一旦装了各式插件就会导致浏览器启动速度加慢等问题,所以我的建议日常浏览信息用傲游,如果需要进行程序开发才用火狐或chrome。

二、记事本

推荐:notepad2http://www.onlinedown.net/soft/31490.htm

候选推荐:EmEditor,notepad++

对于我们开发人员来说经常会要进行编写东西,这个时候我们不会打开VS,eclipse这些专业的开发工具,我们会选择记事本这种小巧的工具来进行简单的代码编写,我这边推荐的是notepad2,主要有以下几个理由:

1、代码高亮,notepad2支持目前所有主流的开发语言,C#、VB.NET、PHP、JSP、HTML等,而且可以自由更改代码高亮的配置

2、完全替代系统自带记事本,windows自带的记事本功能实在是比较糟糕,仅仅能够提供基本的编辑功能(不愧为记事本),而notepad2可以完美的替换windows默认的记事本(仅有一个执行程序+一个配置文件),不仅因为它拥有系统自带记事本的所有功能,还因为他优越的性能、强大的功能和小巧的体积,例如打开几M甚至更大的文本不会像自带的记事本那样卡死,无法响应等情况、可以使用ctrl+鼠标滚轮进行文本放大缩小等等。

具体替换方式可以看这里

3、优秀的文本编辑环境,notepad2不仅仅是提供代码高亮,其文本背景,段落缩进,多编码支持都是程序员日常编写简单代码,简单文本不可缺少的工具。

但是notepad2还是有缺点的,它不支持代码的自动提示,不过这个也是它的好处,如果要支持代码提示那整个程序的体积也不会那么小巧了。

除了notepad2,我还推荐了EmEditor,notepad++,EmEditor也在我电脑里装了,虽然可以靠插件来提升其自身的功能(如代码提示,自动完成,HTML预览等等),但是因为插件装多了整个程序启动过慢,而且太多的插件实在是让人“眼花缭乱”。

而notepad++,功能要比notepad2强大,但是因为功能强大导致其体积大,相关附属程序也多,有点“累赘”的感觉。

三、搜索工具

推荐:everythinghttp://dl.pconline.com.cn/html_2/1/93/id=1769&pn=0.html

候选推荐:windows自带的搜索

我不太清楚别的开发人员电脑里面有多少东西,但是我很清楚我电脑里有多少东西,整个电脑上百G的空间了堆满了各种各样的东西,如果突然想起来要找个东西又不记得原来放哪了,靠windows自带的搜索工具估计你吃完饭回来还没找出来,就算找出来可能也不是你想要的,这里我就推荐everything,一款非常优秀的搜索工具,同时还很小巧就几百K。

在每次使用前everything会扫描你整个电脑,将所有数据进行索引更新保存到sqlite数据库中,只要搜索关键字就可以进行全盘扫描(同时还支持正则表达式),几秒内立刻就可以找到你所需要找到的东西。

同时everything还可以作为一个小型的服务器,你可以把你电脑做为一个服务器,让别人来下载东西。

虽然everything可以很方便的帮你搜索的符合关键字的文件,但是要搜索文本中的内容,它就办不到了,这时候你需要使用别的工具了。

体的信息可以到这里查看点我进入

至于候选推荐的windows自带的搜索我就不介绍了,大家都知道。不过win7的搜索还是很不错的。

四、思维分析

推荐:Mindjet MindManagerhttp://www.onlinedown.net/soft/82527.htm

候选推荐:Visio

在开发的时候需要长时间的思考,但是思考是一件很费事的事情,很容被打断,被干扰,导致思考得重新来过,同时在思考的时候光凭大脑思考很容易混乱,很多人在这个时候总会将大脑中思考的东西用笔和纸记录下来,虽然起到了辅助思考的作用,但是却因为思考的时候随性而写无法与人交流,这边我就推荐了Mindjet MindManager,这款软件叫思维导图。

Mindjet MindManager可以很方便的帮助你将思考的信息按照顺序或逻辑记录下来,同时其可以和office进行无缝集成(如word,PowerPoint,Excel,Outlook,Project),进行辅助记录,而且由于是软件,在你通过它来记录你思考的东西时候,都是以图形化进行记录,这样当你思考完后就可以将这个图保存成图片格式与其他人一起沟通了。

至于候选推荐的Visio,虽然它也可以很容易的帮助我们来进行问题分析,但是操作起来远远没有MindjetMindManager那么简单,Visio过于专业性了。

五、快捷键工具

推荐:AutoHotKeyhttp://www.onlinedown.net/soft/39219.htm

候选推荐:slickrun、Add2Run

作为一个长期通过键盘工作的程序员,我们总希望能更快速的进行操作,来节省时间,比如我,我更希望通过键盘来完成操作,因为我觉得将双手离开键盘,再通过鼠标来操作有点浪费时间,所以我宁愿在脑子里记一大堆的快捷键,可惜这些快捷键只能在某个软件中适用,而在系统环境下我想快速的打开我经常用的软件就无法实现了,这时我就是用了快捷键软件,这边我推荐的是AutoHotKey,一个通过脚本来实现快捷功能

(当然它还有其他很强大的功能,我以前写过相关的文章:AutoHotKey-更加快速的操作,使用自己编写脚本让你的鼠标休息下

AutoHotKey的好处就是可以通过编程的手段来实现许多功能,我可以将许多常用的快捷键都编写好统一放到一个文件里,进行代码编译成exe,在每次开机后进行自己所编写的exe,这样所需要的快捷键都已经潜在运行了,这样我们就可以通过键盘来快速启动程序了(当然我们还可以做其他许多事情)。

候选推荐的slickrun、Add2Run是2款拥有UI的快捷键设定程序,操作起来更为方便,但是我认为AutoHotKey可以做更多的事,所以推荐AutoHotKey。

有关slickrun、Add2Run相关的信息可以看:这里

以上就是我常用的开发辅助软件了,推荐的都是我认为好的,可能与大家想的不同,欢迎大家讨论,介绍下自己常用的辅助软件,分享给其他人!

________________________________________________________________________________________________________________________________________________________________net中怎样在前台用JS验证checkboxlist不为空

2008-9-28 12:48

提问者:xufeng586

我有个页面要求的效果是当我点击保存按钮时,会验证我页面的checkboxlist选择是否为空,要是空就给保存,所以想问问大虾们,怎样在.net中 前台用JS验证checkboxlist不为空

2008-9-28 13:22

最佳答案

HTML页面中并没有CheckBoxList控件, 假设你的CheckBoxList的ID为chkList,  那它到客户端的时候就会变成
 
<table id="chkList" border="0">
 <tr>
  <td><input id="chkList_0" type="checkbox" name="chkList$0" /><label for="chkList_0">1</label></td>
 </tr><tr>
  <td><input id="chkList_1" type="checkbox" name="chkList$1" /><label for="chkList_1">2</label></td>
 </tr><tr>
  <td><input id="chkList_2" type="checkbox" name="chkList$2" /><label for="chkList_2">3</label></td>
 </tr>
</table>
 
 那么你应该用以下这个函数判断:
 
function IsListCheck()
{
   var lst = document.getElementById('<%= chkList.ClientID%>').getElementsByTagName('input');
   var isCheck = false;
   if(lst.length>0) 
     for(var i=0;i<lst.length;i++)
     {
       if(lst[i].checked) isCheck=true;
       continue;
     }
   return isCheck;
}
祝你成功。

_____________________________________________________________________________________________________________________________________________________________________________________________________________________________________________

级光棍节,你想好怎么过了吗?来聆听大师的演讲吧 参加浏览器性能挑战赛免费去美国!

ASP.NET图片验证码的实现

2011-04-1213:357人阅读评论(0)收藏举报

ASP.NET 图文 验证码

虽然我认为图片验证码没有什么用,并且也特别的烦人(每次上移动的网站都要频频地输入验证码),但人家要求,也只好弄一个。

生成图片验证码页面createImg.aspx,验证页面Default.aspx。

CreateImg.aspx页面使用的各个函数如下:

stringgetRandomValidate(int len) 得到随机长度为len的字符串

drawLine(Graphicsgfc,Bitmap img) 在图片中画底线

drawPoint(Bitmapimg) 在图片中画杂点(移动画的杂点挺好不错)

getImageValidate(stringstrValue) 使用getRandomValidate函数返回的字符串生成图片

其具体实现代码如下所示:

using System.Drawing;
using System.IO;

public partialclass createImg : System.Web.UI.Page
{
Random ran = new Random();
protected voidPage_Load(object sender, EventArgs e)
{
string str = getRandomValidate(4);
Session["check"] = str; //这一步是为了将验证码写入Session,进行验证,不能缺省,也可一使用cookie
getImageValidate(str);
}
//得到随机字符串,长度自己定义
private string getRandomValidate(int len)
{
int num;
int tem;
string rtuStr = "";
for (int i = 0; i < len; i++)
{
num = ran.Next();
/*
* 这里可以选择生成字符和数字组合的验证码
*/
tem = num % 10 + '0';//生成数字
//tem = num % 26 + 'A';//生成字符
rtuStr += Convert.ToChar(tem).ToString();
}
return rtuStr;
}
//生成图像
private void getImageValidate(string strValue)
{
//string str = "OO00"; //前两个为字母O,后两个为数字0
int width = Convert.ToInt32(strValue.Length * 12); //计算图像宽度
Bitmap img = new Bitmap(width, 23);
Graphics gfc = Graphics.FromImage(img); //产生Graphics对象,进行画图
gfc.Clear(Color.White);
drawLine(gfc, img);
//写验证码,需要定义Font
Font font = new Font("arial", 12, FontStyle.Bold);
System.Drawing.Drawing2D.LinearGradientBrush brush =
new System.Drawing.Drawing2D.LinearGradientBrush(new Rectangle(0, 0, img.Width,img.Height), Color.DarkOrchid, Color.Blue, 1.5f, true);
gfc.DrawString(strValue, font, brush, 3, 2);
drawPoint(img);
gfc.DrawRectangle(new Pen(Color.DarkBlue), 0, 0, img.Width - 1, img.Height -1);
//将图像添加到页面
MemoryStream ms = new MemoryStream();
img.Save(ms, System.Drawing.Imaging.ImageFormat.Gif);
//更改Http头
Response.ClearContent();
Response.ContentType = "image/gif";
Response.BinaryWrite(ms.ToArray());
//Dispose
gfc.Dispose();
img.Dispose();
Response.End();
}
private void drawLine(Graphics gfc,Bitmap img)
{
//选择画10条线,也可以增加,也可以不要线,只要随机杂点即可
for (int i = 0; i < 10; i++)
{
int x1 = ran.Next(img.Width);
int y1 = ran.Next(img.Height);
int x2 = ran.Next(img.Width);
int y2 = ran.Next(img.Height);
gfc.DrawLine(new Pen(Color.Silver), x1, y1, x2, y2); //注意画笔一定要浅颜色,否则验证码看不清楚
}
}
private void drawPoint(Bitmap img)
{
/*
//选择画100个点,可以根据实际情况改变
for (int i = 0; i < 100; i++)
{
int x = ran.Next(img.Width);
int y = ran.Next(img.Height);
img.SetPixel(x,y,Color.FromArgb(ran.Next()));//杂点颜色随机
}
*/
int col = ran.Next();//在一次的图片中杂店颜色相同
for (int i = 0; i < 100; i++)
{
int x = ran.Next(img.Width);
int y = ran.Next(img.Height);
img.SetPixel(x, y, Color.FromArgb(col));
}
}
}

如何使用:

新建一个页面,至少需要一个文本框、image控件和一个Button控件。需要注意的是要将image控件的imageUrl指定为createImg.aspx,如下所示

<asp:ImageID="Image1" runat="server"ImageUrl="~/createImg.aspx" />

其测试就很简单了,就像平时使用Session一样,饿,还记得createImg页面中有一个Session["check"]吗?该页面已经注册了一个会话,那么在Default页面中只需简单的比较以下就可以了。

protected voidPage_Load(object sender, EventArgs e)
{
if (Session["check"] == null)
message.Text = "sorry,the image is wrong!";
}
protected void checkButton_Click(object sender, EventArgs e)
{
if (check.Text.ToString() == Session["check"].ToString())
message.Text = "Yes,you pass it";
else
message.Text = "I'm sorry!try again...";
}

测试效果如下图所示:

 

最后结合自己的测试,补充如下:

要想实现刷新验证码而不刷新整个页面,需要在文件中加入:

 

<ahref="javascript:ChangeImage();"><span style="color:#0000ff">看不清</span></a>。

 

然后在<head>里加入如下js代码(当然加在js文件里更好):

 

function ChangeImage()
{
document.getElementById("Image1").src = document.getElementById("Image1").src+'?';
}

 

测试效果如下图所示:

 

_____________________________________________________________________________________________________________________________________________________________________________________________________________________________________________

ASP.NET中如何使用前台和后台验证

分类: ASP.NET2011-04-1115:30184人阅读评论(0)收藏举报

最近自学ASP.NET中,遇到一些很基础的问题先记下来,以后也可以复习一下。

刚看了篇文章,讲了后台验证的必要性(《为什么不能只用前台js验证[必须结合后台验证]》 原文博客:http://www.52rs.net/ArticleView.aspx?gID=ac085c49-339b-4315-9447-98e78ac64ae3),既然有了后台验证,为什么还要进行前台验证,这样做不是让程序更复杂了?这个问题先放着,待深入学习之后慢慢了解。

开始进入正题。

前台一般用js验证,比如验证表单的数据:

<scripttype="text/javascript">
function checkForm()
{
var _uid=document.getElementById("<%=txt_Id.ClientID%>").value;
var _upwd=document.getElementById("<%=txt_Pwd.ClientID%>").value;
var _upwdSure=document.getElementById("<%=txt_Pwd_Sure.ClientID%>").value;
var _uemail=document.getElementById("<%=txt_Email.ClientID%>").value;

if(_uid =="")
{
alert("账号不能为空!");
return false;
}
//验证密码与确认密码是否一致
if (_upwd != _upwdSure) {
alert("密码与确认密码不一致");
return false;

}
//验证密码 这里的正则表达式的判断方法跟c#不一样 请注意:/正则表达式 /g g表示全部查找
var reg = /^[a-zA-Z0-9]{6,20}$/g;
if (!reg.test(_upwd))
{
alert("密码长度必须大于6个字符小于20个字符,只能为英语字、数字,例如:snsn2003等");
return false;
}
//验证电子邮件
reg = //w+([-+.]/w+)*@/w+([-.]/w+)*/./w+([-.]/w+)*/g;
if (!reg.test(_uemail))
{
alert("请输入有效的邮件地址");
return false;
}
return true;
}
</script>

这段代码写在<head>里,并在相应事件里加上<OnClientClick="returncheckForm()">。这里要说明一下,OnClientClick=""指的是客户端的前台验证方法,后台验证使用οnclick="",比如:<asp:Button runat="server" οnclick="后台方法" onClientClick="reutrn 前台方法()"/>。前台方法必须返回一个true或者false。true会继续执行后台代码;false不会继续执行后台代码。此方法在ff和IE7中测试通过,还有人说ie6不支持此方法,要写成onClientClick="event.returnValue=true"这样才可以,这个没试过,也是从网上看到的,先记在这里,以备日后查看。

后台验证也大同小异:

//从前台获取值
string uid = txt_Id.Text;
string upwd = txt_Pwd.Text;
string uemail = txt_Email.Text;
string upwdSure = txt_Pwd_Sure.Text;

//--------------------------------------------------------开始验证
//验证输入是否完整
if (uid.Length == 0 || upwd.Length == 0 || uemail.Length == 0 ||upwdSure.Length==0)
{
Response.Write("<script language='JavaScript'>alert('输入不完整')</script>");
return;
}
//验证密码与确认密码是否一致
if (!upwd.Equals(upwdSure))
{
Response.Write("<script language='JavaScript'>alert('后台验证:密码与确认密码不一致')</script>");
return;
}
//验证电子邮件
if (!Regex.IsMatch(uemail,@"/w+([-+.]/w+)*@/w+([-.]/w+)*/./w+([-.]/w+)*"))
{
Response.Write("<script language='JavaScript'>alert('请输入有效的邮件地址')</script>");
return;
}
//--------------------------------------------------------验证结束

前后台的验证方法介绍完了,这个例子里面并没有实现图片验证码的功能,所以没有图片验证码的验证,其方法以后会补充。

希望大家共同学习!

加油!

_____________________________________________________________________________________________________________________________________________________________________________________________________________________________________________---光棍节,你想好怎么过了吗?来聆听大师的演讲吧 参加浏览器性能挑战赛免费去美国!

JSP中如何使用JQuery?

分类: JQuery2011-04-1321:00352人阅读评论(0)收藏举报

首先要导入JQuery的包文件,可以在这里下载。

然后在JSP中引入包:

viewplaincopy toclipboardprint?

1. <mce:script src="lib/jquery-1.5.2.js" mce_src="lib/jquery-1.5.2.js" type="text/javascript"></mce:script>

接下来再引入自己的js:

viewplaincopy toclipboardprint?

1. <mce:script src="my.js" mce_src="my.js" type="text/javascript"></mce:script>

我的my.js文件内容如下:

viewplaincopy toclipboardprint?

1. $(document).ready(function() {

2. $("<div><p>Hello</p></div>").appendTo("body");

3.  

4. $("#stop").css("background","yellow");

5.  

6. $("li").toggle(

7. function () {

8. $(this).css({"list-style-type":"disc", "color":"blue"});

9. },

10.function () {

11.$(this).css({"list-style-type":"disc", "color":"red"});

12.},

13.function () {

14.$(this).css({"list-style-type":"disc", "color":"gray"});

15.}

16.);

17.});

测试页面MyJQueryTest.html的内容如下:

viewplaincopy to clipboardprint?

1. <html xmlns="http://www.w3.org/1999/xhtml">

2. <head>

3. <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />

4. <title>My jQuery Test</title>

5. <mce:script src="lib/jquery-1.5.2.js" mce_src="lib/jquery-1.5.2.js" type="text/javascript"></mce:script>

6. <mce:script src="my.js" mce_src="my.js" type="text/javascript"></mce:script>

7. </head>

8. <body>

9. <button>Change colors</button>

10.<br/>

11.<span>span</span>

12.<div>div1</div>

13.<div>div2</div>

14.<div>div3</div>

15.<div>div4</div>

16.<div id="stop">Stop here</div>

17.<div>div6</div>

18.<div>div7</div>

19.<div>div8</div>

20.<br/>

21.<ul>

22.<li>li1</li>

23.<li>li2</li>

24.<li>li3</li>

25.<li>li4</li>

26.<li>li5</li>

27.</ul>

28.</body>

29.</html>

运行效果如下:

前:

后:

最后值得注意的是,JQuery的包文件的引入必须在js引入的前面,不然JQuery没有效果。

)____________________________________________________________________________________________________________________________________________________________---光棍节,你想好怎么过了吗?来聆听大师的演讲吧 参加浏览器性能挑战赛免费去美国!

ASP.NET(C#)中如何获取IP,名称,操作系统,浏览器等信息

分类: ASP.NET2011-04-0819:28135人阅读评论(0)收藏举报

客户端ip:
Request.ServerVariables.Get("Remote_Addr").ToString();
客户端主机名:
Request.ServerVariables.Get("Remote_Host").ToString();
客户端浏览器IE:
Request.Browser.Browser;
客户端浏览器 版本号:
Request.Browser.MajorVersion;//
客户端操作系统:
Request.Browser.Platform;
服务器ip:
Request.ServerVariables.Get("Local_Addr").ToString();
服务器名:
Request.ServerVariables.Get("Server_Name").ToString();
如果你想进一步了解ServerVariables,可以用
foreach(String o in Request.ServerVariables){
Response.Write(o+"="+Request.ServerVariables[o]+"<br>");
}
=================================================
Request.ServerVariables("Url")
返回服务器地址
Request.ServerVariables("Path_Info")
客户端提供的路径信息
Request.ServerVariables("Appl_Physical_Path")
与应用程序元数据库路径相应的物理路径
Request.ServerVariables("Path_Translated")
通过由虚拟至物理的映射后得到的路径
Request.ServerVariables("Script_Name")
执行脚本的名称
Request.ServerVariables("Query_String")
查询字符串內容
Request.ServerVariables("Http_Referer")
请求的字符串內容
Request.ServerVariables("Server_Port")
接受请求的服务器端口号
Request.ServerVariables("Remote_Addr")
发出请求的远程主机的IP地址
Request.ServerVariables("Remote_Host")
发出请求的远程主机名称
Request.ServerVariables("Local_Addr")
返回接受请求的服务器地址
Request.ServerVariables("Http_Host")
返回服务器地址
Request.ServerVariables("Server_Name")
服务器的主机名、DNS地址或IP地址
Request.ServerVariables("Request_Method")
提出请求的方法比如GET、HEAD、POST等等
Request.ServerVariables("Server_Port_Secure")
如果接受请求的服务器端口为安全端口时,则为1,否则为0
Request.ServerVariables("Server_Protocol")
服务器使用的协议的名称和版本
Request.ServerVariables("Server_Software")
应答请求并运行网关的服务器软件的名称和版本
Request.ServerVariables("All_Http")
客户端发送的所有HTTP标头,前缀HTTP_
Request.ServerVariables("All_Raw")
客户端发送的所有HTTP标头,其结果和客户端发送时一样,没有前缀HTTP_
Request.ServerVariables("Appl_MD_Path")
应用程序的元数据库路径
Request.ServerVariables("Content_Length")
客户端发出內容的长度
Request.ServerVariables("Https")
如果请求穿过安全通道(SSL),则返回ON如果请求来自非安全通道,则返回OFF
Request.ServerVariables("Instance_ID")
IIS实例的ID号
Request.ServerVariables("Instance_Meta_Path")
响应请求的IIS实例的元数据库路径
Request.ServerVariables("Http_Accept_Encoding")
返回內容如:gzip,deflate
Request.ServerVariables("Http_Accept_Language")
返回內容如:en-us
Request.ServerVariables("Http_Connection")
返回內容:Keep-Alive
Request.ServerVariables("Http_Cookie")
返 回內容如:nVisiT%2DYum=125;ASPSESSIONIDCARTQTRA=FDOBFFABJGOECBBKHKGPFIJI;ASPSESSIONIDCAQQTSRB=LKJJPLABABILLPCOGJGAMKAM;ASPSESSIONIDACRRSSRA=DKHHHFBBJOJCCONPPHLKGHPB
Request.ServerVariables("Http_User_Agent")
返回內容:Mozilla/4.0(compatible;MSIE6.0;WindowsNT5.1;SV1)
Request.ServerVariables("Https_Keysize")
安全套接字层连接关键字的位数,如128
Request.ServerVariables("Https_Secretkeysize")
服务器验证私人关键字的位数如1024
Request.ServerVariables("Https_Server_Issuer")
服务器证书的发行者字段
Request.ServerVariables("Https_Server_Subject")
服务器证书的主题字段
Request.ServerVariables("Auth_Password")
当使用基本验证模式时,客户在密码对话框中输入的密码
Request.ServerVariables("Auth_Type")
是用户访问受保护的脚本时,服务器用於检验用户的验证方法
Request.ServerVariables("Auth_User")
代证的用户名
Request.ServerVariables("Cert_Cookie")
唯一的客户证书ID号
Request.ServerVariables("Cert_Flag")
客户证书标誌,如有客户端证书,则bit0为0如果客户端证书验证无效,bit1被设置为1
Request.ServerVariables("Cert_Issuer")
用户证书中的发行者字段
Request.ServerVariables("Cert_Keysize")
安全套接字层连接关键字的位数,如128
Request.ServerVariables("Cert_Secretkeysize")
服务器验证私人关键字的位数如1024
Request.ServerVariables("Cert_Serialnumber")
客户证书的序列号字段
Request.ServerVariables("Cert_Server_Issuer")
服务器证书的发行者字段
Request.ServerVariables("Cert_Server_Subject")
服务器证书的主题字段
Request.ServerVariables("Cert_Subject")
客户端证书的主题字段
Request.ServerVariables("Content_Type")
客户发送的form內容或HTTPPUT的数据类型

_______________________________________________________________________________________________________________________________________________________________级光棍节,你想好怎么过了吗?来聆听大师的演讲吧 参加浏览器性能挑战赛免费去美国!

如何使用Javascript调用后台数据之实例方法详解

分类: Javascript2011-04-2916:0495人阅读评论(0)收藏举报

1. javaScript函数中执行C#代码中的函数:

方法一:1、首先建立一个按钮,在后台将调用或处理的内容写入button_click中;
2、在前台写一个js函数,内容为document.getElementById("btn1").click();
3、在前台或后台调用js函数,激发click事件,等于访问后台c#函数;

方法二:1、函数声明为public
后台代码(把public改成protected也可以)
public string ss()
{
return("a");
}

2、在html里用<%=fucntion()%>可以调用
前台脚本
<script language=javascript>
var a = " <%=ss()%>";
alert(a);
</script>

方法三:1、 <scriptlanguage="javascript">
<!--
function __doPostBack(eventTarget, eventArgument)
{
var theForm = document.Form1; //指runat=server的form
theForm.__EVENTTARGET.value = eventTarget;
theFrom.__EVENTARGUMENT.value = eventArgument;
theForm.submit();
}
-->
</script>
<input type="button" value="按钮" >



方法四: <script language="javascript">
function SubmitKeyClick()
{
if (event.keyCode == 13)
{
event.cancelBubble = true;
event.returnValue = false;
document.all.FunName.value="你要调用的函数名";
document.form[0].submit();
}
}
< /script>
< INPUT type="text">
< input type="hidden" > 〈!--用来存储你要调用的函数 --〉

在.CS里有:
public Page_OnLoad()

{
if (!Page.IsPost())
{
stringstrFunName=Request.Form["FunName"]!=null?Request.Form["FunName"]:"";
//根据传回来的值决定调用哪个函数
switch(strFunName)
{
case "enter()":
enter() ; //调用该函数
break;
case "其他":
//调用其他函数
break;
default:
//调用默认函数
break;
}
}
}
public void enter()
{
//……比如计算某值
}

2.如何在JavaScript访问C#变量?
答案如下:
方法一:1、通过页面上隐藏域访问 <inputtype="hidden" runat="server">
方法二:1、如后台定义了PUBLIC STRING N;前台js中引用该变量的格式为' <%=n%>'或" <%=n%> "
方法三:1、或者你可以在服务器端变量赋值后在页面注册一段脚本
" <script language='javascript'>var temp=" tmp "</script>"
tmp是后台变量,然后js中可以直接访问temp获得值。
3.如何在C#中访问JavaScript的已有变量?
答案如下:
方法一:1、前台使用静态文本控件隐藏域,将js变量值写入其中;
2、后台用request["id"]来获取值;
方法二:可以用cookie或session

4.如何在C#中访问JavaScript函数?
答案如下:
c#代码中执行javaScript函数:
方法一:1、Page.RegisterStartupScript("ggg","<script>SetVisible(1); </script>");
方法二:使用Literal类,然后
private void Button2_Click(object sender, System.EventArgs e)
{
string str;
str=" <script language='javascript'>";
str ="selectRange()";
str =" </script>";
//Literal1.Visible=true;
Literal1.Text=str;
}

____________________________________________________________________________________________________________________________________________________________________——————————————————————————————————————————————教你如何在三个月内获得三年的工作经验

分类: 生活杂谈2011-09-2119:2350人阅读评论(0)收藏举报

一篇很不错的文章,它告诉我们,只要方法正确,3个月能做的事很多,并且,最重要的是:不仅要学,而且要习,那样才能真正有用。转贴此文过来,供大家共同学习,分享!

  很多职场新人都谈到了工作经验的问题,似乎招聘公司不给你机会,你就没办法获得必要的工作经验,其实并不一定。很多资料在网上都是可以找到的,只是看你具备不具备足够的信息收集与处理能力,而这个收集与处理信息的过程,也能极大的提升你的职业能力。

  我一直有个感觉,在“模仿中成长,在创新中成功”,其实在真正的职业工作中,大多数的工作都是模仿重复,强调的是工作效率,而不是创新。对于企业而言,过度的创新必然导致过多的失败,以及效率的低下。以下方式是我的成长中曾经做过的,也是我用来训练新员工的方案。你们也可以试试。

  看到很多谈应聘技巧的帖子,其实并不实用,有菜谱并不代表能做出好菜,能不能做出好菜仍要看你天天炒,日日炒,炒出来的本事。所以,我这里要强调的一点是,你收集到的任何资料都不能只是看看,而必须自己手把手,动手去整理、去归类,去建立新的结构,这个信息收集与处理的过程甚至比你最后总结成文的文字更重要。

  何谓“学习”?学习学习,学而习,习而成习惯。光学不习,那知识还只是书上的,老师教的,不是你自己的,只有你重复练习了,经过量变,才会有质变,当你形成条件反射时,你就真正掌握这个东西了。

  这个过程需要维持两至三个月的时间,一定要坚持下去,你会看到自己的变化的。否则,你会用你最青春的两三年来慢慢沉淀出这些你两三个月就能掌握的东西。一切一切,其实,你们比的不是其它的东西,只是比的速度。 这也是为什么我那么强调基本功的原因。

  1. 职业分析:

  A. 分析性格——分析长处和短处——分析大家都有的长处——确定自己最终发展的专业

  B. 确定兴趣——分析竞争的激烈程度和发展的空间大小——寻找相对优势—确定自己最终进入的行业

  C. 确定行业内自己的专业方向,继续保持自身的专业优势。

  2. 编写行业报告——着重对行业全面性的把握。

  A. 通过上网查询和购买行业报刊,收集不少于三十万字的行业、重点企业的有效资料,在电脑中进行资料分析、分类、汇总。

  B. 参考同类行业书籍,确定写作提纲,确定文章结构和逻辑方向,培养文字表达能力和逻辑能力,以及熟练的电脑使用技能。

  C. 将三十万字资料浓缩成十至十五万字,写成一本符合出版行文格式要求的行业报告。如果选题好,还真的有出版的可能性。如果有一定的独特见解,也可以写成文章争取在专业刊物上发表,树立个人专业形象。

  3. 编写讲座报告——着重对专业系统性的把握。

  A. 根据你希望从事的专业岗位,从报告中选择两到三个重点,将书稿压缩成两万字的讲座稿(按每分钟150字的演讲速度,即两个小时)。

  B. 将演讲稿再浓缩成两千字的提纲和重要内容,使用PPT软件编成演讲用演示文件,并根据相关内容配以精彩图片。

  C. 培养职业化的公众表达能力和表达方式,练习普通话,使用讲座稿进行互动讲座和演讲练习,只到脱口而出。

  告诉大家两个名人是这么成长的.一个是教英语的李阳,他读大学时成绩不好,英语不及格,然后他做什么去了?他跑到没人的地方大声喊英语去了.一个是做广告的叶茂中,他卖广告卖不出去了,他跑回家写书.别人看到的和他自己说的是拿着书出版出了名,发达了.其实做过这个事的人才会知道,当他把这本书写出来时,能不能出版已经不重要的,因为他知道他变化了.

  我当时也是没办法了,把所有的钱买了台电脑,在家里做了三个月这个事,三个月后的变化是惊人的,我的父母、我兼职的公司的老总,最重要的是我自己,都感觉到了自己的变化。完全不同了。其实我写的已经不是理论了,其实什么都没有技巧的,只是多看书,然后多做,硬磕,坚持下去,刚开始觉得没变化,没感觉,很累,坚持不下去,然后做着做着,就越来越快了,然后慢慢的有变化.

  而且有意思的是,我在家呆了三个月,做的事其实根本与我所从事的工作没有一点关系.只是这三个月的训练,对于我的逻辑、结构、全局性、文字表达能力、口头表达能力有了极大的提升。

  至于收入翻5翻,当年一个月也就八百块钱,然后做完这个训练后整个人的状态都变了,有自信了,然后写了一个方案去应聘,结果进了一家大公司。

  当然,开始我还不想去,因为对方只给我800/月,还要自己租房子,吃饭,觉得不好,但是对方连续四个月三次打电话找我,于是我去了,结果去了就后悔了,真正好的公司根本不在乎工资的,重要的是你自己的能力。

  第一个月,我就挣了八千块,我以前想都不敢想的。然后两个月就转了正,而有一个有关系的同事,呆了一年还没能转正。然后每个月的收入超过工资几倍,还有年终奖两万,出国旅游,其实也不累,我到这个家公司的同时,还到另一家广告公司兼职,呵呵,很回忆的过去。

  现在看到太多的人谈工资,我确实不喜欢,我这几年都不和老板谈工资的,因为说出来好笑,帐面工资高了,还要多扣税.我只在意公司的分配方式,怎么样算提成和奖金,年薪.

  上个月有一个和我同龄的名牌大学MBA来我现在所在的小公司应聘,不愿意和人事小姐谈,老板不在,我就来谈了。

  我说好呀,以你的资历我不能和你谈给谁做副手的问题了,我跟你谈谈公司的分配方式吧,其实我们公司普通员工的收入都不高的,长沙平均水平,只是不忙,周末休两天,工作满一年还有一个星期的年休假。

  但是公司几个部门负责人还是有钱的,象我三十岁,一年18万左右的年薪,其它的我就不清楚了,有几个我一个星期才见一次的,比我还小,只怕拿得比我还多.你应该也是这样的吧.

  他要求6千一个月的月薪,我说这倒不重要,重要的是公司不会给你安排业务的,你自己找业务回来,公司给你平台,给你配团队,能挣多少钱是你的本事.

  我说完了,问:你有什么想法吗?他说没想法,起身走人.

  太有意思了,你在长沙想拿六千一个月,你等别人找事给你做,你为什么不能自己找到项目呀?六千是底薪呀,差不多7万2千的底薪,如果是这样的,那我自己算我应该拿到二十五万以上的年薪了.

  从来拿底薪和拿年薪的人就是不一样的. 如果你不敢拿年薪,你就不要想着谈什么老板给你少了. 企业是要盈利的,资本家是要剥削的.问题是,如果你是一个真正能创造价值的人,你自己所创造的价值你是可以拿到手的.

  大学毕业生,如果什么经验也没有,只有知识,没有技能,能找到一个给你几百块钱,让你在这里呆着学东西的企业就应该感谢了,如果你觉得这种企业不是你所向往的,你在上大学时就老老实实努力学,少玩,多练.我工作有一个总结,钱永远不会是目标,但是它会是结果.

  谈到职业规划,有人说过职业可以规划的,我也相信未来可以计划的,问题是,你是不是这个能不能计划出你未来的人,以及,你身边有没有熟悉你的高人指点,如果没有,那你自己都不会明白你自己的未来是什么的,就象象你去做所谓的性向测试,说不定是你自己在自欺欺人了,这种事多了,没人会把自己算成一个坏人的。

  所以重要的还是那一句话,复杂的生活简单过,简单的事情重复做。

  你是中文系的,如果你的年纪还不是很大,建议你凭你自己的能力,哪怕是工资少点,你都要进最好的广告公司,去呆上一年半载,按我说的方法偷师,基本能力提升了,慢慢的你会遇到一些贵人的,还有你会涉及一些行业,慢慢的,你会发觉你内心深处喜欢的行业。

  呵呵,特别是哦,女孩子,只有努力才能进大公司,只有进了大公司才能遇到优秀的男生。好男生都关在写字楼里上班下班加班的,呵呵。生活圈子都小的,你选择的工作圈在你努力的阶段就是你的生活圈。

  在你的成长过程中,有五个人非常重要。

  第一个,导师,教练。

  他教给你实用的技巧、一定的工作经验,而不是知识。他可以给你指明方向。这个人可能是你的上司、前辈、学长。

  第二个,陪练,同路人。

  任何人的成长都不是学出来的,而是学而习,习而成习惯,练出来的。在这个练的过程中,是一件很苦的过程,是一系列简单动作的重复重复再重复,由量变到质变的过程,在这个过程中,一个人很难坚持下来,这时你需要一个同路人。他可以是和你共同兴趣,共同目标的朋友,最好是你生命中所爱的人。

  第三个,榜样,他是你人生的标杆。

  在你一生中,在不同阶段,会有不同的标杆,你向他学习,受他鼓舞,一步一步向他靠扰。最重要的是那个你看得到摸得着的人,你知道,不需要通过机遇,只需要通过努力就可以达到的榜样。

  第四个,敌人,看不起你的人,拒绝过你的人。

  人不到绝境是不会有斗志的,你要证明他是错的,他会给你真正的动力。

  第五个,最重要的是第五个,你们觉得第五个人是你自己。

  世界上没有救世主,任何希望当别人救世主的人不是疯子就是傻子,只有自己才可以救自己。  这个世界上,失败的人除了天分太差之外,只有以下几点,懒,方向不对,方法不对,没有坚持。  如果你自己做不到,你不要怪别人。 基本功是你自己的,细节所积累下来的,能让你迅速融入新环境.

  不知道怎么跟大家谈基本功这个问题.很多东西大家都没把它当基本功了.比如说,我想要的人,他打字很快,他很少很少写错别字,有丰富的词汇量,逻辑很清晰,用词很准确,这些看上去难不难?但是在我这两年见过的应聘的策划文案来看,只有两个人做到了.一个是做了三年文案的女孩子,慢慢磨的.一个是中文硕士生,还没毕业.

  其实大学到底教给大家什么了?知识?

  大学阶段必须打好你的基本功,这些决定了你就业后的学习能力,阶层简单工作的工作效率.

  如果谁还说打字、排版是文员做的事,那只能说他是真正不明白真正的职场需要。

  你们在大学所学到的知识,都是同质化的了,如果将知识变为通用的、标准化的技能才是重要的。

  既然学的东西没用,那在大学还要不要认真学习呢?

  当然要,因为这些东西是系统性的,这个学习过程能培养你的学习能力。知识不能改变你的命运了,但是它可以改变你的气质。如果你读个四年大学出来,你的气质还不能好一点,那你的大学就真的白读了。

  经常有人在问面试穿什么衣服呀?穿什么衣服重要吗?重要的是什么人在穿这些衣服。重要的是你的精气神,你的气质。

  有一天有一个应聘文案的来了,我叫设计总监先和他聊聊。聊完了,我说这个人不行吧,设计总监说为什么?我说我们调性不符,我们多少都有点书卷气,而他是一脸的江湖气。果然,呵呵。

  招聘方当然是要看应聘者的外形条件的,但并不是丑的就不招,重要的是能力和你的气质,是不是符合公司要求的。 重要的是兴趣。然后是狂练基本功,简单重复积累。 学打拳,你先站三个月桩再说。

  面对新人,我说很多东西,你会发现,每个字你都认识,每句话你都看得懂,但是你理解吗?领悟,是教不了的。自己努力吧,自己重复做,再会明白自己最想要的是什么。

  你考公员员如果死活考不上,那你应该去想想,这种机械性的考试你都过不了,那是不是学习方法,或者兴趣不对呀?做销售,同样的,从基阶做起吧。你的财政学对你有没有帮助?当然有,你对销售的认识会不同的。

  象十年前我卖保险,人人都跟银行比,算利息,都算得没有银行高,只能说死了人有赔了。而我是怎么算呢?我用递增,还是增减年金公式算,呵呵,比银行高呢。另外,别人说死了人有赔,最多是说得婉转点。我可没把它当死人卖呀,我把它当礼物卖,当成父母送给孩子的礼物卖,卖得可好了,呵呵。现在哪个做人寿险的人敢说他一年做两百多单?呵呵,我好象一年做了二百四十单左右,全是年缴哦。

  这个世界上最穷的和最富的人都在做销售.做销售的人底薪很低的,大多数人拼的只是体力罢了,如果你想做好,你多花心思就可以了.多想多跑,还是在一个行业里多坚持,找到高手做师父带你.

  我说说当年我混日子的时候怎么过来的.那年头电脑还紧俏,我只要一有机会就到别人电脑上练东西,终于练成了今天的电脑基本功,一方面要多学,一方面要多用心.然后,我每天做记录,记下工作的流程,记下别人说过的工作中重要的话,其实什么叫行业经验?很多老手随便说的话,都是行话了,有它的意思的,听了就要想,就要去查,很多东西就知道了为什么要记录,因为什么叫职业化?职业化就是标准化、流程化,模式化,你多看多记多想就能明白了,这些东西在很多地方都是通用的。

  有一点,如果这里收入还可以的话,你好好学吧,任何工作都要呆一两年,你才会有认识的,跳来跳去的对你不好,真的,你还在磨性情的时候,只要你保持学习的能力,别下班玩去了就可以了,有压力才有动力,好好留心心仪的公司招聘的要求,按那个要求去做一个一年的训练与学习计划,一年后,那个公司在等你。。。

________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________ASP.NET 页面间传递参数的方法

分类: ASP.NET2011-04-0814:1025人阅读评论(0)收藏举报

转自: http://www.cnblogs.com/eoiioe/archive/2008/04/08/1142247.html

  这个新特性意味着ASP.NET2.0开发人员目前有三种可供选择 的技术来将数据从一个web页面传送到另外一个页面。这三种方法是:响应重定向,服务端传输和新的跨网页提交特性。我们可以已经熟悉前两种技术了,因此, 我们只是简要地复习一下它们,然后我们会将主要精力放到学习如何使用跨网页提交特性,以及阐述一下这种方法和响应重定向以及服务传输方式有什么不同。

  一、响应重定向方法

  响应重定向方法是目前为止将一个网页重定向到另一个网页的最简单的 方法的最简单的方法。当Web服务器接到一个重定向请求后,它会将一个响应头送给客户端,这将导致客户端发送一个新的请求到服务器。按句话说,一个重定向 请求实际上是两个请求响应:一个是最初的请求响应,另一个是新的重定向请求响应。

  在ASP.NET中实现重定向很容易。下面的代码演示了如何使用Response.Redirect方法实现网页重定向:

   protected void Redirect_Click(object sender, EventArgs e)
  {
   Response.Redirect("menu.aspx");
  }

要注意的是重定向请求只是一个GET请求,这就意味着我们不能从源页中通过重定向命令提交数据。但是我们可以在重定向中使用查询字符串来传递数据。如下面代码所示:

   protected void Redirect_Click(object sender, EventArgs e)
  {
Response.Redirect("menu.aspx?userName=" + UserName.Text));
}


  上面的例子将一个查询字符串作为参数传递给了Response.Redirect方法的目标URL。我们可以通过如下的代码获得源数据。

protected void Page_Load(object sender, EventArgs e)
  {
string userName = Request["userName"];
}

二、服务器传输方法

  和依赖于客户端向另一个新页发请求不同,服务器传输是一种服务器重 定向技术,这种技术通过简单的改变Web服务器所处理的代码来达到请求一个新页的目的。当被请求页和源页面在同一个服务器时,服务器传输要比Response.Redirect方法更有效,这是由于这种技术可以避免额外的开销,仅仅使用服务器的资源就可以进行重定向。要注意的这种技术有一个副 作用,在页面进行重定向时,客户端的URL仍然会保持源页面的URL,这可能会使客户认为他们所获得的数据是源页面产生的。当然,在大多数情况下,这不是 问题,但是这将使调试变得更困难。

  Server.Transfer方法还可以保存初始页的HttpContext。因此,目标页可以访问源页面的值。我们可以使用FormsCollection属性来从目标页面中获得源页面的值。首先,要确定 我们使用了被重载的方法,这个方法有两个参数:目标URL和一个Boolean类型的值,告诉服务器是否保存用于描述源页面值的Form。如下面的代码所 示:

  Server.Transfer("Menu.aspx",true);
  然后,我们在目标页面中获得一个叫txtUserName的Textbox控件的值的代码如下:
   object obj =Request.Form["txtUserName"];

  三、Response.Redirect和Server.Transfer的比较

  由于Response.Redirect方法要进行两次请求响应操 作,因此,我们应该在对性能要求高的网站尽量避免使用这种方法。然而,只从技术上说,使用redirect方式确实可以从一个网页跳到另一个网页。相比之 下,Server.Transfer会更有效率,但是跳转的范围仅限于同一个Web服务器的不同网页。从本质上说,我们可以使用Server.Transfer来消除不必要的请求响应操作。如果我们需要重定位到不同服务器的网页,就需要使用Response.Redirect方 法。

  四、跨页提交概述

  在ASP.NET 2.0中,我们可以通过实现IbuttonControl接口提交给不同的WebForm,来实现跨网页的提交。和Response.Redirect类 似,跨网页提交是一个基于客户端的传输机制,但也有点象Server.Transfer,目标网页也可以访问源网页的数据。为了使用跨网页提交,我们需要 在源网页中的PostBackUrl属性中指定目标URL。

  五、实现跨网页提交

  这部分将讨论一下如何在ASP.NET2.0中实现跨网页提交。为 了开始我们的学习,假设有两个Web页,一个是源Web页,另一个是目标Web页。在源网页中初始化了使用按钮进行的跨网页提交操作。我们首先必须设置目 标网页按钮的PostBackUrl属性,顺便说一句,所有实现了System.Web.UI.WebControls.IbuttonControl接 口的Web控件都有跨网页提交的特性。下面的代码将演示这一过程。

  PostBackUrl="~/target.aspx" text = "Post to a targetpage"/>
  当我们设置PostBackUrl属性时,ASP.NET框架将相应的控件绑定到一个新的叫WebForm_DoPostBackWithOptions的JavaScript函数,产生的Html代码如下:

  οnclick="javascript:WebForm_DoPostBackWithOptions(newWebForm_PostBackOptions("btnSubmit", "", false,"","Target.aspx", false, false))"id="btnSubmit" />

  对于上面的html代码来说,当用户单击按钮时,浏览器将提交目标URL(Target.aspx),而不是源URL。

六、从目标页面中获得源页面控件的值

  ASP.NET2.0提供了一个叫PreviousPage的新的 属性,这个属性无论在何时当前页面进行跨网页提交操作时都会指向源页面。要注意的是,当源页面和目标页面在不同的应用程序中时,这个属性包含null(这 个null并不是未初始化的意思)。还有要注意的是当目标网页访问PreviousPage属性时可以获得源页面的数据,ASP.NET运行时装载并执行 了源页面。这将引发ProcessChildRequest事件的发生。而且,它还会引发Page_Init事件、Page_Load和任何其他的源页面 按钮单击事件。

  因此,我们要避免由于不小心进行误操作,所以最好通过IsCrossPostBack属性来确认是否为一个跨网页提交发生,如果这个属性值为true,那么目标网页就是通过一个跨网页提交动作而调用的。如果 是通过另外一种方式调用的(如一般的请求、Response.Redirect或是一个Server.Transfer),这个属性的值为false。下 面的例子演示了如何使用这个属性。

if ( PreviousPage.IsCrossPagePostBack)
  {
   //执行代码
  }
//******************************************
  这个PreviousPage属性在Server.Transfer和跨网页提交中都可以使用。在ASP.NET2.0中,我们可以在调用Server.Transfer操作后使用PreviousPage属性在目标页面中来获得源页面的数据,代码如下:
*******************************************//
   {
   Server.Transfer("menu.aspx");
   }

  protected void Redirect_Click(object sender, EventArgs e)

  //在这个接收面中我们现在可以获得Web页面的数据,代码如下:

   protected void Page_Load(object sender, EventArgs e)
  {
   if (PreviousPage != null)
   {
   TextBox txtBox = (TextBox)
   PreviousPage.FindControl("txtUserName");
   if (textBox != null)
   string userName = textBox.Text;
   //其他可执行的代码
   }
  }


  要注意的是上面的代码必须将txtUserName控件转换为TextBox类型,以便可以访问其中的值。

七、使用PreviousPageType

  PreviousPageType属性提供了在跨网页操作中访问源页面的强类型能力,下面让我们演示一下如何不通过任何类型转换来从源页面中获得控件值。代码如下:

  < asp:TextboxID="txtUserName" Runat="server" />
  < asp:TextboxID="txtPassword" Runat="server" />
< asp:Button ID="Submit" Runat="server"Text="Login"PostBackUrl="Menu.aspx" />

  要注意的是单击按钮可以重定向到一个叫“Menu.asp”的目标页。这个目标页可以使用如下的代码获得用户名和密码:

  八、保存视图状态

  对于跨网页提交来说,ASP.NET2.0 内嵌了一个叫__POSTBACK的隐藏字段,这个字段包含了关于源页面的视图信息 -- 也就是由源页面提供了,包含了一个带有一个非空PostBackUrl属性值的服务端控件。目标页可以使用__POSTBACK中的信息来获得源页面的视 图状态信息。代码如下:

   if(PreviousPage!=null && PreviousPage.IsCrossPagePostBack &&
  PreviousPage.IsValid)
  {
TextBoxtxtBox = PreviousPage.FindControl("txtUserName");
   Response.Write(txtBox.Text);
}

  在上面的代码中核对了用于确保PreviousPage属 性不为null的检查代码。顺便提一下,如果目标页和源页面不在同一个应用程序中,这个PreviousPage属性的值为null。只有在进行跨网页提 交操作时,IsCrossPagePostBack属性才为true。

  这个跨网页提交的特性,是ASP.NET2.0中最强有力的特性之一,这种技术将允许在一个页面中提交到另外一个页面,并且可以在目标页面地无缝地操作源页面中的数据。

 

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页