1. [C#: yield return] #1. How It Work ?

    C# 常常拿來跟 Java 比較,在 .NET 1.1 時常常是不相上下,而 .NET 又因為較年輕 & 頂著 Microsoft 的名號,往往被當成玩具一樣,不過 Microsoft 的確是在 .NET 及 C# 下了很多功夫,作了很多 Sun 不願意在 Java 身上作的事,這次要探討 的 yield returnIEnumerable<T> 這搭配的 Interface 就是一例…。

    Java 在過去的版本,往往為了跨平台,把修改 VM 規格視為大忌,連帶的連語法修改都一樣,即使不影響編譯出來的 bytecode 相容性也是 一樣不肯改。而 .NET 就為了語法簡潔,編譯器往往是讓步的一方,因此 C# 有相當多的 Syntax Sugar,讓你寫起 CODE 爽一點…。你只要寫簡單的 CODE,編譯器會幫你轉成 較煩雜的 CODE,有點像是文言文或是成語那樣的味道。古代文人常常用簡單的四個字,就有一大票引申的意義跑出來…,寫作文時只要 套上成語,就代表了成語背後的故事,寓意。

    yield return” 算是最甜的甜頭了,因為編譯器替你翻出來的 code 整整一大串。先來看個簡單的例子,如果我想實作 IEnumerator<T> Interface, 照順序輸出 1 ~ 100 的數字,正統的 C# code 看起來要像這樣:

    用 IEnumerator 依序傳回 1 ~ 100 的數字

    
    public class EnumSample1 : IEnumerator<int>
    {
        private int _start = 1;
        private int _end = 100;
        private int _current = 0;
    
        public EnumSample1(int start, int end)
        {
            this._start = start;
            this._end = end;
            this.Reset();
        }
    
        public int Current
        {
            get { return this._current; }
        }
    
        public void Dispose()
        {
        }
    
        object System.Collections.IEnumerator.Current
        {
            get { return this._current; }
        }
    
        public bool MoveNext()
        {
            this._current++;
            return !(this._current > this._end);
        }
    
        public void Reset()
        {
            this._current = 0;
        }
    }
    
    

    好不容易寫好 IEnumerator 之後,再來是拿來用,一筆一筆印出來:

    取得 IEnumerator 物件後,依序取出裡面的數字

    
    EnumSample1 e = new EnumSample1(1, 100);
    
    while (e.MoveNext())
    {
        Console.WriteLine("Current Number: {0}", e.Current);
    }
    
    

    不過如果只是要列出 1 ~ 100,大部份的人都不會想這樣寫吧? 直接用計概第一堂教你的 loop 不就好了? 程式碼如下:

    送分題: 用 LOOP 印出 1 ~ 100 的數字

    
    for (int current = 1; current <= 100; current++)
    {
        Console.WriteLine("Current Number: {0}", current);
    }
    
    

    兩個範例都沒錯啊,那為什麼要用 IEnumerator ? 其實 IEnumerator 並不是 Microsoft 發明的,在四人幫寫的 經典書籍 (Design Patterns) 裡就有這麼一個設計模式: Iterator,它的目的 很明確:

    “毋須知曉聚合物件的內部細節,即可依序存取內含的每一個元素。”

    (摘自 物件導向設計模式 Design Patterns 中文版,葉秉哲 譯)

    這裡指的 “聚合物件” 就是指 .NET 的 Collection, List, Array 等這類物件。意思是你不需要管 collection 裡每一個物件是怎麼擺的,用什麼結構處理的,用什麼邏輯或演算法處理的,我就只管照你安排好的順序一個一個拿出來就好。沒錯,這就是它主要的目的。換 另一個說法,就是我們希望把物件巡訪的順序 (iteration) 跟依序拿到物件後要作什麼事 (process) 分開,那你就得參考 Iterator Pattern。 不用? 那只好讓你的 iteration / process 混在一起吧。

    差別在那? 我們再來看第二個例子。如果題目改一下,要列出 1 ~ 100 的數字,但如果不是 2 的倍數,也不是 3 的倍數,就跳過去。先來 看看 Loop 的版本:

    進階送分題,用LOOP印出 1~100 之中,2 或 3 的倍數

    
    for (int current = 1; current <= 100; current++)
    {
        bool match = false;
    
        if (current % 2 == 0) match = true;
        if (current % 3 == 0) match = true;
    
        if (match == true)
        {
            Console.WriteLine("Current Number: {0}", current);
        }
    }
    
    

    再來看看 IEnumerator 的版本:

    IEnumerator 列出 1 ~ 100 中 2 或 3 的倍數

    
    public class EnumSample2 : IEnumerator<int>
    {
        private int _start = 1;
        private int _end = 100;
        private int _current = 0;
    
        public EnumSample2(int start, int end)
        {
            this._start = start;
            this._end = end;
            this.Reset();
        }
    
        public int Current
        {
            get { return this._current; }
        }
    
        public void Dispose()
        {
        }
    
        object System.Collections.IEnumerator.Current
        {
            get { return this._current; }
        }
    
        public bool MoveNext()
        {
            do {
                this._current++;
            } while(this._current %2 > 0 && this._current %3 > 0);
            return !(this._current > this._end);
        }
    
        public void Reset()
        {
            this._current = 0;
        }
    }
    
    

    而扣掉 IEnumerator 的部份,要把數字印出來的程式碼則完全沒有改變:

    取出 IEnumerator 的每個數字,印到畫面上

    
    EnumSample2 e = new EnumSample2(1, 100);
    
    while (e.MoveNext())
    {
        Console.WriteLine("Current Number: {0}", e.Current);
    }
    
    

    可以看的到,Loop 版本的確是把 iteration 跟 process 的 code 完全混在一起了,未來任何一方的邏輯要抽換都很 麻煩,而 IEnumerator 則不會,分的很清楚,不過… 這 Code 會不會太 “髒” 了一點啊…? 試問一下,有誰會這麼 勤勞,都用 IEnumerator 來寫 Code? 有的話請留個言,讓我崇拜一下…。

    屁話講了一堆,最後就是要帶出來 “的確有魚與熊掌得兼的方法”,怎麼作? 來看看用 C# 的 yield return 版本的程式碼:

    傳回 IEnumerable 的 METHOD (不用再寫 CLASS,實作 IEnumerator 了)

    
    public static IEnumerable<int> YieldReturnSample3(int start, int end)
    {
        for (int current = 1; current <= 100; current++)
        {
            bool match = false;
    
            if (current % 2 == 0) match = true;
            if (current % 3 == 0) match = true;
    
            if (match == true)
            {
                yield return current;
            }
        }
    }
        
    

    用 foreach 搭配 IEnumerable 印出每一筆數字

    
    foreach (int current in YieldReturnSample3(1, 100))
    {
        Console.WriteLine("Current Number: {0}", current);
    }
    
    
    

    真是太神奇了,安德魯。如何? 完美的結合兩者的優點,這種 code 實在是令人挑不出什麼缺點… 真是優雅… 不過念過 系統程式 的人一定都會吶悶… 這樣的程式執行方式,不就 完全的違背了一般結構化程式典型的 function call / return 的鐵律了? 程式呼叫某個 function 就應該完全執行完 才能 return 啊,怎麼能 yield return 後,跑完一圈又回到剛才執行到一半的 function 繼續跑,然後再 yield return ? 好像同實有兩段獨立的邏輯在運作… 還可以在兩者之間跳來跳去?

    這就是 C# compiler 猛的地方了。搬出 reflector 來看看編譯出來的 code, 再被反組譯回來變成什麼樣子:

    反組譯 YieldReturnSample3

    
    public static IEnumerable<int> YieldReturnSample3(int start, int end)
    {
        <YieldReturnSample3>d__0 d__ = new <YieldReturnSample3>d__0(-2);
        d__.<>3__start = start;
        d__.<>3__end = end;
        return d__;
    }
    
    

    耶? 看到一個多出來的 class: <YieldReturnSample3>d__0 … 再看看它的 class 長啥樣:

    編譯器自動產生的 IEnumerator 衍生類別

    
    [CompilerGenerated]
    private sealed class <YieldReturnSample3>d__0 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable
    {
        // Fields
        private int <>1__state;
        private int <>2__current;
        public int <>3__end;
        public int <>3__start;
        private int <>l__initialThreadId;
        public int <current>5__1;
        public bool <match>5__2;
        public int end;
        public int start;
    
        // Methods
        [DebuggerHidden]
        public <YieldReturnSample3>d__0(int <>1__state)
        {
            this.<>1__state = <>1__state;
            this.<>l__initialThreadId = Thread.CurrentThread.ManagedThreadId;
        }
    
        private bool MoveNext()
        {
            switch (this.<>1__state)
            {
                case 0:
                    this.<>1__state = -1;
                    this.<current>5__1 = 1;
                    while (this.<current>5__1 <= 100)
                    {
                        this.<match>5__2 = false;
                        if ((this.<current>5__1 % 2) == 0)
                        {
                            this.<match>5__2 = true;
                        }
                        if ((this.<current>5__1 % 3) == 0)
                        {
                            this.<match>5__2 = true;
                        }
                        if (!this.<match>5__2)
                        {
                            goto Label_0098;
                        }
                        this.<>2__current = this.<current>5__1;
                        this.<>1__state = 1;
                        return true;
                    Label_0090:
                        this.<>1__state = -1;
                    Label_0098:
                        this.<current>5__1++;
                    }
                    break;
    
                case 1:
                    goto Label_0090;
            }
            return false;
        }
    
        [DebuggerHidden]
        IEnumerator<int> IEnumerable<int>.GetEnumerator()
        {
            Program.<YieldReturnSample3>d__0 d__;
            if ((Thread.CurrentThread.ManagedThreadId == this.<>l__initialThreadId) && (this.<>1__state == -2))
            {
                this.<>1__state = 0;
                d__ = this;
            }
            else
            {
                d__ = new Program.<YieldReturnSample3>d__0(0);
            }
            d__.start = this.<>3__start;
            d__.end = this.<>3__end;
            return d__;
        }
    
        [DebuggerHidden]
        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.System.Collections.Generic.IEnumerable<System.Int32>.GetEnumerator();
        }
    
        [DebuggerHidden]
        void IEnumerator.Reset()
        {
            throw new NotSupportedException();
        }
    
        void IDisposable.Dispose()
        {
        }
    
        // Properties
        int IEnumerator<int>.Current
        {
            [DebuggerHidden]
            get
            {
                return this.<>2__current;
            }
        }
    
        object IEnumerator.Current
        {
            [DebuggerHidden]
            get
            {
                return this.<>2__current;
            }
        }
    }
    
    

    耶? 不就完全跟之前手工寫的 IEnumerator 一樣嘛? 只不過這個 IEnumerator 是自動產生出來的,不是手寫的…。 畢竟是機器 產生的 CODE,總是沒那麼精簡。想到了嗎? 沒錯,這就是 C# compiler 送給你的 syntax sugar …,你可以腦袋裡想像著計概課 入門時教你的 LOOP 那樣簡單的想法,compiler 就幫你換成 IEnumerator 的實作方式,讓你隨隨便便就可以跟別人宣稱:

    看! 我的程式有用到 Iterator 這個設計模式喔…

    聽起來好像很臭屁的樣子… 哈哈! 如果是在真的用的到 Iterator Patterns 的 情況下,真的是可以很臭屁的拿出來炫耀一下。不過,我幹嘛突然講起 yield return ? 各位看的過程中有沒有聯想到 前幾篇 POST 講的 Thread Sync 那兩篇文章 ( #1, #2 ) ? IEnumerator 跟 Thread Sync 又有什麼關係? 賣個關子,下篇繼續!

    2008/09/18 系列文章: Inside C# Yield Return .NET C# Tips 多執行緒 技術隨筆 物件導向

  2. 莫明奇妙的錯誤訊息: 找不到 VJSharpCodeProvider ?

    話說前陣子處理了 BlogEngine.NET 升級到 1.4.5.0,另外也寫了 SecurePost.cs 這個 extension, 其時都碰過這個鳥問題,只是一直沒去理它而以。接下來為了要改 PostViewCounter.cs (BE extension, too), 又碰到... 於是就認真的研究了一下...。

    過程是這樣,為了建立 BlogEngine 的開發環境,首先我從官方網站下載了 source code, 解開後編譯都沒問題,OK。

    接下來 WEB 的部份我把網站上的 source code 搬過來 (不包含 ~/App_Data,太大了),編譯也 OK。

    不過我要改 Counter 的 Code 啊,沒有一些 SAMPLE DATA 很難測試,只好把資料檔也搬過來.. 結果 Visual Studio 2008 就冷冷的回了這訊息給我:

    (0): Build (web): The CodeDom provider type "Microsoft.VJSharp.VJSharpCodeProvider, VJSharpCodeProvider, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" could not be located.

     

    我沒有漏貼前面的訊息... 的確是沒有檔名,也沒有行號(0)。我最不能忍受的就是沒頭沒尾的 ERROR MESSAGE 了。除了告訴你 "掛掉了" 之外,無頭無腦的對於追查問題實在沒什麼幫助。只好靠自己了...。雖然這是個 compile error message,不過我要 RUN 的畢竟是個 web site, 不編譯也是可以跑,除了那個惱人的錯誤訊息之外,要執行倒是沒問題。只不過編譯失敗,我就不能設中斷點,直接 F5 執行測試。雖然可以另外手動 Attach Process 的方式來除錯,不過每次都要這樣搞實在是很煩..

     

    仔細想了想,沒錯,我是沒裝 Visual J#。不過我的確沒要用 Visual J# 啊,如果真的用到 J# 的話,出這訊息是應該的。訊息沒有原始檔? 也沒有錯誤行號? 那問題應該是 Global 的範圍,第一個想到的就是 web.config 是不是定義了 CodeDom 或是指定了相關的 CodeProvider ? 無奈查了一遍沒看到,VS2008 的 PROJECT 設定也沒看到引用任何 J# 相關的 LIB...

     

    已經到了死馬當活馬醫的地步... 開始亂找一通碰碰運氣。搜尋了一下有沒有 *.java 的檔? OUCH,還真的有... 在 ~/App_Data/files 下找到我古董檔案,研究所時代寫的 Java Applet .... 順手試一下,刪掉後還真的就過了? 這個無頭無腦的問題,就在不知不覺中找到 solution, case closed!

     

    怒... 這樣也算? 找到 .java 的程式碼,去找 VJ# 來編譯還說的過去,不過找 "source code" 找到 ~/App_Data 實在是太超過了一點... 好歹也列個要編譯那個檔案,然後找不到對應的 CodeProvider,這樣要排除問題也簡單一點...

     

    結論是: 各位別太鐵齒,看來 ~/App_Data 下的檔案也是不能亂塞的...

    2008/09/10 .NET ASP.NET BlogEngine.NET Tips 技術隨筆 有的沒的

  3. BlogEngine Extension: Secure Post v1.0

    因為家裡大人開出條件,除非新的 BLOG 系統 (就是我在用的 BlogEngine 啦) 有特定文章要輸入密碼才能看的功能,否則她就不想換系統了 (原來是用 CommunityServer 2007)。要弄密碼其實很簡單,不過過去試過 IIS 加上整合式驗證... 弄到最後該看的人看不到,也沒擋到該擋的人而作罷...。

     

    仔細想了想大人的需求,要的就是簡單的控制機制。不需要先建立帳號,也不需要登入,就是特定幾篇文章要輸入暗號才能看到內容,就這樣而以。無耐 BlogEngine 還算很年輕,替它寫的 Extension 也還不多,官方網站提供了幾個 Extension 列表,找到最接近的是這個: Password Protected Post... 不過它是以登入 BE 為使用者認證的方式,再依照 ROLE 跟 CATEGORY 的配對為授權方式,來控制那些讀者能看到那些文章...。就是不想要替每個人建帳號啊,看來只好自己寫了... Orz。

     

    以往都是想要作什麼很簡單,難是難在把它作出來..。現在都反過來了,工具越來越強,系統也越來越完整,難的反而是思考要怎麼作,程式碼沒幾行就搞定了。之前的文章介紹過 BlogEngine 的 Extension 機制,這次就實際來試看看。我要寫的東西很簡單,就一組密碼就好,要有夠簡單的方式讓大人能夠指定那幾篇文章是要保護的,而所有的人 (已登入的除外) 只能看到提示輸入密碼的訊息,密碼打對了才會顯示文章內容。至於密碼要不要加密? 會不會被竊聽? 不重要啦,只要保護不要遜到按右鍵簡示原始碼,密碼跟內容都看光光了就好。

     

    順手寫了幾行 CODE,先驗證一下最基本的動作做不做的到 (POC: Prove Of Concept)。第一步是先把顯示內容的動作攔下來,換成制示的輸入密碼訊息... 這個簡單,沒幾行就搞定了:

     

    image

    直接從 CodePlex 抓下來的 Source Code, 解壓縮完就可以寫了。加上這段 CODE 並不難,整個 Extension 只有這樣而以:

     

    修改 POST 內容,改成提示輸出密碼的畫面[copy code]
    [Extension("SecurePost", "1.0", "<a href=\"http://columns.chicken-house.net\">chicken</a>")]public class SecurePost{    static SecurePost()    {        Post.Serving += new EventHandler<ServingEventArgs>(Post_Serving);    }    private static void Post_Serving(object sender, ServingEventArgs e)    {        Post post = sender as Post;        StringBuilder bodySB = new StringBuilder();        {           // 略。透過 bodySB 輸出 HTML        }        e.Body = bodySB.ToString();    }}
    
       1:  [Extension("SecurePost", "1.0", "<a href=\"http://columns.chicken-house.net\">chicken</a>")]
    
       2:  public class SecurePost
    
       3:  {
    
       4:      static SecurePost()
    
       5:      {
    
       6:          Post.Serving += new EventHandler<ServingEventArgs>(Post_Serving);
    
       7:      }
    
       8:   
    
       9:      private static void Post_Serving(object sender, ServingEventArgs e)
    
      10:      {
    
      11:          Post post = sender as Post;
    
      12:          StringBuilder bodySB = new StringBuilder();
    
      13:          {
    
      14:             // 略。透過 bodySB 輸出 HTML
    
      15:          }
    
      16:          e.Body = bodySB.ToString();
    
      17:      }
    
      18:  }
    

     

     

    看起來 CODE 還不少,不過算一算真正在作事的都是在湊那堆 HTML ... 關鍵只有一開始去攔 Post.Serving 事件,接到自己的事件處理器 Post_Serving( ) 上,之後所有會輸出 Post 內容的地方,都會觸發這個事件。然後只要在事件處理器內去調整 Post 內容就可以了。

     

    好,好的開始是成功的一半,已經完成 1/3 了 (什麼???) 第一部份的 CODE 產生的 HTML,會引導使用者輸入密碼,按下 [GO] 之後,就會連到 POST 的網址了。不過除了原本網址之外 (post.AbsoluteLink) 後面還要加上 "?pwd=xxxxxx" 帶上使用者輸入的密碼。前面講過我只要最基本的防護,其它進階的安全問題就不理它了。我只要掌握兩個原則:

    1. 密碼一定要在 SERVER 端確認 (不能讓不知道密碼的人 view source 就找到密碼)
    2. 沒輸入密碼前不能在 CLIENT 端出現 POST 內容 (不能單純的用 DHTML 把文章內容 "藏" 起來)

    另外補一件事,我也不要讓全部的文章都用這種機制保護。只要有特別標示的 POST 要密碼就好。看到 BlogEngine 內建的 BreakPost 這個擴充程式,我就仿照它的作法,內文找到特定字串就啟用。我定的規則是整篇 POST 內容開頭一定要是 "[password]" 才會啟用密碼保護機制。

    既然這樣,第二步也很簡單。如果密碼對,一切照原狀顯示內容。密碼不對的話就一樣攔下來...。程式碼.... 只是在第一步的程式碼多了... 兩行...

     

    加上檢查密碼的 CODE[copy code]
        private static void Post_Serving(object sender, ServingEventArgs e)    {        Post post = sender as Post;        if (HttpContext.Current.Request["pwd"] == Password) return;        if (!e.Body.StartsWith("[password]", StringComparison.CurrentCultureIgnoreCase)) return;        StringBuilder bodySB = new StringBuilder();        {           // 略。透過 bodySB 輸出 HTML        }        e.Body = bodySB.ToString();    }
    
       1:  private static void Post_Serving(object sender, ServingEventArgs e)
    
       2:  {
    
       3:      Post post = sender as Post;
    
       4:      if (HttpContext.Current.Request["pwd"] == Password) return;
    
       5:      if (!e.Body.StartsWith("[password]", StringComparison.CurrentCultureIgnoreCase)) return;
    
       6:      StringBuilder bodySB = new StringBuilder();
    
       7:      {
    
       8:         // 略。透過 bodySB 輸出 HTML
    
       9:      }
    
      10:      e.Body = bodySB.ToString();
    
      11:  }
    

     

    啥米? 就是第一部份的 CODE 加上第四及第五行就搞定了? 程式不挑的話,現在已經寫完了... 哈哈! 上面的輸入密碼畫面,輸入正確密碼後就可以看到文章內容了。我特地連網址列一起複製下來,在網址列上會看到密碼明碼。照道理應該是要先 HASH 啦,不過 CLIENT SIDE 跟 SERVER SIDE 都要有同樣的 HASH 機制才行,想用 MD5 / SHA256 之類的來算,無耐 CLIENT 要弄這些也是很煩,就決定不理它了...。明碼就明碼吧,執行後的畫面像這樣:

     

    image

     

    剩下的部份就沒什麼了,想想加上去好了。就是透過 BlogEngine 的 Extension Manager,讓使用者可以簡單的調整參數。要讓使用者自定的參數只有三個:

    1. 文章內容被保護時,要顯示的訊息
    2. 密碼提示
    3. 真正的密碼

    這些東西自己做的話,就還得想要開檔案或寫資料庫,有點小囉唆,不過已經有 Extension Manager 了,只要在原本的 static constructor 再加幾行就搞定:

     

    加上 Extension 接受的設定參數,及初始值[copy code]
        static SecurePost()    {        Post.Serving += new EventHandler<ServingEventArgs>(Post_Serving);        ExtensionSettings settings = new ExtensionSettings("SecurePost");        settings.AddParameter(            "SecurePostMessage",            "顯示訊息:");        settings.AddParameter(            "PasswordHint",            "密碼提示:");        settings.AddParameter(            "PasswordValue",            "指定密碼:");        settings.AddValues(new string[] {            "本篇文章已受密碼保護,請依照題示輸入密碼。",             "一二三四",            "1234"});        settings.IsScalar = true;        settings.Help = "用密碼保護文章的內容。";        ExtensionManager.ImportSettings(settings);        _settings = ExtensionManager.GetSettings("SecurePost");    }
    
       1:  static SecurePost()
    
       2:  {
    
       3:      Post.Serving += new EventHandler<ServingEventArgs>(Post_Serving);
    
       4:      ExtensionSettings settings = new ExtensionSettings("SecurePost");
    
       5:      settings.AddParameter(
    
       6:          "SecurePostMessage",
    
       7:          "顯示訊息:");
    
       8:      settings.AddParameter(
    
       9:          "PasswordHint",
    
      10:          "密碼提示:");
    
      11:      settings.AddParameter(
    
      12:          "PasswordValue",
    
      13:          "指定密碼:");
    
      14:      settings.AddValues(new string[] {
    
      15:          "本篇文章已受密碼保護,請依照題示輸入密碼。", 
    
      16:          "一二三四",
    
      17:          "1234"});
    
      18:      settings.IsScalar = true;
    
      19:      settings.Help = "用密碼保護文章的內容。";
    
      20:      ExtensionManager.ImportSettings(settings);
    
      21:      _settings = ExtensionManager.GetSettings("SecurePost");
    
      22:  }
    

     

    我已經很努力的多撐幾行了... 不過也只有這廿行,寫完了...。整個 .cs 檔案直接丟到 ~/App_Code/Extension 就算安裝完成。用管理者身份登入 BE 後,在 Extension 那頁可以看到:

     

    image

    不錯,SecurePost 已經出現在 Extension Manager 裡了。因為有加上 settings 的程式碼,所以右邊有 [編輯] 的字樣出現。點下去之後會到這個畫面:

     

    image

    嗯,看起來真專業,沒想到從頭到尾所有的 CODE 還不到一百行...。幾十行 CODE 寫出來的 Extension 就可以唬人了.. :D,試看看還真的會動耶 (廢話)。早知道寫起來那麼快,當初就不花那麼多時間去找人家寫好的了...。最後附上整段完整的程式碼,有需要的人就拿去用吧! 用法很簡單,全部複製下來 (可以按 [COPY CODE] 就好),存檔,把檔案放在 ~/App_Code/Extension/SecurePost.cs 下,然後用管理者身份進入 BlogEngine Extension Manager 改一改就好了!

     

    大功告成! 這個 Extension 如果對你有用的話就拿去用吧,要散佈也歡迎,不過只有個小要求,請不要把程式碼存到別的地方供人下載,請直接提供我這篇文章的網址就好。覺的好用就留個話給我,要幫我推一下文或讚助就更好了 :D,謝謝收看!

     

     

    --

    完整的 SecurePost.cs 程式碼[copy code]
    using System;using System.Web;using System.Web.UI;using BlogEngine.Core.Web.Controls;using BlogEngine.Core;using System.Text;[Extension("SecurePost", "1.0", "<a href=\"http://columns.chicken-house.net\">chicken</a>")]public class SecurePost{    private static string SecurePostMessage { get { return _settings.GetSingleValue("SecurePostMessage"); } }    private static string Password { get { return _settings.GetSingleValue("PasswordValue"); } }    private static string PasswordHint { get { return _settings.GetSingleValue("PasswordHint"); } }    private static ExtensionSettings _settings = null;    static SecurePost()    {        Post.Serving += new EventHandler<ServingEventArgs>(Post_Serving);        ExtensionSettings settings = new ExtensionSettings("SecurePost");        settings.AddParameter(            "SecurePostMessage",            "顯示訊息:");        settings.AddParameter(            "PasswordHint",            "密碼提示:");        settings.AddParameter(            "PasswordValue",            "指定密碼:");        settings.AddValues(new string[] {            "本篇文章已受密碼保護,請依照題示輸入密碼。",             "一二三四",            "1234"});        //settings.ShowAdd = false;        //settings.ShowDelete = false;        //settings.ShowEdit = true;        settings.IsScalar = true;        settings.Help = "用密碼保護文章的內容。";        ExtensionManager.ImportSettings(settings);        _settings = ExtensionManager.GetSettings("SecurePost");    }    private static void Post_Serving(object sender, ServingEventArgs e)    {        Post post = sender as Post;        if (HttpContext.Current.User.Identity.IsAuthenticated == true) return;        if (HttpContext.Current.Request["pwd"] == Password) return;        if (!e.Body.StartsWith("[password]", StringComparison.CurrentCultureIgnoreCase)) return;        StringBuilder bodySB = new StringBuilder();        {            bodySB.AppendFormat(                "<b>{0}</b><p/>",                HtmlEncode(SecurePostMessage));            if (e.Location == ServingLocation.Feed)            {            }            else            {                bodySB.Append("<div>");                bodySB.AppendFormat(                    @"請輸入密碼(提示: <b>{0}</b>): <input id=""postpwd"" type=""password""/><button onclick=""document.location.href='{1}'+'?pwd='+escape(this.parentNode.all.postpwd.value);"">GO</button>",                     PasswordHint,                    post.AbsoluteLink);                bodySB.Append("</div>");            }        }        e.Body = bodySB.ToString();    }    private static string HtmlEncode(string text)    {        return HttpContext.Current.Server.HtmlEncode(text);    }}
    
       1:  using System;
    
       2:  using System.Web;
    
       3:  using System.Web.UI;
    
       4:  using BlogEngine.Core.Web.Controls;
    
       5:  using BlogEngine.Core;
    
       6:  using System.Text;
    
       7:   
    
       8:   
    
       9:   
    
      10:   
    
      11:  [Extension("SecurePost", "1.0", "<a href=\"http://columns.chicken-house.net\">chicken</a>")]
    
      12:  public class SecurePost
    
      13:  {
    
      14:      private static string SecurePostMessage { get { return _settings.GetSingleValue("SecurePostMessage"); } }
    
      15:      private static string Password { get { return _settings.GetSingleValue("PasswordValue"); } }
    
      16:      private static string PasswordHint { get { return _settings.GetSingleValue("PasswordHint"); } }
    
      17:   
    
      18:      private static ExtensionSettings _settings = null;
    
      19:   
    
      20:      static SecurePost()
    
      21:      {
    
      22:          Post.Serving += new EventHandler<ServingEventArgs>(Post_Serving);
    
      23:   
    
      24:          ExtensionSettings settings = new ExtensionSettings("SecurePost");
    
      25:   
    
      26:          settings.AddParameter(
    
      27:              "SecurePostMessage",
    
      28:              "顯示訊息:");
    
      29:          settings.AddParameter(
    
      30:              "PasswordHint",
    
      31:              "密碼提示:");
    
      32:          settings.AddParameter(
    
      33:              "PasswordValue",
    
      34:              "指定密碼:");
    
      35:   
    
      36:          settings.AddValues(new string[] {
    
      37:              "本篇文章已受密碼保護,請依照題示輸入密碼。", 
    
      38:              "一二三四",
    
      39:              "1234"});
    
      40:   
    
      41:          //settings.ShowAdd = false;
    
      42:          //settings.ShowDelete = false;
    
      43:          //settings.ShowEdit = true;
    
      44:          settings.IsScalar = true;
    
      45:          settings.Help = "用密碼保護文章的內容。";
    
      46:   
    
      47:          ExtensionManager.ImportSettings(settings);
    
      48:   
    
      49:          _settings = ExtensionManager.GetSettings("SecurePost");
    
      50:   
    
      51:      }
    
      52:   
    
      53:      private static void Post_Serving(object sender, ServingEventArgs e)
    
      54:      {
    
      55:          Post post = sender as Post;
    
      56:   
    
      57:   
    
      58:          if (HttpContext.Current.User.Identity.IsAuthenticated == true) return;
    
      59:          if (HttpContext.Current.Request["pwd"] == Password) return;
    
      60:          if (!e.Body.StartsWith("[password]", StringComparison.CurrentCultureIgnoreCase)) return;
    
      61:   
    
      62:   
    
      63:          StringBuilder bodySB = new StringBuilder();
    
      64:          {
    
      65:              bodySB.AppendFormat(
    
      66:                  "<b>{0}</b><p/>",
    
      67:                  HtmlEncode(SecurePostMessage));
    
      68:   
    
      69:              if (e.Location == ServingLocation.Feed)
    
      70:              {
    
      71:              }
    
      72:              else
    
      73:              {
    
      74:                  bodySB.Append("<div>");
    
      75:                  bodySB.AppendFormat(
    
      76:                      @"請輸入密碼(提示: <b>{0}</b>): <input id=""postpwd"" type=""password""/><button onclick=""document.location.href='{1}'+'?pwd='+escape(this.parentNode.all.postpwd.value);"">GO</button>", 
    
      77:                      PasswordHint,
    
      78:                      post.AbsoluteLink);
    
      79:                  bodySB.Append("</div>");
    
      80:              }
    
      81:          }
    
      82:          e.Body = bodySB.ToString();
    
      83:      }
    
      84:   
    
      85:      private static string HtmlEncode(string text)
    
      86:      {
    
      87:          return HttpContext.Current.Server.HtmlEncode(text);
    
      88:      }
    
      89:  }
    

    2008/09/06 .NET ASP.NET BlogEngine Extension BlogEngine.NET 作品集 技術隨筆

  4. [RUN! PC] 2008 九月號

    image

    感謝編輯賞光,第三篇順利刊出 :D

     

    執行緒這種東西,實在不是什麼主流的文章,不過雜誌社願意刊到第三篇,真是感謝... 前兩篇分別介紹了同步機制跟旗標,這次用執行緒集區作總結,提供了綜合的應用,也對效能的影響作整理,讓讀者具體的瞭解使用前後的效能差異。

     

    這次文章內提到了 ThreadPool 的應用,不過因為內容及篇幅的關係,沒有挖到 ThreadPool 本身怎麼設計。對這部份有興趣的讀者可以參考我寫的這三篇:

    ThreadPool 實作 #1. 基本概念

    ThreadPool 實作 #2. 程式碼 (C#)

    ThreadPool 實作 #3. AutoResetEvent / ManualResetEvent

     

    雖然好像沒有人因為看到雜誌才連到這裡來,不過還是要囉唆一下,看到文章有任何意見都可以在這裡留言給我。文章內提到的 SAMPLE CODE 可以在這裡下載! 這次的範例程式是 Console application,不提供直接在網頁上執行,下載回去試試吧!

    2008/09/03 RUN! PC 專欄文章 .NET RUN! PC 作品集 多執行緒 技術隨筆

  5. 好酷的漆彈陣列...

     

     

    雖然我很少貼這些五四三的 (明明 543 這 tags 點下去有一堆..) ,不過無意間在癮科技逛到這段實在是太酷了... 流言終結者這節目的 Adam & Jamie用漆彈示範來比喻 CPU / GPU 繪圖的差別... 不管它的比喻精不精確或妥不妥當啦,那一瞬間噴出蒙那麗莎實在是太酷了... :D

    2008/09/02 有的沒的