1. 程式人生 > >Linux.NET實戰手記—自己動手改泥鰍(下)

Linux.NET實戰手記—自己動手改泥鰍(下)

上回合中,我們不痛不癢的把小泥鰍的資料庫從只能供在Windows下執行的Access資料庫改為支援跨平臺的MYSQL資料庫,毫無營養的修改,本回閤中,我們將把我們修改後得來的專案往Linux中部署、除錯,讓它適應Linux.NET的執行環境。

在本回閤中,我們將討論研究:

  1、由一個謊言引出另一個謊言

  2、遭遇大量大小寫問題怎麼辦

  3、requestValidationMode?

  4、同一個房頂,卻是不同的房間

1、由一個謊言引出另外一個謊言

當我們把小泥鰍部署上Linux之後,首頁一般是沒有問題的(首頁能夠開啟,並且能夠閱讀裡面的文章),但是當我們點選後臺管理時,頁面就開始變得奇怪起來,它沒有像我們想象那樣,出現一個填寫使用者名稱密碼的介面,而是如下圖所示的“問題”頁面:

通過閱讀堆疊跟蹤,我們大概知道程式用一個“AdminPage.CheckLoginAndPermission”的地方進去之後就開始報錯,為此,我們需要先確定這個叫做“CheckLoginAndPermission”的東西到底是Mono或其他三方類庫裡的東西還是我們程式碼裡的。

判斷的方法也挺簡單,對著Visual Studio 按“Ctrl+Shirt+F”,沒錯,就是查詢功能,只要我們能夠在專案中找到相關的程式碼,那就證明改方法是我們專案中自己的東西。

通過搜尋,結果還真讓我們找到癥結的所在,於是,我們就順藤摸瓜的進入到該方法裡面,該方法的程式碼如下:

/// <summary>
/// 檢查登入和許可權 /// </summary> protected void CheckLoginAndPermission() { if (!PageUtils.IsLogin) { HttpContext.Current.Response.Redirect("login.aspx?returnurl=" + HttpContext.Current.Server.UrlEncode(RequestHelper.CurrentUrl)); } UserInfo user
= UserManager.GetUser(PageUtils.CurrentUserId); if (user == null) //刪除已登陸使用者時有效 { PageUtils.RemoveUserCookie(); HttpContext.Current.Response.Redirect("login.aspx?returnurl=" + HttpContext.Current.Server.UrlEncode(RequestHelper.CurrentUrl)); } if (StringHelper.GetMD5(user.UserId + HttpContext.Current.Server.UrlEncode(user.UserName) + user.Password) != PageUtils.CurrentKey) { PageUtils.RemoveUserCookie(); HttpContext.Current.Response.Redirect("login.aspx?returnurl=" + HttpContext.Current.Server.UrlEncode(RequestHelper.CurrentUrl)); } if (PageUtils.CurrentUser.Status == 0) { ResponseError("您的使用者名稱已停用", "您的使用者名稱已停用,請與管理員聯絡!"); } string[] plist = new string[] { "themelist.aspx", "themeedit.aspx", "linklist.aspx", "userlist.aspx", "setting.aspx" ,"categorylist.aspx","taglist.aspx","commentlist.aspx"}; if (PageUtils.CurrentUser.Type == (int)UserType.Author) { string pageName = System.IO.Path.GetFileName(HttpContext.Current.Request.Url.ToString()).ToLower(); foreach (string p in plist) { if (pageName == p) { ResponseError("沒有許可權", "您沒有許可權使用此功能,請與管理員聯絡!"); } } } }
CheckLoginAndPermission

咋眼一看,一個驗證賬戶登入與許可權的方法,如果不滿足則自動的跳轉到各自的頁面,沒有什麼特別的,也沒有什麼問題。但是,程式的異常就是出現在這裡,因此我們需要把他找出來。

各位讀者第一時間想到的可能是馬上按“F5”或者“附加到程序”,依賴Visual Studio 這個強大的IDE來定位哪一步出了問題。但是,別忘了,我們的程式在Windows下是沒有問題的,並且當前的作業系統也不是Windows,因此Visual Studio的功能我們是無法使用的。或許,有些讀者還知道有“Mono Develop”這個IDE,該IDE可以在Linux中使用,可惜,我們的Linux中並沒有安裝這個工具,甚至連Xwindows也沒有安裝,Linux的執行級別也只是“init-3”級別,要弄“Mono Develop”太麻煩了,我們需要一些有趣的手段來定位我們的問題。

先回想一下,既然成功釋出,那就證明專案是成功的編譯,而在執行時出現卻報錯,則表示,這個是一個執行時異常。執行時異常,其實我們也會經常遇到,譬如讓程式讀一個不存在的檔案、資料庫連線字串寫錯之類的,這些都屬於執行時異常,只有程式執行到這一步出現錯誤的時候,程式才終止繼續執行並提示錯誤。

根據這一原理,我們可以自己定義一些“謊言”(手動的新增一些執行時錯誤),讓程式執行到此處終止並提示錯誤,通過比較程式提示的錯誤,我們就可以定位到專案中發生錯誤的哪一行程式碼了,通過一個謊言來引出另外一個謊言。

譬如我在檢查是否登陸這裡新增一個“謊言”。

/// <summary>
    /// 檢查登入和許可權
    /// </summary>
    protected void CheckLoginAndPermission()
    {
        if (!PageUtils.IsLogin)
        {
            HttpContext.Current.Response.Redirect("login.aspx?returnurl=" + HttpContext.Current.Server.UrlEncode(RequestHelper.CurrentUrl));
        }
        UserInfo user = UserManager.GetUser(PageUtils.CurrentUserId);

        //在這裡新增謊言
        var a = decimal.Parse("小蝶驚鴻");

        if (user == null)       //刪除已登陸使用者時有效
        {
            PageUtils.RemoveUserCookie();
            HttpContext.Current.Response.Redirect("login.aspx?returnurl=" + HttpContext.Current.Server.UrlEncode(RequestHelper.CurrentUrl));

        }

        if (StringHelper.GetMD5(user.UserId + HttpContext.Current.Server.UrlEncode(user.UserName) + user.Password) != PageUtils.CurrentKey)
        {
            PageUtils.RemoveUserCookie();
            HttpContext.Current.Response.Redirect("login.aspx?returnurl=" + HttpContext.Current.Server.UrlEncode(RequestHelper.CurrentUrl));
        }

        if (PageUtils.CurrentUser.Status == 0)
        {
            ResponseError("您的使用者名稱已停用", "您的使用者名稱已停用,請與管理員聯絡!");
        }

        string[] plist = new string[] { "themelist.aspx", "themeedit.aspx", "linklist.aspx", "userlist.aspx", "setting.aspx", "categorylist.aspx", "taglist.aspx", "commentlist.aspx" };
        if (PageUtils.CurrentUser.Type == (int)UserType.Author)
        {
            string pageName = System.IO.Path.GetFileName(HttpContext.Current.Request.Url.ToString()).ToLower();

            foreach (string p in plist)
            {
                if (pageName == p)
                {
                    ResponseError("沒有許可權", "您沒有許可權使用此功能,請與管理員聯絡!");
                }
            }
        }
    }
謊言的CheckLoginAndPermission

編譯釋出後再重新整理頁面

我們得到了這個執行時異常,圖明顯的跟之前的不同,那就證明,剛才的異常在此“謊言”的下方。

我們不斷的把我們的“謊言”(手動新增的執行時錯誤)往下挪,直到它把真正的“謊言”(原本的執行時錯誤)引出。

通過這種方法的迭代,我們大概定位到這裡:

再結合它報給我們的錯誤“Object reference not set to an instance of an object”(未將物件例項化),我們可以推演出,這裡有東西為null。在這裡,只有user為需要例項化的類(PageUtils.CurrentKey是一個static的屬性),我們可以猜或許是user為null。

為了驗證,我們可以做如下動作:

通過驗證,我們發現我們的推理是對的,就是user為null造成了此處的失敗。

但是,為什麼user為空呢?或者說,難道說小泥鰍中原程式中沒有對user作出checknull的判斷嗎?我們先追述一下user的來源,user來自於本方法中:

 通過傳入一個CurrentUserId,來獲得user類,而在GetUser方法中,程式碼如下:

        /// <summary>
        /// 獲取使用者
        /// </summary>
        /// <param name="userId"></param>
        /// <returns></returns>
        public static UserInfo GetUser(int userId)
        {
            foreach (UserInfo user in _users)
            {
                if (user.UserId == userId)
                {
                    return user;
                }
            }
            return null;
        }
GetUser

CurrentId實際上還是一個userid,通過比較兩個userid的值來獲得user例項。還在想為什麼沒有得到null嗎?這裡是一個陷阱,我們根本就沒有登陸,所以根本就沒有CurrentUserId(或者說userid的值為null),因此,GetUser方法的輸出東西應該也是為null。

難道小泥鰍沒有對輸出為null的情況作出處理嗎?答案是否定的,小泥鰍中已經有判斷,不然在Windows中就已經報錯了,如果使用者沒有登陸(沒有登陸就必定沒有CurrentUserId了),頁面就跳轉到“login.aspx”頁面(登陸頁面)。

邏輯上當然是這樣的,但是現實卻並非這樣,頁面沒有發生跳轉,或者更確切的說,程式沒有到達Redirect方法之後進行重定向並終止“CurrentUserId”方法中接下來的程式碼。看清楚幕後的“元凶”之後,想要對付它也變得簡單起來,我們只需手動的讓它終止執行方法內接下來的程式碼就可以了。

在Redirect下方加上Return(方法內所有的Redirect都加上):

 然後再次編譯釋出,我們就可以看到我們的登陸頁面了。

2、如果遇到大量大小寫問題怎麼辦?

我們進入後臺管理頁面之後,嘗試新增一篇文章:

為了新增圖片,我們需要點選“插入圖片/檔案”:

然後就……,上面說“'UserControls/upfilemanager.ascx”不存在。

典型的大小寫問題,"UserControls"資料夾的大小寫。解決辦法很簡單,把資料夾名按照大小寫改好就ok。

正如我上回閤中說的,小泥鰍的大小寫還是比較嚴格的,基本上都是大小寫敏感,但是,如果我們現在面對的不是小泥鰍,而是一個比較麻煩專案,裡面充斥著大量的大小寫問題,我們再對此進行地毯式的搜尋並修正就顯得可行性極低了(還不如推倒重寫呢)。

面對這種情況,我們也是有“作弊碼”可以行的,我們可以通過修改jws的指令碼檔案,讓Mono對檔案目錄不區分大小寫(注意,是檔案目錄,SQL語句還是區分的,因為解析SQL語句是資料庫的事情,而不是Mono的事情)。

我們只需開啟“jws”檔案,並把Mono的IOMAP設定為all即可(jws中只需刪掉“#”號)。

重啟一下Jexus,再嘗試新增:

ok,我們又搞定了一個問題。

3、requestValidationMode?

也不記得從什麼時候開始,大概是.NET FrameWork 4.0 吧,我們使用富文字編輯器的時候,提交時會出現“Form表單有危險……”之類的提示,.NET也自動的幫我們驗證從頁面中Post回來的報文,有尖括號之類的敏感字串會自動的被.NET拒絕接收,解決的辦法也很容易,網上是大把大把的,基本上就是把驗證的模式從“4.0”(或更高)改為“2.0”就ok了。

但是,在我們這裡,小泥鰍是基於.NET 2.0哦,所以應該就不會出現上述這種情況吧?!我們先試著新增一點東西:

然後再點選提交:

片刻的廣告之後,我們只能說“嘿嘿~~”了,Mono把小泥鰍用ASP.NET 4.0 來運行了,既然它是使用.NET FrameWork 4.0 的模式來執行,那麼我們也只需使用相應的解決辦法即可。

我在“web.config”中的“httpruntime”節點中加上“requestValidationMode”(在這裡,我直接就用VI加了,無需重新編譯釋出):

 然後我們重新新增文章並儲存,就可以在首頁中找到了我們的文章了:

4、同一個房頂,卻是不同的房間

整好了小泥鰍在Linux.NET中的執行異常之後,我們還需要增加一個對Sqlite資料庫的支援,雖然平時經常聽到這一款的資料庫,但是卻從來都沒有真正的接觸過,直到開始增加此擴充套件的時候還是第一次(初體驗?!),多虧了通用性高的SQL語句和ADO.NET,使我在僅僅知道它有五種資料型別並從Nuget哪裡獲得驅動的情況下就做好了對Sqlite的擴充套件(於是悲劇來了)。

在Windows中測試通過之後,我們迫不及待的往Linux中釋出,然後就:

 

在Linux.NET中,如果遇到DLL明明就在bin目錄中,但是程式卻報找不到或者無法載入之類的,一般要麼就是DLL真的找不到(檔案大小寫問題)、還有其他依賴的DLL沒有成功載入或者是直接不相容造成的。排除了大小寫問題之後,我們找找Mono中有沒有帶有對Sqlite作驅動的dll。

可以發現,Mono中已經自帶了Sqlite的驅動,並且與在MS.NET中是處於兩個不同的名稱空間。此外,這裡還有一個圖片需要讓讀者們看看的:

不僅名稱空間不同,大小寫也是有點點差別,所以,千言萬語都不說了,改吧~~!

至此,小泥鰍的改造基本上完成了,需要程式碼的讀者可以在GitHub中找到(地址在上集中有說),能力有限,如果寫得有不對的歡迎各位讀者留言,有建議或者意見的也歡迎留言,我們下回見~~!

PS:今年是“碼”年,在這裡小蝶驚鴻給各位讀者拜年,祝各位讀者新年快樂,恭喜發財。