[转载]经过一年时间的沉淀 再次回首 TCP Socket服务器编程

mikel阅读(1190)

[转载]经过一年时间的沉淀 再次回首 TCP Socket服务器编程 – 美丽人生 – 博客园.

——————

前言

——————

开发了这么多年,发现最困难的程序开发就是通讯系统。

其他大部分系统,例如CRM/CMS/权限框架/MIS之类的,无论怎么复杂,基本上都能够本地代码本地调试,性能也不太重要。(也许这个就 是.net的企业级开发的战略吧)

可是来到通讯系统,一切变得困难复杂。原因实在太多了,如:

  • 性能永远是第一位:有时候一个if判断都要考虑性能,毕竟要损耗一个CPU指令,而在通讯系统服务 器,每秒钟都产生上百万级别的通讯量,这样一个if就浪费了1个毫秒了。
  • 系统环境极其恶劣:所有我 们可以想象的恶意攻击、异常输入等都要考虑;
  • 网络说断就断:在socket环境下,客户端可以以各 种理由断开链接,而且服务器根本不会知道,连一个流水作业的业务逻辑都无法保证正常执行,因此需要设计各种辅助的协议、架构去监督。
  • 各 种网络链接问题:例如代理、防火墙等等。。。

经过了1年的跌跌撞撞,我总算收获了点有用的经验,本文先从设计角度介绍一些我在Socket编程中的经验,下一篇在放出源代码。

——————

现有的Socket编程资源

——————

1. 首选推荐开源的XMPP框架,也就是Google的Gtalk的开源版本。里面的架构写的非常漂亮。特点就是:简洁、清晰。

2. 其次推荐LumaQQ.net,这套框架本身写的一般般,但是腾讯的服务器非常的猛,这样必然导致客户端也要比较猛。通过学习这套框架,能够了解腾讯的 IM传输协议设计,而且他们的协议是TCP/UDP结合,一举两得。

3. 最后就是DotMsn。这个写的实在很一般般,而且也主要针对了MSN的协议特点。是能够学习到一点点的框架知识的,不过要有所鉴别。

——————

Socket的选择

——————

在Java,到了Java5终于出现了异步编程,NIO,于是各种所谓的框架冒了出来,例如MINA, xsocket等等;而在.NET,微软一早就为我们准备好了完善的Socket模型。主要包括:同步Socket、异步Socket;我还听说 了.net 3.x之后,异步的Socket内置了完成端口。综合各种模型的性能,我总结如下:

1. 如果是短链接,使用同步socket。例如http服务器、转接服务器等等。

2. 如果是长链接,使用异步socket。例如通讯系统(QQ / Fetion)、webgame等。

3. .net的异步socket的连接数性能在 7500/s(每秒并发7500个socket链接)。而听说完成端口在1.5w所有。但是我到目前还没有正式见过所谓的完成端口,不知道到底有多牛逼。

4. 我听说了java的NIO性能在5000/s所有,我们项目内部也进行了链接测试,在4000~5000比较稳定,当然如果代码调优之后,能提高一点点。

——————

TCP Socket协议定义

——————

本文从这里开始,主要介绍TCP的socket编程。

新手们(例如当初的我),第一次写socket,总是以为在发送方压入一个”Helloworld”,接收方收到了这个字符串,就“精通”了 Socket编程了。而实际上,这种编程根本不可能用在现实项目,因为:

1. socket在传输过程中,helloworld有可能被拆分了,分段到达客户端),例如 hello   +   world,一个分段就是一个包(Package),这个就是分包问题

2. socket在传输过成功,不同时间发送的数据包有可能被合并,同时到达了客户端,这个就是黏包问题。例如发送方发送了 hello+world,而接收方可能一次就接受了helloworld.

3. socket会自动在每个包后面补n个 0x0 byte,分割包。具体怎么去补,这个我就没有深入了解。

4. 不同的数据类型转化为byte的长度是不同的,例如int转为byte是4位(int32),这样我们在制作socket协议的时候要特别小心了。具体可 以使用以下代码去测试:

代码

public void test()
{
int myInt = 1;
byte[] bytes = new byte[1024];
BinaryWriter writer
= new BinaryWriter(new MemoryStream(bytes));
writer.Write(myInt);
writer.Write(
j);
writer.Close();
}

尽管socket环境如此恶劣,但是TCP的链接也至少保证了:

  • 包发送顺序在传输过程中是不会改变的,例如发送方发送 H E L L,那么接收方一定也是顺序收到H E L L,这个是TCP协议承诺的,因此这点成为我们解决分包、黏包问题的关键。
  • 如果发送方发送的是helloworld, 传输过程中分割成为hello+world,那么TCP保证了在hello与world之间没有其他的byte。但是不 能保证helloworld和下一个命令之间没有其他的byte。

因此,如果我们要使用socket编程,就一定要编写自己的协议。目前业界主要采取的协议定义方式是:包头+包体长度+包体。 具体如下:

1. 一般包头使用一个int定义,例如int = 173173173;作用是区分每一个有效的数据包,因此我们的服务器可以通过这个int去切割、合并包,组装出完整的传输协议。有人使用回车字符去分割 包体,例如常见的SMTP/POP协议,这种做法在特定的协议是没有问题的,可是如果我们传输的信息内容自带了回车字符串,那么就糟糕了。所以在设计协议 的时候要特别小心。

2. 包体长度使用一个int定义,这个长度表示包体所占的比特流长度,用于服务器正确读取并分割出包。

3. 包体就是自定义的一些协议内容,例如是对像序列化的内容(现有的系统已经很常见了,使用对象序列化、反序列化能够极大简化开发流程,等版本稳定后再转入手 工压入byte操作)。

一个实际编写的例子:比如我要传输2个整型 int = 1, int = 2,那么实际传输的数据包如下:

173173173               8                  1         2

|——包头——|—-包体长度—-|——–包体——–|

这个数据包就是4个整型,总长度 = 4*4  = 16。

说说我走的弯路:

我曾经偷懒,使用特殊结束符去分割包体,这样传输的数据包就不需要指名长度了。可是后来高人告诉我,如果使用特殊结束符去判断包,性能会损失很大, 因为我们每次读取一个byte,都要做一次if判断,这个性能损失是非常严重的。所以最终还是走主流,使用以上的结构体。

——————

Socket接收的逻辑概述

——————

针对了我们的数据包设计+socket的传输特点,我们的接收逻辑主要是:

1. 寻找包头。这个包头就是一个int整型。但是写代码的时候要非常注意,一个int实际上占据了4个byte,而可悲的是这4个byte在传输过程中也可能 被socket 分割了,因此读取判断的逻辑是:

  • 判断剩余长度是否大于4
  • 读取一个int,判断是否包头,如果是就跳出循环。
  • 如果不是包头,则倒退3 个byte,回到第一点。
  • 如果读取完毕也没有找到,则有可能包头被分割了,因此当前已读信息压入接收缓存,等待下一个包到达后合并判 断。

2. 读取包体长度。由于长度也是一个int,因此判断的时候也要小心,同上。

3. 读取包体,由于已知包体长度,因此读取包体就变得非常简单了,只要一直读取到长度未知,剩余的又回到第一条寻找包头。

这个逻辑不要小看,就这点东西忙了我1天时间。而非常奇怪的是,我发现C#写的socket,似乎没有我说的这么复杂逻辑。大家可以看看 LumaQQ.net / DotMsn等,他们的socket接收代码都非常简单。我猜想:要么是.net的socket进行了优化,不会对int之类的进行分割传输;要么就是作 者偷懒,随便写点代码开源糊弄一下。

——————

Socket服务器参数概述

——————

我在开篇也说了,Socket服务器的环境是非常糟糕了,最糟糕的就是客户端断线之后服务器没有收到通知。 因为socket断线这个也是个信息,也要从客户端传递到我们socket服务器。有可能网络阻塞了,导致服务器连断开的通知都没有收到。

因此,我们写socket服务器,就要面对2个环境:

1. 服务器在处理业务逻辑中的任何时候都会收到Exception, 任何时候都会因为链接中断而断开。

2. 服务器接收到的客户端请求可以是任意字符串,因此在处理业务逻辑的时候,必须对各种可能的输入都判断,防止恶意攻击。

针对以上几点,我们的服务器设计必须包含以下参数:

1. 客户端链接时间记录:主要判断客户端空连接情况,防止连接数被恶意占用。

2. 客户端请求频率记录:要防止客户端频繁发送请求导致服务器负荷过重。

3. 客户端错误记录:一次错误可能导致服务器产生一次exception,而这个性能损耗是非常严重的,因此要严格监控客户端的发送协议错误情况。

4. 客户端发送信息长度记录:有可能客户端恶意发送非常长的信息,导致服务器处理内存爆满,直接导致宕机。

5. 客户端短时间暴涨:有可能在短时间内,客户端突然发送海量数据,直接导致服务器宕机。因此我们必须有对服务器负荷进行监控,一旦发现负荷过重,直接对请求 的socket返回处理失败,例如我们常见的“404”。

6. 服务器短时间发送信息激增:有可能在服务器内部处理逻辑中,突然产生了海量的数据需要发送,例如游戏中的“群发”;因此必须对发送进行队列缓存,然后进行 合并发送,减轻socket的负荷。

——————

后记

——————

本文从架构设计分析了一个socket服务器的设计要点。如果您有其他见解,欢迎留言与讨论。


我们的最新动态 (Bamboo@pixysoft.net)

  • 1.大城小格服务器升级到G级别.空间3G.数据库1G.为下一步高级应用..[2010-7-4]
  • 2. 基于socket的通讯架构成功集成到信息流服务器.替换第三方的即时通..[2010-6-15]
  • 3.Socket服 务器第一阶段开发完成.即时通讯即将全面移植到内部即时通..[2010-6-14]
  • 我们每天都在努力着!

作者:美丽人生
技术支持:reborn_zhang@hotmail.com

[转载]策略模式-5

mikel阅读(997)

[转载]策略模式-5 – 云飞龙行 – 博客园.

3.4  策略模式结合模板方法模式

在实际应用策略模式的过程中,经常会出现这样一种情况,就是发现这一系列算法的实现上存在公共功能,甚至这一系列算法的实现步骤都是一样的,只是在某些局 部步骤上有所不同,这个时候,就需要对策略模式进行些许的变化使用了。
对于一系列算法的实现上存在公共功能的情况,策略模式可以有如下三种实现方式:

  • 一个是在上下文当中实现公共功能,让所有具体的策略算法回调这些方法。
  • 另外一种情况就是把策略的接口改成抽象类,然后在里面实现具体算法的公共功能。
  • 还有一种情况是给所有的策略算法定义一个抽象的父类,让这个父类去实现策略的接口,然后在这个父类里面去实现公共的功能。

更进一步,如果这个时候发现“一系列算法的实现步骤都是一样的,只是在某些局部步骤上有所不同”的情况,那就可以在这个抽象类里面定义算法实现的骨架,然 后让具体的策略算法去实现变化的部分。这样的一个结构自然就变成了策略模式来结合模板方法模式了,那个抽象类就成了模板方法模式的模板类。
在上一章我们讨论过模板方法模式来结合策略模式的方式,也就是主要的结构是模板方法模式,局部采用策略模式。而这里讨论的是策略模式来结合模板方法模式, 也就是主要的结构是策略模式,局部实现上采用模板方法模式。通过这个示例也可以看出来,模式之间的结合是没有定势的,要具体问题具体分析。
此时策略模式结合模板方法模式的系统结构如下图5所示:

图5  策略模式结合模板方法模式的结构示意图
还是用实际的例子来说吧,比如上面那个记录日志的例子,如果现在需要在所有的消息前面都添加上日志时间,也就是说现在记录日志的步骤变成了:第一步为日志 消息添加日志时间;第二步具体记录日志。
那么该怎么实现呢?
(1)记录日志的策略接口没有变化,为了看起来方便,还是示 例一下,示例代码如下:

/**

* 日志记录策略的接口

*/

public interface LogStrategy {

/**

* 记录日志

* @param msg 需记录的日志信息

*/

public void log(String msg);

}

(2)增加一个实现这个策略接口的抽象类,在里面定义记录日志的算法骨架,相当于模板方法模式的模板,示例代码如下:

/**

* 实现日志策略的抽象模板,实现给消息添加时间

*/

public abstract class LogStrategyTemplate implements LogStrategy{

public final void log(String msg) {

//第一步:给消息添加记录日志的时间

DateFormat df = new SimpleDateFormat(

“yyyy-MM-dd HH:mm:ss SSS”);

msg = df.format(new java.util.Date())+” 内容是:”+ msg;

//第二步:真正执行日志记录

doLog(msg);

}

/**

* 真正执行日志记录,让子类去具体实现

* @param msg 需记录的日志信息

*/

protected abstract void doLog(String msg);

}

(3)这个时候那两个具体的日志算法实现也需要做些改变,不再直接实现策略接口了,而是继承模板,实现模板方法了。这个时候记录日志到数据库的类, 示例代码如下:

/**

* 把日志记录到数据库

*/

public class DbLog extends LogStrategyTemplate{

//除了定义上发生了改变外,具体的实现没变

public void doLog(String msg) {

//制造错误

if(msg!=null && msg.trim().length()>5){

int a = 5/0;

}

System.out.println(“现在把 ‘”+msg+”‘ 记录到数据库中”);

}

}

同理实现记录日志到文件的类如下:

/**

* 把日志记录到数据库

*/

public class FileLog extends LogStrategyTemplate{

public void doLog(String msg) {

System.out.println(“现在把 ‘”+msg+”‘ 记录到文件中”);

}

}

(4)算法实现的改变不影响使用算法的上下文,上下文跟前面一样,示例代码如下:

/**

* 日志记录的上下文

*/

public class LogContext {

/**

* 记录日志的方法,提供给客户端使用

* @param msg 需记录的日志信息

*/

public void log(String msg){

//在上下文里面,自行实现对具体策略的选择

//优先选用策略:记录到数据库

LogStrategy strategy = new DbLog();

try{

strategy.log(msg);

}catch(Exception err){

//出错了,那就记录到文件中

strategy = new FileLog();

strategy.log(msg);

}

}

}

(5)客户端跟以前也一样,示例代码如下:

public class Client {

public static void main(String[] args) {

LogContext log = new LogContext();

log.log(“记录日志”);

log.log(“再次记录日志”);

}

}

运行一下客户端再次测试看看,体会一下,看看结果是否带上了时间。
通过这个示例,好好体会一下策略模式和模板方法模式的组合使用,在实用开发中是很常见的方式。

3.5  策略模式的优缺点

  • 定义一系列算法
    策略模式的功能就是定义一系列算法,实现让这些算法可以相互替换。所以会为这一系列算法定义公共的接口,以约束一系列算法要实现的功能。如果这一系列算法 具有公共功能,可以把策略接口实现成为抽象类,把这些公共功能实现到父类里面,对于这个问题,前面讲了三种处理方法,这里就不罗嗦了。
  • 避免多重条件语句
    根据前面的示例会发现,策略模式的一系列策略算法是平等的,可以互换的,写在一起就是通过if-else结构来组织,如果此时具体的算法实现里面又有条件 语句,就构成了多重条件语句,使用策略模式能避免这样的多重条件语句。
    如下示例来演示了不使用策略模式的多重条件语句,示例代码如下:
public class OneClass {

/**

* 示范多重条件语句

* @param type 某个用于判断的类型

*/

public void oneMethod(int type){

//使用策略模式的时候,这些算法的处理代码就被拿出去,

//放到单独的算法实现类去了,这里就不再是多重条件了

if(type==1){

//算法一示范

//从某个地方获取这个s的值

String s = “”;

//然后判断进行相应处理

if(s.indexOf(“a”) > 0){

//处理

}else{

//处理

}

}else if(type==2){

//算法二示范

//从某个地方获取这个a的值

int a = 3;

//然后判断进行相应处理

if(a > 10){

//处理

}else{

//处理

}

}

}

}

  • 更好的扩展性
    在策略模式中扩展新的策略实现非常容易,只要增加新的策略实现类,然后在选择使用策略的地方选择使用这个新的策略实现就好了。
  • 客户必须了解每种策略的不同
    策略模式也有缺点,比如让客户端来选择具体使用哪一个策略,这就可能会让客户需要了解所有的策略,还要了解各种策略的功能和不同,这样才能做出正确的选 择,而且这样也暴露了策略的具体实现。
  • 增加了对象数目
    由于策略模式把每个具体的策略实现都单独封装成为类,如果备选的策略很多的话,那么对象的数目就会很可观。
  • 只适合扁平的算法结构
    策略模式的一系列算法地位是平等的,是可以相互替换的,事实上构成了一个扁平的算法结构,也就是在一个策略接口下,有多个平等的策略算法,就相当于兄弟算 法。而且在运行时刻只有一个算法被使用,这就限制了算法使用的层级,使用的时候不能嵌套使用。
    对于出现需要嵌套使用多个算法的情况,比如折上折、折后返卷等业务的实现,需要组合或者是嵌套使用多个算法的情况,可以考虑使用装饰模式、或是变形的职责 链、或是AOP等方式来实现。

3.6  思考策略模式

1:策略模式的本质
策略模式的本质:分离算法,选择实现
仔细思考策略模式的结构和实现的功能,会发现,如果没有上下文,策略模式就回到了最基本的接口和实现了,只要是面向接口编程的,那么就能够享受到接口的封 装隔离带来的好处。也就是通过一个统一的策略接口来封装和隔离具体的策略算法,面向接口编程的话,自然不需要关心具体的策略实现,也可以通过使用不同的实 现类来实例化接口,从而实现切换具体的策略。
看起来好像没有上下文什么事情,但是如果没有上下文,那么就需要客户端来直接与具体的策略交互,尤其是当需要提供一些公共功能,或者是相关状态存储的时 候,会大大增加客户端使用的难度。因此,引入上下文还是很必要的,有了上下文,这些工作就由上下文来完成了,客户端只需要与上下文交互就可以了,这样会让 整个设计模式更独立、更有整体性,也让客户端更简单。
但纵观整个策略模式实现的功能和设计,它的本质还是“分离算法,选择实现”,因为分离并封装了算法,才能够很容易的修改和添加算法;也能很容易的动态切换 使用不同的算法,也就是动态选择一个算法来实现需要的功能了。
2:对设计原则的体现
从设计原则上来看,策略模式很好的体现了开-闭原则。策略模式通过把一系列可变的算法进行封装,并定义出合理的使用结构,使得在系统出现新算法的时候,能 很容易的把新的算法加入到已有的系统中,而已有的实现不需要做任何修改。这在前面的示例中已经体现出来了,好好体会一下。
从设计原则上来看,策略模式还很好的体现了里氏替换原则。策略模式是一个扁平结构,一系列的实现算法其实是兄弟关系,都是实现同一个接口或者继承的同一个 父类。这样只要使用策略的客户保持面向抽象类型编程,就能够使用不同的策略的具体实现对象来配置它,从而实现一系列算法可以相互替换。

3:何时选用策略模式
建议在如下情况中,选用策略模式:

  • 出现有许多相关的类,仅仅是行为有差别的情况,可以使用策略模式来使用多个行为中的一个来配置一个类的方法,实现算法动态切换
  • 出现同一个算法,有很多不同的实现的情况,可以使用策略模式来把这些“不同的实现”实现成为一个算法的类层次
  • 需要封装算法中,与算法相关的数据的情况,可以使用策略模式来避免暴露这些跟算法相关的数据结构
  • 出现抽象一个定义了很多行为的类,并且是通过多个if-else语句来选择这些行为的情况,可以使用策略模式来代替这些条件语句

3.7  相关模式

  • 策略模式和状态模式
    这两个模式从模式结构上看是一样的,但是实现的功能是不一样的。
    状态模式是根据状态的变化来选择相应的行为,不同的状态对应不同的类,每个状态对应的类实现了该状态对应的功能,在实现功能的同时,还会维护状态数据的变 化。这些实现状态对应的功能的类之间是不能相互替换的。
    策略模式是根据需要或者是客户端的要求来选择相应的实现类,各个实现类是平等的,是可以相互替换的。
    另外策略模式可以让客户端来选择需要使用的策略算法,而状态模式一般是由上下文,或者是在状态实现类里面来维护具体的状态数据,通常不由客户端来指定状 态。
  • 策略模式和模板方法模式
    这两个模式可组合使用,如同前面示例的那样。
    模板方法重在封装算法骨架,而策略模式重在分离并封装算法实现。
  • 策略模式和享元模式
    这两个模式可组合使用。
    策略模式分离并封装出一系列的策略算法对象,这些对象的功能通常都比较单一,很多时候就是为了实现某个算法的功能而存在,因此,针对这一系列的、多个细粒 度的对象,可以应用享元模式来节省资源,但前提是这些算法对象要被频繁的使用,如果偶尔用一次,就没有必要做成享元了。

[转载]前端开发必须知道的JS(二) 闭包及应用

mikel阅读(910)

[转载]前端开发必须知道的JS(二) 闭包及应用 – JayChow – 博客园.

前端开发必须知道的JS(一) 原型和继承一文中说过下面写篇闭包, 加之最近越来越发现需要加强我的闭包应用能力,所以此文不能再拖了。本文讲的是函数闭包,不涉及对象闭包(如用with实现)。如果你觉得我说的有偏差, 欢迎拍砖,欢迎指教。

一. 闭包的理论

首先必须了解以下几个概念:

执行环境

每调用一个函数时(执行函数时),系统会为该函数创建一个封闭的局部的运行环境,即该函数的执行环境。函数总是在自己的执行环境中执行,如读写 局部变量、函数参数、运行内部逻辑。创建执行环境的过程包含了创建函数的作用域,函数也是在自己的作用域下执行的。从另一个角度说,每个函数执行环境都有 一个作用域链,子函数的作用域链包括它的父函数的作用域链。关于作用域、作用域链请看下面。

作用域、作用域链、调用对象

函数作用域分为词法作用域和动态作用域。

词法作用域是函数定义时的作用域,即静态作用域。当一个函数定义时,他的词法作用域就确定了,词法作用域说明的是在函数结构的嵌套关系下,函数 作用的范围。这个时候也就形成了该函数的作用域链。作用域链就是把这些具有嵌套层级关系的作用域串联起来。函数的内部[[scope]]属性指向了该作用 域链。

动态作用域是函数调用执行时的作用域。当一个函数被调用时,首先将函数内部[[scope]]属性指向了函数的作用域链,然后会创建一个调用对 象,并用该调用对象记录函数参数和函数的局部变量,将其置于作用域链顶部。动态作用域就是通过把该调用对象加到作用域链的顶部来创建的,此时的 [[scope]]除了具有定义时的作用域链,还具有了调用时创建的调用对象。换句话说,执行环境下的作用域等于该函数定义时就确定的作用域链加上该函数 刚刚创建的调用对象,从而也形成了新的作用域链。所以说是动态的作用域,并且作用域链也随之发生了变化。再看这里的作用域,其实是一个对象链,这些对象就 是函数调用时创建的调用对象,以及他上面一层层的调用对象直到最上层的全局对象。

譬如全局环境下的函数A内嵌套了一个函数B,则该函数B的作用域链就是:函数B的作用域—>函数A的作用域—>全局window的 作用域。当函数B调用时,寻找某标识符,会按函数B的作用域—>函数A的作用域—>全局window的作用域去寻找,实际上是按函数B的调用 对象—>函数A的调用对象—>全局对象这个顺序去寻找的。也就是说当函数调用时,函数的作用域链实际上是调用对象链。

闭包

在动态执行环境中,数据实时地发生变化,为了保持这些非持久型变量的值,我们用闭包这种载体来存储这些动态数据(看完下面的应用就会很好的体会 这句话)。闭包的定义:所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。

闭包就是嵌套在函数里面的内部函数,并且该内部函数可以访问外部函数中声明的所有局部变量、参数和其他内部函数。当该内部函数在外部函数外被调 用,就生成了闭包。(实际上任何函数都是全局作用域的内部函数,都能访问全局变量,所以都是window的闭包)

譬如下面这个例子:

01 <script type="text/JavaScript">
02 function f(x) {
03 var a = 0;
04 a++;
05 x++;
06 var inner = function() {
07 return a + x;
08 }
09 return inner;
10 }
11 var test = f(1);
12 alert(test());
13 </script>

垃圾回收机制:如果某个对象不再被引用,该对象将被回收。

再结合前面所讲的一些概念,在执行var test=f(1)时创建了f的调用对象,这里暂且记作obj,执行完后虽然退出了外部执行环境,但内部函数inner被外部函数f外面的一个变量 test引用。由于外部函数创建的调用对象obj有一个属性指向此内部函数,而现在这个内部函数又被引用,所以调用对象obj会继续存在,不会被垃圾回收 器回收,其函数参数x和局部变量a都会在这个调用对象中得以维持。虽然调用对象不能被直接访问,但是该调用对象已成为内部函数作用域链中的一部分,可以被 内部函数访问并修改,所以执行test()时,可以正确访问x和a。所以说, 当执行了外部函数时,生成了闭包,被引用的外部函数的变量将继续存在。

二. 闭包的应用

应用1:

这个是我在用js模拟排序算法过程遇到的问题。我要输出每一次插入排序后的数组,如果在循环中写成

setTimeout(function() { $(“proc”).innerHTML += arr + “<br/>”; }, i * 500);

会发现每次输出的都是最终排好序的数组,因为arr数组不会为你保留每次排序的状态值。为了保存会不断发生变化的数组值,我们用外面包裹一层函数来 实现闭包,用闭包存储这个动态数据。下面用了2种方式实现闭包,一种是用参数存储数组的值,一种是用临时变量存储,后者必须要深拷贝。所有要通过闭包存储 非持久型变量,均可以用临时变量或参数两种方式实现。


应用2:

这个是无忧上的例子(点击这 里查看原帖),为每个<li>结点绑定click事件弹出循环的索引值。起初写成

id.onclick = function(){
alert(i);
}

id.onclick = function(){alert(i);}

发现最终弹出的都是4,而不是想要的 1、2、3,因为循环完毕后i值变成了4。为了保存i的值,同样我们用闭包实现:


(ps:var a = (function(){})(); 与 var a =new function(){}效果是一样的,均表示自执行函数。)

应用3:

下面的code是缓存的应用,catchNameArr。在匿名函数的调用对象中保存catch的值,返回的对象由于被CachedBox变量 引用导致匿名函数的调用对象不会被回收,从而保持了catch的值。可以通过CachedBox.getCatch(“regionId”);来操作,若 找不到regionId则从后台取,catchNameArr 主要是为了防止缓存过大。

01 <script type="text/javascript">
02 var CachedBox = (function() {
03 var cache = {}, catchNameArr = [], catchMax = 10000;
04 return {
05 getCatch: function(name) {
06 if (name in cache) {
07 return cache[name];
08 }
09 var value = GetDataFromBackend();
10 cache[name] = value;
11 catchNameArr.push(name);
12 this.clearOldCatch();
13 return value;
14 },
15 clearOldCatch: function() {
16 if (catchNameArr.length > catchMax) {
17 delete cache[catchNameArr.shift()];
18 }
19 }
20 };
21 })();
22 </script>

同理,也可以用这种思想实现自增长的ID。

01 <script type="text/javascript">
02 var GetId = (function() {
03 var id = 0;
04 return function() {
05 return id++;
06 }
07 })();
08 var newId1 = GetId();
09 var newId2 = GetId();
10 </script>

应用4:

这个是无忧上月MM的例子(点击这里查看原帖),用闭包实现程序的暂停执行功能,还蛮创意


把这个作用延伸下,我想到了用他来实现window.confirm。


看了上面的这些应用,再回到前面的一句话:在动态执行环境中,数据实时地发生变化,为了保持这些非持久型变量的值,我们用闭包这种载体来存储这 些动态数据。这就是闭包的作用。也就说遇到需要存储动态变化的数据或将被回收的数据时,我们可以通过外面再包裹一层函数形成闭包来解决。

当然,闭包会导致很多外部函数的调用对象不能释放,滥用闭包会使得内存泄露,所以在频繁生成闭包的情景下我们要估计下他带来的副作用。

毕了。希望能对大家有所帮助。

作者:JayChow
出处:http://ljchow.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,请在文章页面明显位置给出原文链接。

[转载][jQuery]使用jQuery.Validate进行客户端验证(高级篇-下)

mikel阅读(1006)

[转载][jQuery]使用jQuery.Validate进行客户端验证(高级篇-下)——不使用微软验证控件的理由 – kyo-yo – 博客园.

继续上一篇文章使用jQuery.Validate 进行客户端验证(高级篇-上),本文将继续介绍JQuery.Validate的高级应用——JQuery.Validate的AJAX验证及简 单扩展。

今天主要介绍的内容有:

1、如何使用jQuery.Validate进行AJAX验证?

2、默认jQuery.Validate在进行AJAX验证时返回必须是bool类型,如何返回一个对象包括错误消息及验证结果?

3、在反复使用jQuery.Validate进行AJAX验证时,总是需要编写相关AJAX参数,可否进行进一步封装?

第一点:如何使用jQuery.Validate进行AJAX验证?(具体见High-2.aspx

jQuery.Validate为我们提供了一个方便的AJAX验证方式(封装了jQuery的AJAX,同时将jQuery的AJAX和 jQuery.Validate的验证很好的结合在一起),在此我仅仅介绍jQuery.Validate在ASP.NET下如何进行AJAX验 证,PHP、JSP等请查看官方例子。

我是采用jQuery.Validate+WebService进行AJAX验证,客户端编写jQuery.Validate的remote验证属 性,服务器端采用WebSerice进行接收参数进行验证。

首先来看下jQuery.Validate的remote属性如何编写AJAX验证规则:

01 function InitRules() {
02 opts = {
03 rules:
04 {
05 <%=txtUid.UniqueID %>:
06 {
07 required: true,
08 remote:{
09 type: "POST",
10 async: false,
11 url: "WebService.asmx/CheckUid",
12 dataType: "xml",
13 data: {uid:function(){ return jQuery("#<%=txtUid.ClientID %>").val();}}
14 }
15 }
16 }
17 }
18 }

如果使用过jQuery.ajax的朋友肯定会很熟悉这段代码,jQuery.Validate的remote采用和jQuery.ajax相同的 参数设置(原因就上面所说的封装了jQuery.ajax的原因)。

这边来详细讲解下jQuery.Validate的 remote的一些知识:

1、jQuery.Validate的remote默认可以直接填写远程验证的地址,格式为:remote:”validate.aspx”,但是 很多情况下这个远程验证需要提交参数、返回类型等限制,所以就可以采用在“{}”中编写具体属性的方式来包装提交参数。

2、jQuery.Validate的remote官方代码中,远程的输出只能是true或者false,不允许有其他输出,这个我觉得不太好,具 体的扩展在后面我会讲到

3、jQuery.Validate的remote在使用时如果想提交参数需要以JSON的方式提交格式如下:

1 data: {
2 uid:function(){
3 return jQuery("#<%=txtUid.ClientID %>").val();
4 }
5 }

此处肯定会有人不明白,为什么参数需要以function的形式提交,而不是直接写

jQuery(“#<%=txtUid.ClientID %>”).val();

这里我要说明的是:jQuery.Validate 的验证规则是在页面加载的时候就已经被加载了的,如果还是像以往一样直接写”jQuery(“#<%=txtUid.ClientID %>”).val();”,那么验证的时候提交给服务器端的数据永远是页面加载时txtUid控件的值。

而使用function的好处就是在页面加载的时候 仅仅告诉jQuery.Validate,在控件需要进行remote验证的时候需要调用function这个函数,这样就保证了在执行remote验证 的时候可以获取到最新的值

我还修改了jQuery.Validate的remote方法,先来看下我修改的代码(具体见scripts/jquery.validate1.js 896行):

01 if (previous.old !== value) {
02 previous.old = value;
03 var validator = this;
04 this.startRequest(element);
05 var data = {};
06 data[element.name] = value;
07 $.ajax($.extend(true, {
08 //url:url,//此URL无法获取
09 url: param.url,//获取param验证规则中所写路径
10 mode: "abort",
11 port: "validate" + element.name,
12 type: param.type,
13 dataType: "json",
14 //data:data,
15 data: param.data || data,//获取param.data或data中所提交的参数
16 success: function(response) {
17 以下省略...
18 }

这边我修改了url和data的获取方式,主要是因为在真正执行的时候,变量url是空的,所以需要从param中获取。

而data为什么要改成param.data || data,主要原因就是下面这句代码:

data[element.name] = value;

这句代码的意思就是:为哪个控件设置远程验证就获取哪个控件的值,但是在实际的开发中,当遇到进行AJAX验证的时候会出现需要同时提交多个数据的 情况,此时这句代码就是错误的了,所以需要改成

param.data || data,这样就能保证在提交多个数据的时候以多个数据为准。

下面来看下webservice的代码:

1 [WebMethod]
2 public bool CheckUid(string uid)
3 {
4 return uid == "testuid" ? true : false;
5 }

相当的简单,就是判断下用户名是不是指定的用户名。

注意:webservice.cs中必须将 [System.Web.Script.Services.ScriptService]这个特性取消注释,否则AJAX验证将无效!

第二点:默认jQuery.Validate在进行AJAX验证时返回必须是bool类型,如何返回一个对象包括错误消息及验证结 果?(具体见 App_Code/WebService.cs/CheckUid

在第一点中介绍jQuery.Validate知识的时候就提到了,jQuery.Validate默认直接收true或false,但是在具体的 开发中,我们会分层开发,三层或者多层,webservice在接收到验证请求后不做具体的处理直接调用逻辑层的验证方法,交由逻辑层进行验证操作(当 然你也可以把验证全部写在webservice中,但是这样就体现不出分层的好处了),此时的验证会产生多种情况,以最常见的用户名验 证为例:

1)用户名已存在,此时的消息应该是“用户名已存在,请重新输入!”

2)用户名不符合规则,此时的消息应该是“用户名不符合规则,请重新输入!”

3)验证时出现程序异常,此时的消息应该是“程序出现异常,请联系管理员!”

可以看出,仅仅一个用户名验证就会出现这3种信息,如果不返回一个明确的消息,仅仅告诉用户“用户名有误”,客户端的使用者将会相当的痛苦,因为使 用者并不知道他的用户名输入到底错在哪了。

所以为了更好的客户体验,以及项目的合理性,我们在服务器端封装一个实体类(具体见AppCode/AjaxClass),代码如下:

1 [Serializable]
2 public class AjaxClass
3 {
4 public string Msg { get; set; }
5 public int Result { get; set; }
6 }

就是一个最简单的实体类,有2个属性,Msg和Result,Msg用于存放验证失败的信息,Result用于存放结果。

看下WebSerivce的代码如何修改:

01 [WebMethod]
02 public AjaxClass CheckUid(string uid)
03 {
04 //return uid == "testuid" ? true : false;
05 AjaxClass ajaxClass = new AjaxClass();
06 try
07 {
08 if (uid == "testuid")
09 {
10 ajaxClass.Msg = "用户名已存在,请重新输入!";
11 ajaxClass.Result = 0;
12 }
13 if (uid.IndexOf("test") == -1)
14 {
15 ajaxClass.Msg = "用户名格式不正确,用户名必须包含test,请重新输入!";
16 ajaxClass.Result = 0;
17 }
18 else
19 {
20 ajaxClass.Msg = "格式正确!";
21 ajaxClass.Result = 1;
22 }
23 }
24 catch
25 {
26 ajaxClass.Msg = "程序出现异常,请联系管理员!";
27 ajaxClass.Result = 0;
28 }
29 return ajaxClass;
30 }

上面的WebService就完整的实现了我先前说的3种错误情况(由于这边仅仅是例子所以就只有表示层,实际开发中需要分层开 发,此代码应该放入业务逻辑层

注意:在webservice返回值前,如果检查成 功必须要为ajaxClass.Result = 1,否则客户端验证会无法通过。

虽然完成了服务器端的代码修改,但是直接运行页面还是会出错,这是因为我上面所说过的,jQuery.Validate的remote远程的输出只 能是true或者false,我们来看下具体的代码,其中注释掉的就是原来官方的代码:

01 success: function(response) {
02 if (response.Result) {//if(response){
03 var submitted = validator.formSubmitted;
04 validator.prepareElement(element);
05 validator.formSubmitted = submitted;
06 validator.successList.push(element);
07 validator.showErrors();
08 } else {
09 var errors = {};
10 //errors[element.name] = response.Result || validator.defaultMessage(element, "remote");
11 errors[element.name] = response.Msg;
12 validator.showErrors(errors);
13 }
14 previous.message = response.Msg; //previous.valid = response;
15 previous.valid = response.Result;
16 validator.stopRequest(element, response.Result);
17 }

可以看到一共修改了3处地方:

1、判断返回值,原来是直接判断response,现在则是判断response.Result,因为现在的response已经 是一个包含消息及结果的对象了。

2、错误消息,原来的错误消息是直接获取默认配置好的消息,我这边是获取response.Msg。

3、设置previous对象,将previous对象的消息和结果设置为AJAX返回的消息和结果,以供 jQuery.Validate下面代码的返回。

这样jQuery.Validate的remote的方法就修改了,但是并没有结束,原因是先前在AJAX提交参数的时候由于 jQuery.Validate的验证规则的缘故,提交的参数并不是以JSON的格式提交的而是以{uid:function()}这样的方式,结果就导 致了无法设置jQuery.AJAX的contentType:”application/json; charset=utf-8″,如果设置了会出现以下错误:

pic24这样从webservice返回的AjaxClass对象就无法像以往的JSON方式直接操作 了,所以我们只能换一种格式——XML,因为webservice默认返回的数据是XML格式:

1 <?xml version="1.0" encoding="utf-8" ?>
3 <Msg>用户名格式不正确,用户名必 须包含test,请重新输入!</Msg>
4 <Result>0</Result>
5 </AjaxClass>

接下来看下具体的remote方法应该如何编写,设 置dataType:”xml”,然后将XML数据转换成一个对象以供上面我修改的jQuery.Validate的remote方法中 ajaxsuccess的使用,具体看一下代码:

01 remote:{
02 type: "POST",
03 dataType:"json",
04 async: false,
05 url: "WebService.asmx/CheckUid",
06 data: {uid:function(){ return jQuery("#<%=txtUid.ClientID %>").val();}},
07 dataFilter: function(dataXML) {
08 var result = new Object();
09 result.Result = jQuery(dataXML).find("Result").text();
10 result.Msg = jQuery(dataXML).find("Msg").text();
11 if (result.Result == "-1") {
12 result.Result = false;
13 return result;
14 }
15 else {
16 result.Result = result.Result == "1" ? true : false;
17 return result;
18 }
19 }
20 }

就是jQuery.Ajax方法dataFilter,可以在AJAX请求成功后将数据进行过滤处理,这里我就使用了jQuery方法把结果和消息 从XML中获取出来直接赋给一个对象,再将这个对象返回,交由ajaxsuccess使用。

这样就算是完成了修改jQuery.Validate的remote方法,使得可以返回验证结果及验证消息,看下效果图:

pic25

第三点:在反复使用jQuery.Validate进行AJAX验证时,总是需要编写相关AJAX参数,可否进行进一步封装?(具 体见High-3.aspx和jquery.validate.extension.js)

在开发一个系统的时候经常会用到AJAX的验证,而如果每次都要编写上面那么多的代码还是很不方便,所以我现在就来进行一下简单的封装,代码如下:

01 //远程验证抽象方法
02 function GetRemoteInfo(postUrl, data) {
03 var remote = {
04 type: "POST",
05 async: false,
06 url: postUrl,
07 dataType: "xml",
08 data: data,
09 dataFilter: function(dataXML) {
10 var result = new Object();
11 result.Result = jQuery(dataXML).find("Result").text();
12 result.Msg = jQuery(dataXML).find("Msg").text();
13 if (result.Result == "-1") {
14 result.Result = false;
15 return result;
16 }
17 else {
18 result.Result = result.Result == "1" ? true : false;
19 return result;
20 }
21 }
22 };
23 return remote;
24 }

这个函数主要接收2个参数,一个是远程验证的路径和需要提交的参数,返回包装好的remote对象。

页面调用也很简单,代码如下:

01 <script src="scripts/jquery.validate.extension.js" type="text/JavaScript"></script>
02 <script type="text/JavaScript">
03 function InitRules() {
04 var dataInfo = {uid:function(){ return jQuery("#<%=txtUid.ClientID %>").val();}};
05 var remoteInfo = GetRemoteInfo('WebService.asmx/CheckUid', dataInfo);
06
07 opts = {
08 rules:
09 {
10 <%=txtUid.UniqueID %>:
11 {
12 required: true,
13 remote:remoteInfo
14 }
15 }
16 }
17 }
18 </script>

怎么样?相比上面的代码一下子干净了很多吧?

页面上只要做3步操作:

1、包装好需要提交的data对象。

2、将远程验证地址和包装好的data对象传递给封装好的方法获取remote对象。

3、将函数返回的remote对象放入规则中。

至此使用jQuery.Validate进行客户端验证——不使用微软验证控件的理由这一系列就算全部写完了,大体上将 jQuery.Validate在ASP.NET上的一些常见应用讲了一下,同时也提出了许多我自己修改扩展的东西,希望对正在苦恼客户端验证的朋友有所 帮助,谢谢大家的支持了!

PS:1、其实这一系列并没有把 jQuery.Validate的所有功能介绍完,比如onfocusin,onfocusout,onkeyup等,这些就需要大家在使用的过程中自己 查看源代码实验了。

2、本文有点长,而且内容比较多,如果文中有什么错 误或者有指导意见欢迎大家提出来,谢谢了!

源代码下载:点我下载

作者:kyo-yo
出处:http://kyo-yo.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

[转载]ASP.NET MVC实现(查询+分页)

mikel阅读(1107)

[转载]ASP.NET MVC实现(查询+分页) – NSun快速开发 – 博客园.

ASP.NET中我们知道有viewstate这样的页面级容器为我们保存表单数据,这样我们每次提交时数据 都不会丢失,很容易的完成查询+分页的实现找 过相关MVC分 页的例子,都是扩展HtmlHelper方法来实现。我想大家在ASP.NET开发中都用过 wuqiAspNetPager分页 控件以及daceyNSunPage用来开发Winform项目的分页控 件非常方便的满足了大家的分页需求。那么我们来看下在MVC中的查询+分页是怎么实现的。(这里我用到了wuqi的mvcpager)

下面例子是asp.net中的分页查询:

前台代码:

<html>
<body>
<form id=”form1″ runat=”server”>
部门编号:
<asp:TextBox ID=”deptcode” Width=”80px” runat=”server”></asp:TextBox>
部门名称:
<asp:TextBox ID=”deptname” Width=”80px” runat=”server” ></asp:TextBox>
<asp:Button ID=”btnquery” runat=”server” Text=”查询” onclick=” btnquery_Click”/>
<table class=”TableBlock” width=”100%” style=”margin-top: 10px;”>
<tr>
<th>编号</th>
<th>名称</th>
</tr>
<asp:Repeater ID=”TableBlockList” runat=”server”>
<ItemTemplate>
<tr>
<td><%#Eval(code) %></td>
<td><%#Eval(name) %></td>
</tr>
</ItemTemplate>
</asp:Repeater>
</table>
</form>
</body>
</html>

后台代码:

protected virtual SelectSQLSection GetSelectSearch()
{
SelectSQLSection select
= db.GetSelectSqlSection ();
if (!string.IsNullOrEmpty (deptname.Text))  {
select.Where (View_DeptQueryInfo.__name.Like(deptname.Text
+ %));
}
if(!string.IsNullOrEmpty(deptcode.Text)){
select.Where (View_DeptQueryInfo.__code.Like(deptcode.Text
+ %));
}
return select;
}
protected void BindTable(Repeater rpt, AspNetPager anp)
{
int countPage = 0;
DataTable dt
=db.SelectPageToDataTable(GetSelectSearch (), anp.PageSize, anp.CurrentPageIndex,
out countPage);
anp.RecordCount
= countPage;
rpt.DataSource
= dt;
rpt.DataBind();
}

以上就完成了查询分页的功能,没什么特别的和我们做winform的差不多。第二次查询一样能获得表单内的值。

那么asp.net mvc中怎么实现呢?用什么来保存 表单数据呢?记得原来写java的时候我是用session这样笨拙的方法来保存查询提交后的方法来保存用户查询条件的。那么在asp.net mvc中我们该怎么实现呢?

页面代码:

<html>
<body>
<form id=”form1″ method=”post” action=”/Dept/Dept” runat=”server”>
部门名称:
<input id=”deptname” name=” deptname “ type=”text” value='<%=ViewData [“deptname “] %>‘/>
部门编号:
<input id=”deptcode” name=” deptcode “ type=”text” value='<%=ViewData [“deptcode “] %>‘/>
<input id=”btnquery” type=”submit” value=”查询 “/>
<table class=”TableBlock” width=”100%”>
<tr>
<th>编号</th>
<th>部门名称</th>
</tr>
<asp:MvcRepeater ID=”MvcRepeater1″ Key=”deptlist” runat=”server”>
<ItemTemplate>
<tr>
<td style=”width:5;”><%# Eval(code)%></td>
<td style=”width:10;”><%# Eval(name)%></td>
</tr>
</ItemTemplate>
</asp:MvcRepeater>
</table>
<div class=”pager” style=”width:100%;text-align:right;”>
<div class=”pager” align=”left” style=”float: left; width: 40%;”>
<%=+ ((Webdiyer.WebControls.Mvc.PagedList<DepartmentInfo>)ViewData [deptlist]).TotalPageCount + + ((Webdiyer.WebControls.Mvc.PagedList<DepartmentInfo>)ViewData [deptlist]).TotalItemCount + %></div>
<div align=”right” class=”pager” style=”width: 60%; float: left;”> <%=Html.Pager<DepartmentInfo> ((Webdiyer.WebControls.Mvc.PagedList<DepartmentInfo>)ViewData [deptlist], new PagerOptions() { PageIndexParameterName = id, CurrentPagerItemWrapperFormatString = <span class=\cpb\> {0}</span>, NumericPagerItemWrapperFormatString = <span class=\item\> {0}</span>, CssClass = pages, SeparatorHtml = “”,IsPost=true })%></div>
</div>
</form>
</body>
</html>

首先用到了Repeater,在mvcRepeater的使用需要我们重写一下。如果想用for循环来实现也可以。我们 继承Repeater然后重写OnLoad(EventArgs e) 方法得到Controller中放入ViewData的数据。我们添加一个公开的属性来取得对应ViewDate的值。

Repeater重写:

public string Key { get; set; }
//绑定数据
protected override void OnLoad(EventArgs e)
{
this.DataSource = (base.Page as ViewPage).ViewData[this.Key];

this.DataBind();

base.OnLoad(e);
}

这样就得到我们想要的MVC中的Repeater,因为Repeater必须放在runat=”server”form中所以这里action没 有使用UrlHepler给我们提供的方法来写。

我们把查询条件放入ViewData为了提交后的保存与显示。

<input id=”deptname” name=” deptname “ type=”text” value='<%=ViewData [“deptname“] %>‘/>

后台保存:

public ActionResult Dept (int? id)
{
ViewData [
deptlist] = BindTable(id);
return View();
}
protected Webdiyer.WebControls.Mvc.PagedList<DepartmentInfo> BindTable(int? id)
{
int countPage = 0;
List
<DepartmentInfo> dt = db.SelectPageToList(GetSelectSearch(), 10, id ?? 1, out countPage);
Webdiyer.WebC ontrols.Mvc.PagedList
<DepartmentInfo> p = new Webdiyer.WebControls.Mvc.PagedList<DepartmentInfo>(dt, id ?? 1, 10, countPage);
return p;
}
protected virtual SelectSqlSection GetSelectSearch()
{
SelectSqlSectio n select
= db.GetSelectSqlSection();
select.OrderBy (DepartmentInfo.__ordernumber.Asc);
if (!string.IsNullOrWhiteSpace(Request[deptname]))
{
select.Where(DepartmentInfo.__name.Like(% + GetFormValue(deptname) + %));
}
if (!string.IsNullOrWhiteSpace(Request[deptcode]))
{
select.Where(DepartmentInfo.__code.Like(% + GetFormValue(deptcode) + %));
}
return select;
}
private object GetFormValue(string name)
{
ViewData [name]
= Request[name];
return ViewData[name];
}

这里出现问题了,在查询时候的确可 以获得表单的值,也可以这样保存显示,那么查询后点分页怎么办?分页可是a标签,难道让a标签也提交么?

OK,那么我们就来实现a标签的提交,我们就来给它分页时做提交功能。

因为每次分页都提交表单可能不是各个场景都需要,这里我对 PagerOptions类扩展了2个属性一个是 ispost(分页是否提交,默认是false FormName(提交表单id,这里默认是 asp.net生成的form1)。

再来看怎么生成控件到页面,我们看PagerBuilder类,找到GeneratePagerElement方法,看最后一句String.Format (“<a href='{0}’>{1}</a>”, url, item.Text));默认是生成只带连接的a标签,我们来改造他吧。

我们重新写这个方法名字就叫GeneratePagerElementPost,我们把a 标签的href执行一段脚本,把刚才 那句替换为 String.Format(“<a href=’JavaScript:_PagePostSubmit (\”{0}\”)’>{1}</a>”, url, item.Text));

_PagePostSubmit这 个js方法 就是用来点击a标签进行表单的提交的。那么我们看这个方法是怎么实现和生成到页 面的。找到RenderPager方法,我们拉到方法最 后看到如下代码,这里是添加脚本到客户端。

if (!string.IsNullOrEmpty(pagerScript))

pagerScript = “<script language=\”JavaScript\” type=\”text/JavaScript\”>” + pagerScript + “</script>”;

在这个方面上面添加一下代码:首先我们判断下客户端是否需要每次分页都提交页面,然后加入我们的_PagePostSubmit这个js方法

if (_pagerOptions.IsPost)

{

//通过 _pagerOptions.FormName获得用户选定表单名称,修改其 action,然后提交。

pagerScript += ” function _PagePostSubmit(href){ document.forms[‘” + _pagerOptions.FormName + “‘].action=href;document.forms[‘” + _pagerOptions.FormName + “‘].submit();}”;

}

Ok,这样每个 a标签都能提交表单了,我们在页面添加下面代码就完成了查询+分页的实现

<%=Html.Pager<DepartmentInfo> ((Webdiyer.WebControls.Mvc.PagedList<DepartmentInfo>)ViewData [deptlist], new PagerOptions() { PageIndexParameterName = id, CurrentPagerItemWrapperFormatString = <span class=\cpb\> {0}</span>, NumericPagerItemWrapperFormatString = <span class=\item\> {0}</span>, CssClass = pages, SeparatorHtml = “”,IsPost=true })%>

把刚加的 IsPost属性变为 true,默认FormName=form1这里就没有在赋值了。

实现DEMO:ASP.NET MVC查询+分页 DEMO

修改后的MVCPAGER: ModifyPostMvcPagerSrc

[转载]Memcached 两款.NET客户端的郁闷事儿

mikel阅读(1160)

[转载]Memcached 两款.NET客户端的郁闷事儿 – 汤家大院 – 博客园.

不久以后就要负责一个比较大的项目,有多大?反正就是挺大的。现在处于筹备阶段,我主要负责系统框架搭建,在系统缓存这一块决定采用Http运 行时缓存+memcached。

memcached 以前用过几次 不过也是小打小闹型,尚未正式大型应用过。这次也算是个难得的练手机会吧。memcached服务器打算分布在web应用服务器以及数据库服务器上。

(两台服务器有点花大手笔了 两台Dell PowerEdge R810 自定义了配置 大约单台6.5万)。

关于Memcached的.NET 客户端的选择这一块,主要有两款候选库,Memcached.ClientLibrary(discuz .net版本企业版使用的缓存客户端) 以及 Enyim 。

这两款类库都比较好用,后者使用更加方便,只需简单的配置。我选择了Enyim。

在测试的时候发现了一个共同的问题,假如在使用多个Memcached服务时,当其中一台服务器网络不通(其它至少存在一个可用的 Memcached服务)的情况下,

Memcached.ClientLibrary 的缓存功能失效,Enyim 在缓存操作的时候实例化socket对象时没有进行连接超时的处理(Memcached.ClientLibrary处理了)。

一般都要等待10秒以上线程才能继续,线程被卡住不放了。在以往的应用中都是 memcached 与 web 共存在一台服务器上,所以没遇到过这个问题,

但是这次打算 web 服务器上开一个memcached实例,数据库服务器上开一个memcached实例,所以才关注到了这个问题。一般情况下这个问题也不会有太大影响,

但是心里总是感觉不爽。不打算使用Memcached.ClientLibrary ,所以找了 Enyim 的源代码,打算自己修改一下。

翻看了一下源代码, Enyim 的问题主要存在于 Enyim.Caching.Memcached.PooledSocket,该类主要用于创建socket 连接 。

以下是构造函数

01 internal PooledSocket(IPEndPoint endpoint, TimeSpan connectionTimeout, TimeSpan receiveTimeout, Action<PooledSocket> cleanupCallback)
02
03 {
04
05 this.endpoint = endpoint;
06
07 this.cleanupCallback = cleanupCallback;
08
09
10
11 this.socket = new Socket(endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
12
13
14
15 this.socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, connectionTimeout == TimeSpan.MaxValue ? Timeout.Infinite : (int)connectionTimeout.TotalMilliseconds);
16
17 this.socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, receiveTimeout == TimeSpan.MaxValue ? Timeout.Infinite : (int)receiveTimeout.TotalMilliseconds);
18
19
20
21 // all operations are "atomic", we do not send small chunks of data
22
23 this.socket.NoDelay = true;
24
25
26
27 this.socket.Connect(endpoint);
28
29 this.inputStream = new BufferedStream(new BasicNetworkStream(this.socket));
30
31 }

由于Socket (我对Socket没啥研究 几乎就是文盲)对象没法设置连接超时之类的属性(这下怎么办呀),并且在源代码中可以看出 this.socket.Connect(endpoint)

的时候没有做任何处理,导致当连接有问题的服务时出现了较长时间的线程等待。我在网上找啊找,找到了类似问题的解决方案,就是使用线程池。

网上找的代码修修改改 差不多能用了,创建了 Enyim.Caching.Memcached.SocketConnector,其中有一个核心的静态方法,用来创建Socket对象,并且运行定义 超时时间。

01 public static Socket GetConnectdSocket(IPEndPoint endpoint, TimeSpan connectionTimeout, TimeSpan receiveTimeout, int millisecondsTimeout)
02
03 {
04
05 ConnectorState state = new ConnectorState();
06
07 state.Endpoint = endpoint;
08
09 state.ConnectionTimeout = connectionTimeout;
10
11 state.ReceiveTimeout = receiveTimeout;
12
13 ThreadPool.QueueUserWorkItem(new WaitCallback(SocketConnector.ConnectThreaded), state);
14
15 if (state.Completed.WaitOne(millisecondsTimeout, false))
16
17 {
18
19 if (state.Socket == null)
20
21 {
22
23 throw state.Exception;
24
25 }
26
27 return state.Socket;
28
29 }
30
31 state.Abort();
32
33 throw new SocketException(0x2af9);
34
35 }

然后修改PooledSocket的构造为:

01 internal PooledSocket(IPEndPoint endpoint, TimeSpan connectionTimeout, TimeSpan receiveTimeout, Action<PooledSocket> cleanupCallback)
02
03 {
04
05 this.endpoint = endpoint;
06
07 this.cleanupCallback = cleanupCallback;
08
09 this.socket = SocketConnector.GetConnectdSocket(endpoint, connectionTimeout, receiveTimeout, 100);
10
11 this.inputStream = new BufferedStream(new BasicNetworkStream(this.socket));
12
13 }

我将连接超时设置为100毫秒,那个恶心的问题基本算是解决了。

[转载]asp.net mvc 2 简简单单做开发 实现基本数据操作操作RepositoryController

mikel阅读(766)

[转载]asp.net mvc 2 简简单单做开发 实现基本数据操作操作RepositoryController – 飞创cms – 博客园.

ASP.NET mvc 2 使用Linq to SQL 来操作数据库,基本实现面向对象编程,同样在操作数据是我们也可以使用面向对象的继承再加上泛型,是操作更加简单。代码简单,不详细说明,里面用到几个简 单的扩展方法,以后会详细介绍。如db.find<T>(t),db.findkey<T>(id)。这是对dbcontent 做了些扩展。

RepositoryController.cs 代码如下:

1 public class RepositoryController<T>: BaseController where T : class,new()
2 {
3 private Table<T> list()
4 {
5 return db.GetTable<T>();
6 }
7
8
9 public string orderby = Id desc;
10 public virtual ActionResult Index(T art)
11 {
12 ViewData[searchModel] = art;
13 var Model = db.Find<T>(art);
14 RecordCount = Model.Count();
15 Model=Model.OrderBy(orderby);
16
17 Model = Model.Skip((CurPage 1) * PageSize).Take(PageSize);
18
19 GetPager();
20 return View(index, Model);
21 }
22 [ValidateInput(false)]
23 public virtual ActionResult Add()
24 {
25
26 return View(new T());
27 }
28 [HttpPost]
29 [ValidateInput(false)]
30 public virtual ActionResult Add(T Model)
31 {
32 if (ModelState.IsValid)
33 {
34 try
35 {
36 list().InsertOnSubmit(Model);
37 db.SubmitChanges();
38 return RedirectToAction(index);
39 }
40 catch
41 {
42 return View(Model);
43 }
44 }
45 else
46 return View(Model);
47 }
48 [ValidateInput(false)]
49 public virtual ActionResult Edit(int id)
50 {
51 return View(db.FindKey<T>(id));
52 }
53 [HttpPost]
54 [ValidateInput(false)]
55 public virtual ActionResult Edit(int id, T Model)
56 {
57 try
58 {
59 var cate = db.FindKey<T>(id);
60 UpdateModel(cate);
61 db.SubmitChanges();
62 return RedirectToAction(index);
63 }
64 catch
65 {
66 return View(Model);
67 }
68 }
69 public virtual ActionResult Delete(int id)
70 {
71 list().DeleteOnSubmit(db.FindKey<T>(id));
72 db.SubmitChanges();
73 return RedirectToAction(index);
74 }
75
76
77 }
————————————————————————————
作者:王继坤
出处:http://www.wjk3.cn/
本文版权归作者和博客园共有,欢迎转 载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

[转载]javascript闭包

mikel阅读(985)

[转载]javascript闭包 – zhangle – 博客园.

官方对闭包的解释是:一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。闭包的特点:
1.作为一个函数变量的一个引用,当函数返回时,其处于激活状态。
2.一个闭包就是当一个函数返回时,一个没有释放资源的栈区。
简单的说,JavaScript允许使用内部函数—即函数定义和函数表达式位于另一个函数的函数体内。而且,这些内部函数可以访问它们所在的外部函数 中声明的所有局部变量、参数和声明的其他内部函数。当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包。

function closure(){
var str = I’m a part variable.;
return function(){
alert(str);
}
}
var fObj = closure();
fObj();

在上面代码中,str是定义在函数closure中局部变量,若strclosure函数调用完成以后不能再被访问,则在函数执行完成后str将被释放。但是由于函数closure返回了一个内部函数,且这个返回的函数引用了str变 量,导致了str可能会在closure函数执行完成以后还会被引用,所以str所 占用的资源不会被回收。这样closure就形成了一个闭包。

事实上,当我们给自定 义的JavaScript类中的私有变量添加get/set函数时,已经用到了闭包。

function Class1(){
var vari;
this.getVari = function(){return vari;}
this.setVari = function(_vari){vari = _vari;}
}
var cls = new Class1();
cls.setVari(
test variable);
alert(cls.getVari());

在这个例子中,构造器中定义了变量vari,构造器执行结束后如果vari没有被其他外部能够应用的成员方法所引用时,这个变量将被回收。但 是我们给它定义了get/set函数以后,由于这两个内部函数可以被包含他们的函数Class1(类)的外部所访问,且变量vari被这两个函数所引用, 因此vari不会被回收。这样就形成了闭包。

JavaScript垃圾回收的原则是:如果一个对象不再被引用,那么这个对象会被垃圾 回收器回收。如果两个对象无干扰的互相引用,那么这两个对象也会被回收。

闭包在javascript编程中有重要的作用,如果使用恰当,可以为某些问题的解决带 来很多方便。但是如果使用不恰当,也会带来很多麻烦。

1.为执行的函数提供参数。
setTimeout可以延迟执行某个函数,原型如下: setTimeout(code,millisec)。其中第一个参数为需要执行的函数或者代码,第二个参数是延迟的毫秒数。常见用法:

function sayHello(){
alert(
hello world);
}
setTimeout(sayHello,
1000);

但是可能我们会需要一个通用一些的函数,而不仅仅是只会说“hello world”。我们写一个say()函数,给say传递一个需要说的内容的参数。但是在setTimeout中我们无法给需要延迟执行的函数传递参数。使 用闭包,我们可以这样做:

function say(words){
return function(){
alert(words)
}
}
setTimeout(say(
hello word),1000);
setTimeout(say(
I’m hungry,I’m need some food!),2000);

这样可以在调用setTimeout时给需要延迟执行的函数传递参数了。

2.将对象的方法与其他实例关联

比如我们在做一个多文件上传的程序,要求可以动态添加和删除文件:

function addFile(){
var fileInput = document.createElement(input);
fileInput.type
= file;
fileInput.name
= file;

var btnDel = document.createElement(input);
btnDel.type
= button;
btnDel.value
= 删 除;

btnDel.onclick = (function(){
//删除文件
return function(){
var c = _(fileContent);
c.removeChild(fileInput);
c.removeChild(btnDel);
}
})();

var fContent = _(fileContent)
fContent.appendChild(fileInput);
fContent.appendChild(btnDel);
}

在这个例子中,我们在删除按钮的点击时间关联的函数中删除对应的上传表单和按钮本身。由于表单和按钮都是动态生成,在事先并不知道删除按钮点击 后需要删除的内容是什么。但是我们可以使用javascript闭包的特性,在上传表单和按钮生成的时候将按钮的点击事件与上传表单实例及和按钮实例进行 关联。

3.模拟静态私有变量

Javascript本身并不支持面向对象的特性。但是我们可以通过 javascript的一些特性模拟实现javascript面向对象。
比如我们需要定义一个类,并且模拟静态私有变量。

var Class2 = (function(){
var s_var = 0; //静态私有变量
return function(){
this.getInstanceCount = function(){
return s_var;
}
//constructor
s_var++;
}
})()

var cls1 = new Class2();
alert(cls1.getInstanceCount());
//1

var cls2 = new Class2();
alert(cls1.getInstanceCount());
//2
alert(cls2.getInstanceCount()); //2

var cls3 = new Class2();
alert(cls1.getInstanceCount());
//3
alert(cls3.getInstanceCount()); //3

在这个例子中,我们用s_var记录Class2被实例化的次数,使用闭包,我们可以将s_var模拟为一个静态私有变量,每次Class2被实例化的时 候将s_var加1。

上例中我们使用了这样形式的一段代码,其中定义在外层函数内,内层函数外的成员类似于静 态成员。所以这样形式的代码我们可以把他叫做“静态封装环境”。

(function(){
return function(){
}
}
})()

参考文章地址:

http://baike.baidu.com/view/648413.htm
http://www.cnblogs.com/yangjian/archive/2009/09/24/1573176.html
http://www.cnblogs.com/chongzi/
http://www.360doc.com/content/09/1227/11/370235_12087397.shtml
http://softbbs.pconline.com.cn/9497825.html

[转载]asp.net mvc 2 简简单单做开发 自定义DropdownList控件

mikel阅读(886)

[转载]asp.net mvc 2 简简单单做开发 自定义DropdownList控件 – 飞创cms – 博客园.

ASP.NET mvc 2 给我们提供了强大的自定义功能,今天主要说下DropdownList自定义绑定字段显示,通过ViewData设定DropdownList的数据项。 自动绑定显示。实现的方式。在global.asax 中注册 FieldTemplateMetadataProvider,

ModelMetadataProviders.Current = new mvc.Models.FieldTemplateMetadataProvider();
通过返回的 FieldTemplateMetadata 。在MetaData中指定使用DropDownList的字段
[Display( Name=“”,Order=12)]
[Required]
[SearchFilter]
[DisplayName(“栏目”)]
[DropDownList(“Category”“Id”“Name”)]
public int Cid { getset; }

通过DropDownList指定 调用的模板为dropdownlist.ascx ,在dropdownlist.ascx 将默认的 ModelMetadata 转成FieldTemplateMetadata 获取 DropDownListAttribute 。

<script runat="server">
    DropDownListAttribute GetDropDownListAttribute()
    {
        FieldTemplateMetadata metaData = ViewData.ModelMetadata as FieldTemplateMetadata;
 
        return (metaData != null) ? metaData.Attributes.OfType<DropDownListAttribute>().SingleOrDefault() : null;
    }
</script>
 
  通过DropDownListAttribute 获得 ViewData的key ,绑定的文本对应的字段,值对应的字段,使用html.DropDownlist显示数据

DropdownList.ascx 代码

代码

<%@ Import Namespace=mvc.Models%>
<%@ Control Language=C# Inherits=System.Web.Mvc.ViewUserControl %>
<script runat=server>
DropDownListAttribute GetDropDownListAttribute()
{
FieldTemplateMetadata metaData
= ViewData.ModelMetadata as FieldTemplateMetadata;

return (metaData != null) ? metaData.Attributes.OfType<DropDownListAttribute>().SingleOrDefault() : null;
}
</script>
<% DropDownListAttribute attribute = GetDropDownListAttribute();%>
<% if (attribute != null) {%>
<%= Html.DropDownList(string.Empty, new SelectList(ViewData[attribute.ViewDataKey] as IEnumerable, attribute.DataValueField, attribute.DataTextField, Model), attribute.OptionLabel, attribute.HtmlAttributes) %>
<% }%>
<% else {%>
<%= Html.DisplayForModel() %>
<% }%>

自定义DropDownListAttribute 属性

代码

namespace mvc.Models
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web.Routing;

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class DropDownListAttribute : Attribute, ITemplateField
{
private static string defaultTemplateName;

public DropDownListAttribute(string viewDataKey, string dataValueField) : this(viewDataKey, dataValueField, null)
{
}

public DropDownListAttribute(string viewDataKey, string dataValueField, string dataTextField) : this(viewDataKey, dataValueField, dataTextField, null)
{
}

public DropDownListAttribute(string viewDataKey, string dataValueField, string dataTextField, string optionLabel) : this(DefaultTemplateName, viewDataKey, dataValueField, dataTextField, optionLabel, null)
{
}

public DropDownListAttribute(string viewDataKey, string dataValueField, string dataTextField, string optionLabel, object htmlAttributes) : this(DefaultTemplateName, viewDataKey, dataValueField, dataTextField, optionLabel, htmlAttributes)
{
}

public DropDownListAttribute(string templateName, string viewDataKey, string dataValueField, string dataTextField, string optionLabel, object htmlAttributes)
{
if (string.IsNullOrEmpty(templateName))
{
throw new ArgumentException(Template name cannot be empty.);
}

if (string.IsNullOrEmpty(viewDataKey))
{
throw new ArgumentException(View data key cannot be empty.);
}

if (string.IsNullOrEmpty(dataValueField))
{
throw new ArgumentException(Data value field cannot be empty.);
}

TemplateName = templateName;
ViewDataKey
= viewDataKey;
DataValueField
= dataValueField;
DataTextField
= dataTextField;
OptionLabel
= optionLabel;
HtmlAttributes
= new RouteValueDictionary(htmlAttributes);
}

public static string DefaultTemplateName
{
get
{
if (string.IsNullOrEmpty(defaultTemplateName))
{
defaultTemplateName
= DropDownList;
}

return defaultTemplateName;
}
set
{
defaultTemplateName
= value;
}
}

public string TemplateName { get; private set; }

public string ViewDataKey { get; private set; }

public string DataValueField { get; private set; }

public string DataTextField { get; private set; }

public string OptionLabel { get; private set; }

public IDictionary<string, object> HtmlAttributes { get; private set; }

public object GetSelectedValue(object model)
{
return GetPropertyValue(model, DataValueField);
}

public object GetSelectedText(object model)
{
return GetPropertyValue(model, !string.IsNullOrEmpty(DataTextField) ? DataTextField : DataValueField);
}

private static object GetPropertyValue(object model, string propertyName)
{
if (model != null)
{
PropertyDescriptor property
= GetTypeDescriptor(model.GetType()).GetProperties()
.Cast
<PropertyDescriptor>()
.SingleOrDefault(p
=> string.Compare(p.Name, propertyName, StringComparison.OrdinalIgnoreCase) == 0);

if (property != null)
{
return property.GetValue(model);
}
}

return null;
}

private static ICustomTypeDescriptor GetTypeDescriptor(Type type)
{
return new AssociatedMetadataTypeTypeDescriptionProvider(type).GetTypeDescriptor(type);
}
}
}

自定义DataAnnotationsModelMetadata

代码

public class FieldTemplateMetadata : DataAnnotationsModelMetadata
{
public FieldTemplateMetadata(DisplayAttribute aa, DataAnnotationsModelMetadataProvider provider, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName, DisplayColumnAttribute displayColumnAttribute, IEnumerable<Attribute> attributes) : base(provider, containerType, modelAccessor, modelType, propertyName, displayColumnAttribute)
{
Attributes
= new List<Attribute>(attributes);
Display
= aa;
}

public IList<Attribute> Attributes
{
get;
private set;
}
public DisplayAttribute Display { get; set; }
}

自定义 DataAnnotationsModelMetadataProvider

代码

public class FieldTemplateMetadataProvider : DataAnnotationsModelMetadataProvider
{
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
DataAnnotationsModelMetadata result
= (DataAnnotationsModelMetadata) base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);

string templateName = attributes.OfType<ITemplateField>()
.Select(field
=> field.TemplateName)
.LastOrDefault();
List
<System.Attribute> attributeList = new List<System.Attribute>(attributes);
DisplayAttribute disp
= attributeList.OfType<DisplayAttribute>().FirstOrDefault();
if (disp != null)
{
result.ShortDisplayName
= disp.Order.ToString(); ;
result.Description
= disp.Description;
}

var data= (new FieldTemplateMetadata(disp, this, containerType, modelAccessor, modelType, propertyName, attributes.OfType<DisplayColumnAttribute>().FirstOrDefault(), attributes)

{
TemplateHint = !string.IsNullOrEmpty(templateName) ? templateName : result.TemplateHint,
HideSurroundingHtml
= result.HideSurroundingHtml,
DataTypeName
= result.DataTypeName,
IsReadOnly
= result.IsReadOnly,
NullDisplayText
= result.NullDisplayText,
DisplayFormatString
= result.DisplayFormatString,
ConvertEmptyStringToNull
= result.ConvertEmptyStringToNull,
EditFormatString
= result.EditFormatString,
ShowForDisplay
= result.ShowForDisplay,
ShowForEdit
= result.ShowForEdit,
DisplayName
= result.DisplayName,
Description
= result.Description,
ShortDisplayName
= result.ShortDisplayName,

});

SearchFilterAttribute searchFilterAttribute = attributes.OfType<SearchFilterAttribute>().FirstOrDefault();
if (searchFilterAttribute != null)
{
data.AdditionalValues.Add(
Search, searchFilterAttribute);
}

OrderByAttribute orderByAttribute = attributes.OfType<OrderByAttribute>().FirstOrDefault();
if (orderByAttribute != null)
{
data.AdditionalValues.Add(
OrderBy, orderByAttribute);
}
return data;
}
}

————————————————————————————
作者:王继坤
出处:http://www.wjk3.cn/
本文版权归作者和博客园共有,欢迎转 载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。