项目背景:
要求开发一个篆文识别网站,由于之前做好了WinForm的,把系统直接移植到WebForm上就好。工作比较简单,但确实遇到不少问题。
核心问题是:
篆文识别涉及到用户对原始图片的预处理(例如二值化、去除纹理等等),Win应用可以直接new Bitmap把过渡图都放在内存里,再用PictureBox控件显示出来即可。而Web应用里的图像显示控件是Image,只能通过设置ImageUrl来改变正在显示的图片。也就是说,要想显示图片,必须先把它存储为物理文件。
关键问题:
1.怎么获取原图,就是怎么把Image显示的图像转化为我们可以操作的Bitmap对象?
2.怎么把处理结果保存为物理文件(*.png)?
3.怎么把用户留下的临时文件清空?(前面两个都好办,这个才是关键)
解决方案:
1.两行代码轻松搞定
//获取当前图片 string path = Server.MapPath(imgShow.ImageUrl); Bitmap img = new Bitmap(path);
2.同上
//保存临时文件 string newPath = Server.MapPath("temp/tmp.png"); img.Save(newPath); //设置ImageUrl imgShow.ImageUrl = "~/temp/tmp.png"; //释放资源 img.Dispose();
3.最简单的思路就是:
<1>用SessionID来作为临时文件名(区分不同用户),建立用户与临时文件的联系
<2>当用户注销时,把该用户留下的所有临时文件删掉(删除所有文件名含当前sid的文件)
详细步骤:
<1>没什么好说的,文件名 = Session.SessionID + "_abc.png" 即可
<2>问题来了,如何catch用户注销这个动作,并在这之前执行我们的代码来清理临时文件
我们找到了Global.aspx里的Session_End方法,这个理论上能够满足我们的需求(注意Session_End与Session_OnEnd的区别,这里不再详述)
我们要做的就是在Session_End方法体里面添上clearTmpFiles()处理,它可能是这样子的:
//删除本次会话产生的临时文件 //文件路径 List<string> paths = new List<string>(); paths.Add(Server.MapPath("temp/" + Session.SessionID + "_b.png"));//二值化文件 paths.Add(Server.MapPath("temp/" + Session.SessionID + "_d.png"));//纹理消除文件 paths.Add(Server.MapPath("temp/" + Session.SessionID + "_search.png"));//搜索结果文件 paths.Add(Server.MapPath("temp/" + Session.SessionID + "_p1.png"));//图像分割文件1 paths.Add(Server.MapPath("temp/" + Session.SessionID + "_p2.png"));//图像分割文件2 paths.Add(Server.MapPath("temp/" + Session.SessionID + "_p3.png"));//图像分割文件3 paths.Add(Server.MapPath("temp/" + Session.SessionID + "_p4.png"));//图像分割文件4 //删除 foreach(string path in pathes) File.Delete(path);
现在我们把上面的处理添进Session_End里面了,测试一下...
发现我们失败了,临时文件还在...为什么呢,可能是因为Session_End方法没有执行(该事件没有被触发)
于是疯狂地百度、Google,无果。很多人都遇到了这个问题,但好像所有人都没有解决,其中stackoverflow上面的说法就比较玄乎了,看似靠谱,经实测根本没这回事儿,链接是这个:http://stackoverflow.com/questions/4813462
。。。
不甘心的想了很久,最终找到了原因:
不是因为Session_End没有被触发,而是里面的代码有错误,Session的运行机制决定了这里面的错误不会被开发者发现(对运行时的错误不会有任何提示,也别想用什么Response.Write来尝试调试,因为根本不会有任何反应...导致的结果就是:所有人都以为Session_End没有被触发...)
Web应用的生命周期是这样的:Application_Start -> Session_Start -> Session_End -> Application_End
这就是事件被触发的顺序,A事件occur -> 执行对应方法
Session_End方法执行时遵循的原则是【一旦遇到错误,立即返回】,这就是为什么所有人都以为它没有被触发的原因(完全感受不到嘛T_T)
那么什么情况算是错误?
在Session_End里面调用Server.MapPath()就算错误,没有为什么,这是龟腚,要用它,就必须遵守它
搞清楚问题是什么就好办了,对症下药:
a.当用户连接时(Session_Start)把临时文件路径paths存在Session对象里面
b.当用户注销时候(Session_End)把paths从Session里面取出来,循环删除就好
就像这样:
void Session_Start(object sender, EventArgs e) { // 在新会话启动时运行的代码 //存储path //文件路径 List<string> paths = new List<string>(); paths.Add(Server.MapPath("temp/" + Session.SessionID + "_b.png"));//二值化文件 paths.Add(Server.MapPath("temp/" + Session.SessionID + "_d.png"));//纹理消除文件 paths.Add(Server.MapPath("temp/" + Session.SessionID + "_search.png"));//搜索结果文件 paths.Add(Server.MapPath("temp/" + Session.SessionID + "_p1.png"));//图像分割文件1 paths.Add(Server.MapPath("temp/" + Session.SessionID + "_p2.png"));//图像分割文件2 paths.Add(Server.MapPath("temp/" + Session.SessionID + "_p3.png"));//图像分割文件3 paths.Add(Server.MapPath("temp/" + Session.SessionID + "_p4.png"));//图像分割文件4 Session.Add("paths", paths); } void Session_End(object sender, EventArgs e) { // 在会话结束时运行的代码。 // 注意: 只有在 Web.config 文件中的 sessionstate 模式设置为 // InProc 时,才会引发 Session_End 事件。如果会话模式设置为 StateServer // 或 SQLServer,则不会引发该事件。 //删除本次会话产生的临时文件 //文件路径 List<string> pathes = (List<string>)Session["paths"]; //删除 foreach(string path in pathes) File.Delete(path); }
测试代码:
Session.Abandon();//注销Session
//放在Button的Click里面,点一点立竿见影
经过测试没有问题
总结:
如果你的Session_End“没有被触发”,请往下看:
1.检查配置文件Web.config,如果里面没有下面的内容,就把下面的代码添进去
<sessionState mode="InProc" timeout="1"></sessionState> //说明:<sessionState>是<system.web>的子元素,其中timeout属性表示Session的过期时间,单位是分钟,上面的意思是用户一分钟在页面无任何操作则Session失效
2.检查你的方法体里面有没有出现Server的相关调用(如Server.MapPath("xxx");),如果有,请看上面的解决方法
3.你的操作是不是只能写在Session_End里面?善后操作能不能通过用户注销或者关闭浏览器时弹出确认框点击确定来触发?如果Session_End还是不能正常工作,不妨试试这种方式
P.S.为什么不用上面提到的确认框的方式来解决这个问题?
因为浏览器兼容问题,主流的浏览器有IE、FF、GC...很难对所有浏览器考虑全面,本机用的是GC,很多JS代码都不能达到预期效果
另外,用确认框来善后需要考虑:浏览器兼容性问题、异常退出问题(用任务管理器暴力关掉、Alt + F4、任务栏右键关闭...)
用Session_End善后的优点是:无论用户是正常注销还是异常退出,其sid失效的时候都会触发Session_End,因为Session是保存在Server的,不会出现意外