解读asp.net中的观察者模式(2)_.Net教程

编辑Tag赚U币
教程Tag:暂无Tag,欢迎添加,赚取U币!

推荐:解读Asp.net教程:设计IP地址屏蔽功能
出于安全考虑,几乎每个动态网站都具备IP地址屏蔽功能,而网上流传的很多关于该功能的教程大都采用字符串保存和验证IP地址,我认为这是不太科学的,我试图找到最佳的设计方案。 “I

第二个问题: 当管理员页面的ajax请求的时候,每两个请求如何保存数据?呵呵,上面那个问题不是说了么,用单件,但是单件是全局存在的,我们的管理员是多个,每个管理员可以决定是否订阅数据,以及什么时候订阅。想起来没?除了全局数据外我们还有Session

在管理页面上我放置一个“开始监视”的按钮,这个按钮使用ajax请求服务器端的一个HttpHandler,在Handler的ProcessRequest方法里这样来做:

以下为引用的内容:
[img]http://www.cnblogs.com/Images/OutliningIndicators/None.gif[/img]
Admin admin = context.Session["monitor_listener"] as Admin;
[img]http://www.cnblogs.com/Images/OutliningIndicators/None.gif[/img]
if(admin == null)
[img]http://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif[/img]
[img]http://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif[/img]
[img]http://www.cnblogs.com/Images/dot.gif[/img]{
[img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img]
admin = new Admin(Monitor.Current);
[img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img]
context.Session["monitor_listener"] = admin;
[img]http://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockEnd.gif[/img]}
[img]http://www.cnblogs.com/Images/OutliningIndicators/None.gif[/img]

 

注意,由于这个Handler需要访问Session,所以你需要让这个Handler继承IRequiresSessionState接口(为什么使用继承而不用实现这个术语?实际上这个接口是一个标记接口,没有任何需要实现的成员,只是标记这个Handler可以访问Session,我不知道为什么MS不使用Attribute,是不是更合理些)

在管理页面还有个一个SetInterval不断的调用一个含有ajax的方法,去请求另外一个Handler,这个Handler将Admin收到的数据返回到web页面,让我们来看看这个Handler的部分实现:

以下为引用的内容:
img]http://www.cnblogs.com/Images/OutliningIndicators/None.gif[/img]
public void ProcessRequest(HttpContext context)
[img]http://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockStart.gif[/img]
[img]http://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif[/img]
[img]http://www.cnblogs.com/Images/dot.gif[/img]{
[img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img]
context.Response.Buffer = true;
[img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img]
context.Response.ExpiresAbsolute = System.DateTime.Now.AddSeconds(-1);
[img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img]
context.Response.Expires = 0;
[img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img]
context.Response.CacheControl = "no-cache";
[img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img]
Admin admin = context.Session["monitor_listener"] as Admin;
[img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img]
if (admin == null || admin.MessageCollection == null ||
admin.MessageCollection.Count <= 0)
[img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img]
return;
[img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img]
string[] messages = new string[admin.MessageCollection.Count];
[img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img]
admin.MessageCollection.CopyTo(messages, 0);
[img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img]
StringBuilder sb = new StringBuilder();
[img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img]
for (int i = 0; i < messages.Length; i )
[img]http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockStart.gif[/img]
[img]http://www.cnblogs.com/Images/OutliningIndicators/ContractedSubBlock.gif[/img]
[img]http://www.cnblogs.com/Images/dot.gif[/img]{
[img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img]
sb.AppendFormat("<li>{0}</li>", messages);
[img]http://www.cnblogs.com/Images/OutliningIndicators/ExpandedSubBlockEnd.gif[/img]
}
[img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img]
admin.MessageCollection.Clear();
[img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img]
context.Session["monitor_listener"] = admin;
[img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img]
context.Response.Write(sb);
[img]http://www.cnblogs.com/Images/OutliningIndicators/InBlock.gif[/img]
context.Response.Flush();
[img]http://www.cnblogs.com/Images/OutliningIndicators/ExpandedBlockEnd.gif[/img]}
[img]http://www.cnblogs.com/Images/OutliningIndicators/None.gif[/img]

[OK,一个在asp.net环境中实现的观察者模式基本上就算完成了,不过上面只有怎样订阅,那什么时候取消订阅了,可以在Session_End事件里面取消订阅

还查看了一些关于长连接的文章,发现这个不错,准备改进一下。

完整的代码稍后提供,希望这块转头能引来一些玉

写完这个Post后本来想把完整代码实现传上来,后来看到不少园友提出异议,看了大家的留言后我也一直在思索:我为什么这样做?当初我是怎样想到这个解决方案的?我在几个解决方案之间做了取舍了么?我这样做是不是矫枉过正了?经过这些思考有了现在的这个Post。

首先我进一步谈一下需求:

这是一个Web Application,有很多客户端向服务器端提交数据(客户端是C 的,以http-post方式向服务器端提交二进制数据,服务器端解析这个二进制包,数据提交很频繁),管理员可以进入监视页面浏览这些数据,数据要即时的,客户端发来一条,管理员屏幕上要马上可以看到,允许多个管理员同时监视即时数据,所有管理员看到的数据都是一样的(目前是这样的,也许以后对管理员要分角色,各角色管理员看到的信息将不同)。

由于数据提交非常频繁,客户要求不允许频繁的数据库操作,所以我将数据保存在一个IList的缓存里面,当这个IList的大小超过了我在配置文件里定义的大小的时候就将数据批量插入到数据库。

下面我将以我当初思考的思路为主线描述:

第一个版本:

以下为引用的内容:
//在程序里我写了一个静态类,这个静态类保存整个程序中共享的一些数据,
相当于原来的//Application对象,但是静态成员是编译期类型检查的
public static ApplicationData
{
//这个队列用来保存客户端传递过来的数据,当队列达到一定长度的时候同步到数据库
public static Queue<DataHead> OperateDataList = new Queue<DataHead>();
//这个List也是保存客户端传递过来的数据的,但它是为监视准备数据的,
//当一个监视页面的请求到来的时候将这个List的数据Response过去,然后Clear这个//List
public static IList<DataHead> MonitorDataList = new List<DataHead>();
}
public class ReciveDataHandler : IHttpHandler
{
//……
Public void ProcessRequest(HttpContext context)
{
//解析从客户端传递过来的数据
DataHead data = GetData(context);
OperateDataList.Add(data);
If(OperateDataList.Count > BufferSize)
{
//将数据写入到数据库
AddToBase();
}
MonitorDataList.Add(data);
}
}
//监视页面从这里获取数据
public class MonitorHandler : IHttpHandler
{
//……
Public void ProcessRequest(HttpContext context)
{
If(MonitorDataList.Count > 0)
{
//将MonitorDataList里的数据Response出去
OutPut();
MonitorDataList.Clear();
}
}
}

说实话,我当初做出这个的时候觉得一点问题都没有,开始的时候客户测试也没有发现任何问题,终于有一天客户和我同时测试部署在同一IIS的时候,问题出现了:只有一个监视页面有数据。看到这个后我还百思不得其解,顺着程序的执行流程一步一步走下去,没有找出任何错误。后来做了下日志,原来MonitorDataList是一个全局共享的,一个在监视把数据Clear了后别人就无法获取数据了。不知道有没有人这样做过:有时候忘记了自己正在做一个web程序,而web程序是一个并发的,对一些共享资源的访问有着微妙的问题,如果没有记住这点,按照程序流程的执行步骤是找不出任何问题的。

怎么办?再一看这不是事件订阅所描述的场景么?所以就有了上一篇Post的Solution。不过那个方案受到不少人质疑,其中金色海洋提出这样的方法:

以下为引用的内容:
Public class ReciveData : IHttpHandler
{
//……….
//将客户端传递过来的数据存入数据库
}

 

Public class MonitorHandler : IHttpHandler
{
//………
//为null的时候说明该管理员第一次监视
If(Session[“id”] == null)
{
//根据时间从服务器取出数据
//并将取出数据的最后一个id保存在session中
Session[“id”] = id;
}
//不为null则说明该管理员已经开始监视了
Else
{
//根据session里保存的最后一个id,取出大于那个id的数据
Session[“id”] = currentId;
}
}

看似这个方案不错,我尝试着将我的程序修改为这样,但是我将上面的代码编写完,我发现我不可以再进行下去了:上面的方案满足不了我的需求,客户明确要求了客户端提交的数据要先缓存然后缓存超过配置大小(这个大小还需要可以在配置文件里面配置,以便可以经过测试找出一个最合理的值),而这种Session记录的方案是依靠数据库来保存数据,这个Session[“id”]就相当于一个游标,这个游标指向的是数据库,那好,我们将Session[“id”]指向缓存数据,但是请注意缓存随时可能超过设置大小而被同步到数据库并被清空。

分享:.Net教程之HTTP状态码200,301,302
跳转非常常用,在哪里都一样,这里的一些说明和用法也如此,不止适用于asp.net,其他语言也会用得到。跳转的目的本来很简单,就是当用户或系统需要时从一个页面转向另一个页面,但自从有了

来源:模板无忧//所属分类:.Net教程/更新时间:2009-08-01
相关.Net教程