用户注册



邮箱:

密码:

用户登录


邮箱:

密码:
记住登录一个月忘记密码?

发表随想


还能输入:200字

csharp    -  云代码空间

——

asp.net实现延迟操作(利用缓存项的移除通知)

2014-05-19|1967阅||

摘要:我看过一些ASP.NET的书,也看过一些人写的关于Cache方面的文章,基本上,要么是一带而过,要么只是举个毫无实际意义的示例。 可惜啊,这么强大的特性,我很少见到有人把它用起来。 今天,我就举个有实际意义的示例,再现Cache的强大功能! 我有这样一个页面,可以

我看过一些ASP.NET的书,也看过一些人写的关于Cache方面的文章,基本上,要么是一带而过,要么只是举个毫无实际意义的示例。 可惜啊,这么强大的特性,我很少见到有人把它用起来。

今天,我就举个有实际意义的示例,再现Cache的强大功能!

我有这样一个页面,可以让用户调整(上下移动)某个项目分支记录的上线顺序:

当用户需要调整某条记录的位置时,页面会弹出一个对话框,要求输入一个调整原因,并会发邮件通知所有相关人员。

由于界面的限制,一次操作(点击上下键头)只是将一条记录移动一个位置,当要对某条记录执行跨越多行移动时,必须进行多次移动。 考虑到操作的方便性以及不受重复邮件的影响,程序需要实现这样一个需求: 页面只要求输入一次原因便可以对一条记录执行多次移动操作,并且不要多次发重复邮件,而且要求将最后的移动结果在邮件中发出来。

这个需求很合理,毕竟谁都希望操作简单。

那么如何实现这个需求呢?这里要从二个方面来实现,首先,在页面上我们应该要完成这个功能,对一条记录只弹一次对话框。 由于页面与服务端的交互全部采用Ajax方式进行(不刷新),状态可以采用JS变量来维持,所以这个功能在页面中是很容易实现。 再来看一下服务端,由于服务端并没有任何状态,当然也可以由页面把它的状态传给服务端,但是,哪次操作是最后一次呢? 显然,这是无法知道的,最后只能修改需求,如果用户在2分钟之内不再操作某条记录时,便将最近一次操作视为最后一次操作。

基于新的需求,程序必须记录用户的最近一次操作,以便在2分钟不操作后,发出一次邮件,但要包含第一次输入的原因, 还应包含最后的修改结果哦。

该怎么实现这个需求呢? 我立即就想到了ASP.NET Cache,因为我了解它,知道它能帮我完成这个功能。下面我来说说在服务端是如何实现的。

整个实现的思路是:
1. 客户端页面还是每次将记录的RowGuid, 调整方向,调整原因,这三个参数发到服务端。
2. 服务端在处理完顺序调整操作后,将要发送的邮件信息Insert到Cache中,同时提供slidingExpiration和onRemoveCallback参数。
3. 在CacheItemRemovedCallback回调委托中,忽略CacheItemRemovedReason.Removed的通知,如果是其它的通知,则发邮件。

为了便于理解,我特意为大家准备了一个示例。整个示例由三部分组成:一个页面,一个JS文件,服务端代码。先来看页面代码: 


<body>
    <p> 为了简单,示例页面只处理一条记录,且将记录的RowGuid直接显示出来。<br /> 实际场景中,这个RowGuid应该可以从一个表格的【当前选择行】中获取到。 </p>
    <p> 当前选择行的 RowGuid = <span id="spanRowGuid"><%= Guid.NewGuid().ToString() %></span><br /> 当前选择行的 Sequence= <span id="spanSequence">0</span>
    </p>
    <p><input type="button" id="btnMoveUp" value="上移" />
        <input type="button" id="btnMoveDown" value="下移" />
    </p>
</body>


页面的显示效果如下:

处理页面中二个按钮的JS代码如下: 


// 用户输入的调整记录的原因 var g_reason = null;

$(function(){
    $("#btnMoveUp").click( function() { MoveRec(-1); } );
    $("#btnMoveDown").click( function() { MoveRec(1); } );
}); function MoveRec(direction){ if( ~~($("#spanSequence").text()) + direction < 0 ){
        alert("已经不能上移了。"); return;
    } if( g_reason == null ){
        g_reason = prompt("请输入调整记录顺序的原因:", "由于什么什么原因,我要调整..."); if( g_reason == null ) return;
    }
    
    $.ajax({
        url: "/AjaxDelaySendMail/MoveRec.fish", data: { RowGuid: $("#spanRowGuid").text(), Direction: direction, Reason: g_reason
        }, type: "POST", dataType: "text", success: function(responseText){
            $("#spanSequence").text(responseText);
        }
    });
}

说明:在服务端,我使用了我在【用Asp.net写自己的服务框架】那篇博客中提供的服务框架, 服务端的全部代码是这个样子的:(注意代码中的注释) 

 
/// <summary>
/// 移动记录的相关信息。
/// </summary>
public class MoveRecInfo {
    public string RowGuid;
    public int Direction;
    public string Reason;
}


[MyService]
public class AjaxDelaySendMail {
    [MyServiceMethod]
    public int MoveRec(MoveRecInfo info) {
        // 这里就不验证从客户端传入的参数了。实际开发中这个是必须的。

        // 先来调整记录的顺序,示例程序没有数据库,就用Cache来代替。
        int sequence = 0;
        int.TryParse(HttpRuntime.Cache[info.RowGuid] as string, out sequence);
        // 简单地示例一下调整顺序。
        sequence += info.Direction;
        HttpRuntime.Cache[info.RowGuid] = sequence.ToString();


        string key = info.RowGuid + "_DelaySendMail";
        // 这里我不直接发邮件,而是把这个信息放入Cache中,并设置2秒的滑过过期时间,并指定移除通知委托
        // 将操作信息放在缓存,并且以覆盖形式放入,这样便可以实现保存最后状态。
        // 注意:这里我用Insert方法。
        HttpRuntime.Cache.Insert(key, info, null, Cache.NoAbsoluteExpiration,
                                 TimeSpan.FromMinutes(2.0), CacheItemPriority.NotRemovable, MoveRecInfoRemovedCallback);

        return sequence;
    }

    private void MoveRecInfoRemovedCallback(string key, object value, CacheItemRemovedReason reason) {
        if( reason == CacheItemRemovedReason.Removed )
            return;        // 忽略后续调用HttpRuntime.Cache.Insert()所触发的操作

        // 能运行到这里,就表示是肯定是缓存过期了。
        // 换句话说就是:用户2分钟再也没操作过了。

        // 从参数value取回操作信息
        MoveRecInfo info = (MoveRecInfo)value;
        // 这里可以对info做其它的处理。

        // 最后发一次邮件。整个延迟发邮件的过程就处理完了。
        MailSender.SendMail(info);
    }
}

为了能让JavaScript能直接调用C#中的方法,还需要在web.config中加入如下配置:


<httpHandlers>
    <add path="*.fish" verb="*" validate="false" type="MySimpleServiceFramework.AjaxServiceHandler"/>
</httpHandlers> 


好了,示例代码就是这些。如果您有兴趣,可以在本文的结尾处下载这些示例代码,自己亲自感受一下利用Cache实现的【延迟处理】的功能。

其实这种【延迟处理】的功能是很有用的,比如还有一种适用场景:有些数据记录可能需要频繁更新,如果每次更新都去写数据库,肯定会对数据库造成一定的压力, 但由于这些数据也不是特别重要,因此,我们可以利用这种【延迟处理】来将写数据库的时机进行合并处理, 最终我们可以实现:将多次的写入变成一次或者少量的写入操作,我称这样效果为:延迟合并写入

这里我就对数据库的延迟合并写入提供一个思路:将需要写入的数据记录放入Cache,调用Insert方法并提供slidingExpiration和onRemoveCallback参数, 然后在CacheItemRemovedCallback回调委托中,模仿我前面的示例代码,将多次变成一次。不过,这样可能会有一个问题:如果数据是一直在修改,那么就一直不会写入数据库。 最后如果网站重启了,数据可能会丢失。如果担心这个问题,那么,可以在回调委托中,遇到CacheItemRemovedReason.Removed时,使用计数累加的方式,当到达一定数量后, 再写入数据库。比如:遇到10次CacheItemRemovedReason.Removed我就写一次数据库,这样就会将原来需要写10次的数据库操作变成一次了。 当然了,如果是其它移除原因,写数据库总是必要的。注意:对于金额这类敏感的数据,绝对不要使用这种方法。

再补充二点:
1. 当CacheItemRemovedCallback回调委托被调用时,缓存项已经不在Cache中了。
2. 在CacheItemRemovedCallback回调委托中,我们还可以将缓存项重新放入缓存。
有没有想过:这种设计可以构成一个循环?如果再结合参数slidingExpiration便可实现一个定时器的效果。

关于缓存的失效时间,我要再提醒一点:通过absoluteExpiration, slidingExpiration参数所传入的时间,当缓存时间生效时,缓存对象并不会立即移除, ASP.NET Cache大约以20秒的频率去检查这些已过时的缓存项。

顶 2踩 1收藏
文章评论
    发表评论

    个人资料

    • 昵称: csharp
    • 等级: 中级程序员
    • 积分: 240
    • 代码: 2 个
    • 文章: 4 篇
    • 随想: 0 条
    • 访问: 2 次
    • 关注

    最新提问

      站长推荐