項目背景:
要求開發一個篆文識別網站,由於之前做好了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的,不會出現意外