2012年12月14日 星期五

走到最後還能留在身邊的,都要珍惜

在網路看到一段 Java Code,正想要好好品味一下時,才發現身邊只剩下那台跟著我許多年的 NB,因為他太老了,跑微軟的作業系統更顯吃力,所以才變節投靠 Ubuntu ,早些年前,他身上還貼著正版的微軟 XP 序號、標籤、標語,當年的繁華光景,連「華碩品質堅若石頭」的彩帶我都還留著,如今卻落魄到同我為伍,不經讓我想起「老人與狗」這本書...

我問他,你跑得快嗎?他搖搖頭。
我再問他,你可以跑 Java 嗎?他也搖搖頭。

看到他一臉徬徨無助的眼神,讓我升起悲憫之心,於是我決定好好教他,讓他可以「跑 Java」。


於是,我在深夜裡,冒著外頭的刮風下雪,來到爪哇旗艦店,一進門,就看到親切的業務向我正面迎來,並單手向我熱情的招呼著,同時遞送上來一杯熱騰騰的咖啡。沒多久,我就在展示間看到今年最流行的第七代 Java,型號是七架。望著七架流線的造型,彷彿可以感受到風在我臉上吹拂,速度在我腳下奔馳的快感。我當下就決定買下,並轉送給我的 NB,這種感覺,就好比當年淑珍買積架送給致中的心情是一模一樣的,「天下父母心,花別人的錢最開心」。















印象中,要能夠在 Ubuntu上跑 Java,是要花蠻多功夫的,但我的業務員告訴我一個簡單的方法,叫做 PPA (Personal Package Archive)。只要三行指令,就可以辦到好。

sudo add-apt-repository ppa:webupd8team/java
sudo apt-get update
sudo apt-get install oracle-java7-installer

原來有一段小故事是這樣的。在 Ubuntu 的世界裡,會有一個叫做「軟體中心」,伴隨著每六個月推出新版本的檔期,都可以在裡面找到最新的套件,而這些套件都是經過 Ubuntu 團隊審核過才放上去的。問題,就在於我們所需要的軟體,是否可以慢慢等待到審核通過後才能使用呢?如果我要的七架已經問世,但又不想等待半年後才拿到,這時就可以使用 PPA  的方式來取得,但風險則是沒通過審核,所以不保證安裝完後是否會影響系統的穩定性。這跟我要買 iPhone 的道理是一樣的,在美國新版的已經問世,但台灣還沒正式開賣,所以只好透過「水貨」的管道來取得。

透過那三行指令,我終於讓我的 NB 可以跑 Java 了,但我發現一個很大的問題,他不認識中文。於是,我又開始「ㄠ」我的業務員,同樣也是得到三行簡單好用的指令來完成中文環境的開發。

# cd /usr/lib/jvm/java-6-sun/jre/lib/fonts
# mkdir fallback
# ln -s /usr/share/fonts/truetype/wqy/wqy-zenhei.ttc

看著他正在努力的奔跑著,我笑了,我睡了....



參考:
01:INSTALL ORACLE JAVA 7 IN UBUNTU VIA PPA REPOSITORY
02:解决ubuntu下java中文字体显示方框问题
03:What Is An Ubuntu PPA & Why Would I Want To Use One?
04:快速添加 Ubuntu PPA 源

2012年12月13日 星期四

與 Split 更親近一點

先前在使用 C# 對字串進行切割 (split) 動作時,常常會希望把空白的字串過濾掉,只留下字串長度不是 0 的。而這工作其實也不難,就是將  split 後所得到的字串陣列,在你要用時用迴圈一筆一筆去濾掉空白的陣列元素。

剛好今天看到 「StringSplitOptions 列舉型別」介紹了其中的「RemoveEmptyEntries」成員,可以限定傳回值不會加入包含了空字串的陣列元素,有這麼好康的事情就來試試效果如何。於是寫了一個簡單的測試程式,沒想到添加了這作法還真是簡潔啊, 而且他從 .net 2.0 就開始支援了。

class Program
{
    static void Main(string[] args)
    {
        string strTest1 = "b,a,c,,";
        char[] charSeprator = new char[] { ',' };

        string strTest2 = "BB[x]CC[x]AA[x]";
        string [] strSeprator=new string[]{"[x]"};

       //以字元來切割
        foreach (string str in strTest1.Split(charSeprator, StringSplitOptions.RemoveEmptyEntries))
            Console.WriteLine(str);

       //以字串來切割
        foreach (string str in strTest2.Split(strSeprator, StringSplitOptions.RemoveEmptyEntries))
            Console.WriteLine(str);

        Console.ReadLine();

    }
}

如果開發環境是.net 3.5以上的版本,還可以更進一步去將 split 的結果進行排序後再取出。例如:

strTest2.Split(strSeprator, StringSplitOptions.RemoveEmptyEntries).OrderBy(g=>g)

就可將陣列進行排序。


參考:
01:StringSplitOptions 列舉型別
02:Extension Methods (C# Programming Guide)

2012年12月11日 星期二

String,StringBuffer,StringBuilder 的三角關係

不管是在 Java 或是 C#,對於 String 這物件應該都不陌生。但如果考量是在字串串接的用途上時, String 的效能可真的就顯得遜色許多。

String 物件一旦在記憶體被配置之後,他就不能夠再改變了,所以他常常被說成是 immutable object。但我們卻很習慣寫成這樣:


三行程式寫完後,請求了三塊新的記憶體配置,也同時浪費了兩塊記憶體。

針對這個問題,Java 提供了 StringBuffer 物件,讓我們可以對字串加加減減,也不會另外再要一塊新的記憶體位址來存放。在這物件當中,Java 還考慮到多執行緒的問題,也就是說,不同的執行緒不會同時對同一個 StringBuffer 物件做新增或是修改的動作,例如當執行緒 A 在使用 「StringBuffer 甲」的時候,執行緒 B 必須等到執行緒 A 完成「StringBuffer 甲」的處理動作 之後才能使用,這就是我們常說的 thread-safe。

對於 Java 所提供的 StringBuffer 物件,針對字串串接的執行校能上,的確可以非常明顯地看到提昇,但一般對程式設計師來說,大部分時機其實可能都不需要考量多執行緒的問題,如果不考慮多執行緒的情況下,減少多餘的判斷處理,是否還有機會再提昇字串串接的效能呢?

果真,在 JDK 1.5. 之後,開始出現了新的物件 StringBuilder ,他與 StringBuffer 的用途幾乎如出一轍,唯一的不同是減少了多執行緒的考量。日後如果只是在寫單一執行緒的程式考量下,StringBuilder 會是較好的選擇。「StringBuffer vs. StringBuilder performance comparison」文章作者Daniel 針對 StringBuilder 與 StringBuffer 兩者間的效能寫了一段簡單的程式來比較,而我自己也透過他的程式在自己電腦上得到了下面數據:


從數據上看來, StringBuilder 已經比 StringBuffer 提昇了一倍以上的效能,當然這兩者更遠遠超過 String 的字串串接。

同樣的情況,也出現在 微軟的 C# 裡。在字串串接的情況下, StringBuilder 遠比 String 的效能提昇很多,唯一讓我不解的,是這次微軟的身上少了 StringBuffer 的味道...,我很認真的想知道這次微軟為何沒收錄 StringBuffer 這物件,但迄今依舊無解。而微軟的 StringBuilder,同樣也是不考慮多執行緒,可以在微軟的官方文件「StringBuilder Class」看到 Thread Safety 的聲明:

Any public static (Shared in Visual Basic) members of this type are thread safe.
Any instance members are not guaranteed to be thread safe.

可從上面申明中知道,C# 裡面的 StringBuilder 並不是 thread-safe。

另一個有趣的地方,是從 JDK 釋出的時程來看看,彷彿 .net 在 2003年時釋出的 .net 1.1, 已經比 Java 更早公佈 StringBuilder 物件了!


經由這一次的整理,可以知道 String 在字串串接上所面臨的效能瓶頸,並透過 Daniel 的程式在定量上可以得到支撐。此外,也可以更清楚去分別依據是否為單一執行緒來正確抉擇 StringBuilder 或 StringBuffer。

參考:
01:StringBuffer vs. StringBuilder performance comparison
02:StringBuilder Class
03:Java JDK (Java Development Kit) Releases Dates and Release Differences
04:Java SE 6全方位學習
05:StringBuilder和StringBuffer的差別及清空內容方式
06:StringBuilder vs StringBuffer
07:Why use StringBuilder? StringBuffer can work with multiple thread as well as one thread?
08:StringBuffer and StringBuilder
09:Is the C# '??' operator thread safe?
10:如何:增進 Visual C# .NET 中的字串串連效能
11:Daniel 簡單測試程式

2012年12月7日 星期五

當 SSRS 遇到 VSS 時應該知道的事

最近接收了一包  SQL Server Reporting Service (SSRS) 所開發的報表,因為這包報表程式沒有使用任何的版本管控。富有正義感且集聚勇氣於一身的我,就當仁不讓地主動請纓要做好版本管控,於是我就用了最簡單的 Visual SourceSafe(VSS) 來控管這包報表。

其實做這事情真的沒有甚麼難度,一下子就把報表全部加到 VSS 裡面去了。但當我一執行報表時,竟然出現:


拒絕存取路徑 'D:\MyCode\bin\Debug\新世代青年生命成長營.rdl'

我想,是不是 「新世代青年生命成長營.rdl」 這隻檔案被設為唯讀所造成的,於是手動去把唯讀取消,再執行一次,結果還是一樣。於是我網路上找了很一陣子,發現有一篇微軟的文章:「修正: 在組建中,清除,伺服器專案錯誤報告,或預覽中 SQL Server 2008 R2 和出價等作業」,原來 SSRS 有 hotfix 啊,於是造著說明去下載,蠻肥的,約2百多M,沒想到更新完後再重試,竟然還是一樣。

在一股不理智的衝動下,我誤刪了 \bin 資料夾。但只要重新佈署一次,這個 \bin 資料夾自己又會重新生出來,且回覆正常了。但過些時日,這問題又會再跑出來一次,我刪除 \bin 資料夾後又可以正常。

當我閱讀了「FISERV TAP - Checking it RDL Source Sets Output Folder to Read Only」,發現這問題在 SSRS 與 VSS 搭配在一起,就很容易發生。解決方法可以參考該篇文章,但我覺得自己最省力氣的,還是刪除 \bin 資料夾。

參考:
01:修正: 在組建中,清除,伺服器專案錯誤報告,或預覽中 SQL Server 2008 R2 和出價等作業
02:FISERV TAP - Checking it RDL Source Sets Output Folder to Read Only
03:Access to the Path Error in Visual Studio
04:修正: 在組建中,清除,伺服器專案錯誤報告,或預覽中 SQL Server 2008 R2 和出價等作業

2012年11月9日 星期五

關於 Reporting Service 的報表資料

不管是使用 SQL Server 的 Business Intelligence Development Studio 或是 Visual Studio 2012 開發 Reporting Service 報表,應該對於報表資料  (Report Data) 這視窗不陌生。


可是,當我不小心按了「X」把他關閉後,卻再也怎麼找不回來了。找了好久才發現,要呼叫他之前竟還要先做一個動作。首先,必須先將專案裡的任何一支報表打開,然後在該報表的設計頁面點一下,主要是讓設計頁面目前是被聚焦(focus)的,這時,再點選最上面功能列裡的「檢視(View)」,在檢視頁籤中,最下面就會出現「報表資料(Report Data)」,點選後就可以找回自己的報表資料視窗了。

關於要在「檢視」功能列中出現「報表資料」選項,還得先點選設計頁面才行的作法,這 UI 的設計邏輯真的會讓人混淆,或許不需這麼複雜,永遠都出現在「檢視」功能列中不就好了嗎?而且加上這限制,還真的想不出有何特別意義。

參考:
01:停駐報表設計師中的報表資料窗格 (SSRS)

2012年11月6日 星期二

由VS2003 升級後的專案無法使用 Update Panel

將專案從 VS2003 升級到 VS2005 後,原本想要試試微軟新功能 Update Panel,但竟然一點效果也沒有,是不是我哪裡弄錯了呢?自己也是照著書本上一行一行打的啊?於是在 google 上開始打著 Update Panel 無路用之類的關鍵字,哈哈,還竟然真的查到許多人在討論這件事。

原來這故事,要從 XHTML 開始說起。話說 HTML 稱霸網壇數十年早已不是甚麼新鮮事,他嚴然已成為一種標準通用的標記語言,但他的缺點就是太鬆散了。大小寫不分,有沒有結束的封閉符號也不管,人這麼聰明,看懂是沒問題,但機器這麼笨,大寫小寫傻傻分不清楚。美其名是為了提昇機器的效率,其實是掩蓋機器沒那麼聰明的事實,所以大家訂了一個更嚴格的約定,都用小寫、一定要有封閉符號...,諸如此類的規定,只是為了讓機器能夠減少去做多餘的判斷,快速的達成我們所要的結果。這項別稱為近代人類的不平等條約,就是 XHTML(詳見:wiki 說明的 XHTML)。


目前 XHTML 的版本大致分為以下幾類:

  • XHTML 1.0 Strict(嚴格版): 是參照「HTML 4.01 Strict」改編,但不包括被棄用的元素。
  • XHTML 1.0 Transitional(過渡版): 是參照「HTML 4.01 Transitional」改編,包括已於Strict版本被棄用的呈現性元素(例如<center>, <font>等)。
  • XHTML 1.0 Frameset(框架版): 是參照「HTML 4.01 Frameset」改編,並允許於網頁中定義框架元素。
  • XHTML 1.1
  • XHTML Basic
然而,在微軟 .Net 1.0 、.Net 1.1 的年代裡,還是依循著早期 HTML 的快樂好日子,我的程式也是隨便寫寫,他也睜一隻眼閉一隻眼讓我過。但好景不常,隨著 .Net 2.0 降臨,開始要求要符合 XHTML 的規範,這也難怪從 VS2003 升級到 VS2005 後的程式碼,處處可見紅字。

如果專案是從 .Net 1.0、.Net 1.1 透過自動升級精靈升級到 2.0 以後的版本,為了讓先前已經寫好的程式能夠順利的渡過這青黃不接的階段,微軟提出了三種方案讓你選擇,而這個設定,就是在 web.config 檔裡的 xhtmlConformance。這三種方案分別是:


  • Legacy:他就如同早期 .net 1.0、.net 1.1 的 HTML 規範,並不會去限制你一定要遵守 XHTML 的規定。
  • Transitional:他表示要符合等同 XHTML 1.0 Transitional 的規範
  • Strict:他表示要符合更嚴格的 XHTML 1.0 Strict 的規範

而我們升級過後的專案,為了要能夠向下相容、無痛升級,所以就會主動在我們的 web.config 上加上一行:
<system.web>
      <xhtmlConformance mode="Legacy" />
</system.web>

很不巧地,我想要使用的 Update Panel 功能,他卻只能在 Transitional 與 Strict 的模式下才能執行,這也正是為何透過升級而來的專案,會出現 Update Panel  無法正常使用的狀況。所以,這時的處理方式,可以透過修改 xhtmlConformance ,將其改為 Transitional 或 Strict ,就可以讓 Update Panel 正常使用了!如果你看 xhtmlConformance 不順眼,刪了他也是可以,因為他的預設值是 Transitional。

參考:
01:XHTML
02:ASP.NET AJAX & XHTML Conformance
03:Gotcha: Don't use <xhtmlConformance mode="Legacy"/> with ASP.NET AJAX
04:.NET 2.0 Update Panel won't perform partial postbacks on upgraded 1.1 sites

2012年11月5日 星期一

一場誤會

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="jsTest.aspx.cs" Inherits="WebApplication1.jsTest" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <script src="http://code.jquery.com/jquery-1.6.4.min.js"></script>    
  
</head>
<body>
    <form id="form1" runat="server">
    <div>
昔誰持種來 而今佳色開<br>
昔誰持種來 而今佳色開<br>
昔誰持種來 而今佳色開<br>
    </div>

    <script>alert('提示訊息');</script>


    </form>
</body>
</html>

如上很簡單的 HTML 語法,只是好奇的想知道,是先跳出 javascript 的「提示訊息」呢,還是會先顯示「昔誰持種來 而今佳色開」的說明呢?我覺得在剖析 HTML 時,是由上而下的順序,應該會先顯示文字說明,而 javascript 是擺在最後面,所以會放到後面才執行。不只我這麼說,別人也是如是說,請看:「Load and execution sequence of a web page?」對於 mauris 所解釋的內容,真的非常詳細。

但我在 IE8 瀏覽結果時,卻出現如下結果:

而 chrome 與 firefox 則是如下(以 chrome 為例):

這說明了,以上面的範例來說,IE8 是先執行 javascript ,然後再對文字說明做處理;相反地,Chrome 與 FireFox 則是先對文字說明做處理,然後再執行 javascript。但這樣的結果,還真的讓人很疑惑?於是我再測了一下 IE6,沒想到 IE6 的行為,竟然跟 chrome 與 firefox 是一樣的,難道 IE8 是怪胎嗎?或許把過錯都推給 IE8 還蠻多人會覺得理所當然,但直到有一天,我把 jQuery 的版本升級到 1.7.1 之後,IE8 的處理結果竟也跟其他瀏覽器都一致了。原來事情的始末,竟是 jQuery 1.6.4 的版本,造成 IE8 在執行 javascript 時的觸發順序都提到最前面了。

在尚未發現主要原因是 jQuery 版本所引起之前,我的作法是在 javascript 標籤屬性裡加上 defer:

<script defer>alert('提示訊息');</script>

defer 這關鍵字,僅支援 IE 瀏覽器,他會讓 javascript 的執行時間延遲到整個頁面都下載完成後才執行。對我而言,只要跳出訊息視窗時不讓背景畫面一片空白,也就可以了。只是沒想到,真正引發問題的原因竟是自己所使用的 jQuery 版本。

IE8,我錯了,請您原諒~



參考:
01:Load and execution sequence of a web page?
02:姚博文 Script中defer的作用
03:asp.net弹出信息框——没有用ScriptManager
04:HTML <script> 标签的 defer 属性

2012年10月19日 星期五

String Pool

在 Java 所定義的 String 物件裡,可以用兩種方式去指定物件變數的值,例如:

String str1= new String("Java");
String str2= "Java";
由於 str1 與 str2 是兩個不同的物件,當然他的記憶體位址也會不同。所以當我用比較運算子 (==)來比較 str1 與 str2 時,因為比較運算子是比較記憶體位址,可以如期得到 false 的結果。
str1 == str2 /* 回傳 false */
可是,當我另外宣告一個如同 str2 的變數,再去比較一次:
String str2= "Java";
String str3= "Java";

str2 == str3 /* 回傳 true */
這 str2 與 str3 不是兩個不同物件嗎? 為何用比較運算子去比較時,竟然會出現相等的結果?原來,這裡面還有一小段故事啊!在 「JAVA SE6 全方位學習」一書有提到:

Java 為了加快程式的執行速度,又因為字串是每個程式中一定會用到的物件,所以 Java 設計了兩種不同的方式來產生字串物件,一種是呼叫 String 類別的建構子,另一種是使用雙引號【"】。這兩種方式產生出來的字串物件,它們在記憶體中存放的方式不一樣;使用建構子的方式所產生出來的物件,自己有自己的獨立空間,而之所以會有雙引號的方式,主要是為了加快程式執行的速度,所以 Java 會把這類的字串放在一個字串池(String Pool)之中,當用雙引號產生字串物件時,電腦會先去字串池中尋找有沒有相同的字串已經放在裡面了,如果有就直接拿出來用,如果沒有就產生一個新的字串放到字串池中。

當我們看到 Java 為了程式效能的提升,在 String 物件做了如上的調整,那 .Net 陣營的 C# 呢?答案是肯定的。

我把剛剛在 Java 的程式移植到 .Net 平台,改成如下:
String str1=new String("ASP.Net".ToCharArray());
String str2 = "ASP.Net";
String str3 = "ASP.Net";

Console.WriteLine(string.Format("str1==str2 {0}", (str1==str2).ToString()));
/* 回傳 True */
Console.WriteLine(string.Format("str2==str3 {0}", (str2 == str3).ToString()));
/* 回傳 True */

有了先前在 Java 的 String Pool 概念後, str2==str3 回傳 True 已經可以理解,但為何 str1==str2 卻是 True 呢?比較運算子不是去比記憶體位址的嗎?

這當中的曲折,又要用另一個故事來說明了:

參考了「C# - Equals和等於等於( 等於比較運算式 )」這篇文章所引述 MSDN 的一段話:

對於預先定義的實值型別 (Value Type),等號比較運算子 (==) 在運算元相等時傳回 true;否則傳回 false。 對於 string 以外的參考型別,若兩個運算元參考到同一物件,== 會傳回 true。 對於 string 型別,== 會比較字串的值。

原來在 C# ,如果是屬於字串型別,比較運算子會去比較存放在記憶體裡面的值而不是記憶體位址,這一點的確是還蠻細的。

換言之,如果把剛剛 C# 的程式,把原先的字串型別都 Boxing 成 Object 型別呢?

Object obj1 = new String("ASP.Net".ToCharArray());
Object obj2 = "ASP.Net";
Object obj3 = "ASP.Net";

Console.WriteLine(string.Format("obj1==obj2 {0}", (obj1 == obj2).ToString()));
/* 回傳 False */
Console.WriteLine(string.Format("obj2==obj3 {0}", (obj2 == obj3).ToString()));
/* 回傳 True */
改成用 Object 型別後,的確就跟 Java 所認知的概念是一樣了。

最後自己還留下個疑問,到底這樣大費周章的使用 String Pool ,到底可以佔到多少便宜啊?於是寫了兩個小程式比較一下,就讓他跑個 2千萬次的迴圈看看:

Java:
public class jstring02
{
    public static void main(String[] args)
 { 
  System.out.println(new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date())); 
  
  String [] a_str= new String[20000000];
  for(int i=0;i<20000000;i++)
  {
   a_str[i]=new String("ASP.Net");
  }
  
  System.out.println(new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date())); 

  String [] b_str= new String[20000000];
  for(int i=0;i<20000000;i++)
  {
   b_str[i]="ASP.Net";
  }
    
  System.out.println(new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date())); 

 }

}
C#
class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(string.Format("{0}", DateTime.Now.ToString()));

        String[] a_str = new String[20000000];
        for (int i = 0; i < 20000000; i++)
        {
            a_str[i] = new String("ASP.Net".ToCharArray());
        }

        Console.WriteLine(string.Format("{0}", DateTime.Now.ToString()));

        String[] b_str = new String[20000000];
        for (int i = 0; i < 20000000; i++)
        {
            b_str[i] = "ASP.Net";
        }
        Console.WriteLine(string.Format("{0}", DateTime.Now.ToString()));

        Console.ReadLine();
    }
}


在 Java 與 C# ,第一次的時間是起始時間,第二次是執行使用建構子方式後的時間,第三次則是執行完 String Pool 後的時間。有了數據上的比較,對於有沒有使用 String Pool 的效能差別,會比較有感覺些。


PS:自己程式功力很差,但主要重點是想凸顯是否有使用 String Pool 的感覺。


 參考:
 01:Java SE 6全方位學習
 02:C# - Equals和等於等於( 等於比較運算式 )

2012年10月4日 星期四

colorbox 的右邊、下面都消失了

今天遇到一個很沒良心的問題,找到問題的癥結點後,反而沒有一種喜悅感,卻隱約有一股想要去撞牆的衝動。

我想要透過 colorbox 去完成一個開視窗的動作,但開的卻很不漂亮。如下圖所示,右邊的框、下面的框都不見了,檢查了程式,該有的都有了,不該有的也加了一堆,畫面就一直缺了一角,醜醜的。


最後看到「Colorbox的官方介绍和colorbox的常见问题中文翻译」提到,jQuery 和 colorbox.css 必須在 jquery.colorbox.js 之前引入。自己檢查了一下程式碼的順序,我竟然把 colorbox.css 擺在最下面。經過調整順序之後,真的恢復正常了。


這烏龍讓我想起一句話:「我們都沒有錯,只是在錯的時間遇上對的人」。

如果硬是要多寫一些話來描述,我覺得引用下面這段還蠻適合的:


當我倆敞開自己
你把自己給我, 我把自己給妳
當我們沉潛
妳進入我, 我進入妳
當我們消失
你消失在我裡面, 我消失在妳裡面
然後, 我是我
而妳是妳!

出處:為愛朗讀

在愛情的世界裡,錯誤的時間遇到對的人,往往不會有好的結局
在程式的世界了,錯誤的順序遇到colorbox,往往不會有好的畫面


參考:
01:Colorbox的官方介绍和colorbox的常见问题中文翻译
02:為愛朗讀

2012年8月22日 星期三

Asp.Net Ajax 生命週期

看到 Asithangae 把 ASP.Net 使用 Ajax 技術後的前端事件生命週期寫得這麼詳細,真是令人讚嘆。 閱讀後,大致上對於前端的事件,清楚許多。

先以一般的事件來說,大概可分為三種:

一、pageInit   (ref. Sys.Application.init 事件)
當頁面第一次被載入,或是整個頁面觸發 post back 之後會發生。但如果是非同步的請求,也就是僅有部份頁面被更新,則不會發生。
Sys.Application.add_init(MyInit);

function MyInit()
{
    alert("MyInit added to page init");
}

二、pageLoad  (ref. Sys.Application.load 事件)
他的觸發順序是在 pageInit 之後。他觸發的時機點跟 pageInit 相似,當頁面第一次被載入,或是整個頁面觸發 post back 之後會發生。唯一不同的地方,是非同步的請求,也就是僅有部份頁面被更新時,他也會發生。
Sys.Application.add_load(MyLoad);

function MyLoad()
{
    alert("MyLoad added to page load");
}

三、pageUnload  (ref. Sys.Application.unload 事件)
當要離開頁面的時候,這事件會被觸發。例如程式裡面使用了 Response.Redirect() 或是 Server.Transfer()。
Sys.Application.add_unload(MyUnload);

function MyUnload()
{
    alert("MyUnload added to page unload");
}


此外,針對非同步請求,則又可以分為以下五種:

一、initializeRequest (ref. Sys.WebForms.PageRequestManager initializeRequest 事件)
觸發的時機點,是使用者壓下頁面上的控制項,準備要執行非同步更新的請求。只是目前這個請求,尚未送到後端的伺服器。既然這請求尚未送到後端,表示在這時間點,我們有機會執行取消或放棄請求的動作。
Sys.WebForms.PageRequestManager.getInstance().add_initializeRequest( MyIntializeRequest );

function MyIntializeRequest(sender,args)
{
    alert("My Request is getting initalized");
}


二、beginRequest (ref. Sys.WebForms.PageRequestManager beginRequest 事件)
這事件是發生在 initializeRequest 之後,此時的非同步更新請求已經傳送到後端伺服器,並等待處理。通常在這時候,可以擺放「處理中」的動畫圖示。
Sys.WebForms.PageRequestManager.getInstance().add_beginRequest( MyBeginRequest );

function MyBeginRequest(sender,args)
{
    alert("My Request is ready about to sent to server");
}

三、pageLoading (ref. Sys.WebForms.PageRequestManager pageLoading 事件)
當後端伺服器處理完我們的非同步更新請求後,在尚未把處理結果交予前端之前,會觸發這個事件。
Sys.WebForms.PageRequestManager.getInstance().add_pageLoading( MyPageLoading );

function MyPageLoading(sender, args)
{
    alert("My page is started loading");
}

四、pageLoaded (ref. Sys.WebForms.PageRequestManager pageLoaded 事件)
當前端頁面完成所有非同步更新請求的結果後,會觸發這個事件。我們原先在 beginRequest 所擺放的「處理中」動畫圖示,在這個時間點就可以撤除了。
Sys.WebForms.PageRequestManager.getInstance().add_pageLoaded( MyPageLoaded );

function MyPageLoaded(sender, args)
{
    alert("My page is loaded");
}

五、endRequest (ref. Sys.WebForms.PageRequestManager endRequest 事件)
這個事件是整個非同步更新請求的最後一個,當他結束後,就會把控制權還給使用者。而再這個事件裡,也會回傳此次非同步更新請求的結果,例如:失敗或成功,如果有需要做紀錄(log),也是在這個事件裡面來撰寫。
Sys.WebForms.PageRequestManager.getInstance().add_endRequest(MyEndRequest);

function MyEndRequest(sender, args)
{
    alert("My Request has end");
}



參考「Ajax 用戶端生命週期事件」一文,假設非同步回傳的狀況假設如下:

頁面包含一個 ScriptManager 控制項,且控制項的 SupportsPartialRendering 和 EnablePartialRendering 屬性皆為 true。

頁面包含一個 UpdatePanel 控制項,且控制項的 ChildrenAsTriggers 屬性為 true。

UpdatePanel 控制項內部的按鈕會啟始非同步回傳。

伺服器成功傳回回應。

下列用戶端事件會依此順序發生:

1.按一下 UpdatePanel 內部的按鈕,該按鈕會啟始非同步回傳。

2.PageRequestManager 執行個體會引發 initializeRequest 事件。

3.PageRequestManager 執行個體會引發 beginRequest 事件。

4.傳送要求至伺服器。

5.用戶端收到回應。

6.PageRequestManager 執行個體會引發 pageLoading 事件。

7.PageRequestManager 執行個體會引發 pageLoaded 事件。

8.Application 執行個體會引發 load 事件。

9.PageRequestManager 執行個體會引發 endRequest 事件。

請注意,Application 執行個體的 load 事件,是在 PageRequestManager pageLoaded 事件之後、及其 endRequest 事件之前引發的。



在上面所介紹的各種事件觸發裡,實際測試後,發現 pageUnload 其實並沒這麼單純。
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>    
        <asp:ScriptManager ID="ScriptManager1" runat="server">
        </asp:ScriptManager>
        <script>
            function MyUnload() {
                alert('this is my unload');              
            }

            Sys.Application.add_unload(MyUnload);
        </script>    
    </div>
    </form>
</body>
</html>

這個範例裡,期待當網頁關閉,或要切換到別的頁面時,會出現「this is my unload」的訊息。這需求在 IE 瀏覽器可以正常執行,但是在 Firefox、Chrome 則完全失效。以前 IE 獨大的年代或許可以不考慮這問題,但今非昔比,接下來就是要特別處理 unload 事件了。
參考先前 網頁離開前跳出確認視窗 的文章,改成去攔 window.onbeforeunload 事件。這時候,我把原先 Sys.Application.add_unload(MyUnload)  改成使用 Sys.UI.DomEvent.addHandler(window, "beforeunload", MyUnload)後,的確有些改善,Firefox 可以在離開網頁前跳出「this is my unload」訊息,但 chrome 還是不行。原因是 Chrome 在 onbeforeunload 事件,如果要在當下跳出訊息,不可以用 alert( ),而是要用 return( )。 也就是要寫成:return 'this is my unload'; (參考:unload not working in Chrome)。為了同時讓這三大主流瀏覽器同時能夠支援,所以我在 MyUnload( ) 事件裡,就同時寫了:  alert('this is my unload');   return 'this is my unload';  如此才讓三者相安無事,彼此共存。


<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>    
        <asp:ScriptManager ID="ScriptManager1" runat="server">
        </asp:ScriptManager>
        <script>
            function MyUnload() {
                alert('this is my unload');
                return 'this is my unload';
            }
          
            Sys.UI.DomEvent.addHandler(window, "beforeunload", MyUnload);
        </script>    
    </div>
    </form>
</body>
</html>


以上是針對 Unload 事件時跳出訊息( alert )的處理,如果是要產生確認 ( confirm )的效果,可以參考網頁離開前跳出確認視窗 的作法。

參考:
01:The Page Life Cycle of Client [Browser]
02:網頁離開前跳出確認視窗
03:unload not working in Chrome
04:Sys.Application.init 事件
05:Sys.Application.load 事件
06:Sys.Application.unload 事件
07:Sys.WebForms.PageRequestManager initializeRequest 事件
08:Sys.WebForms.PageRequestManager beginRequest 事件
09:Sys.WebForms.PageRequestManager pageLoading 事件
10:Sys.WebForms.PageRequestManager pageLoaded 事件
11:Sys.WebForms.PageRequestManager endRequest 事件
12:Ajax 用戶端生命週期事件

2012年8月16日 星期四

在 UpdatePanel 裡面使用 javascript

近日在使用 Asp.Net 所提供的 Ajax 功能時,對於 UpdatePanel 與 JavaScript 之間的愛恨糾葛,還是跳出來說幾句話好了。

從自己最常使用的功能說起。在網頁上放個 Button,壓下去後,跳出一個 alert 視窗,程式如下:
<form id="form1" runat="server">
<div>
    <asp:button id="Button1" runat="server" onclick="Button1_Click" text="Button" />
</div>
</form>
以及 .cs 程式
protected void Button1_Click(object sender, EventArgs e)
{
    ClientScript.RegisterStartupScript(this.GetType(), "JustAlert", 
        "<script> alert('Hello');</script>");
}
但當我將這個 button 移到 UpdatePanel 裡,就開始不正常了。我的 javascript 完全沒有反應,也沒有錯誤訊息。詳閱了「RegisterStartupScript Method (control, type, key, script, addScriptTags)」後,發現程式需要做些調整。該文章有提到,如果是在 UpdatePanel 裡,當我們使用了非同步更新的方法,要改用 ScriptManager 的 RegisterStartupScript,如下所示:

protected void Button1_Click(object sender, EventArgs e)
{
    ScriptManager.RegisterStartupScript(this.Page, this.GetType(),
        "JustAlert", "alert('Hello');", true);
}

既然在 UpdatePanel 裡需改用 ScriptManager 來呼叫 JavaScript,那理論上我在後端也可以呼叫前端所定義的 JavaScript 囉!所以,再試了一下:
/* .aspx  */
<head runat="server">
    <title></title>
    <script>
        function SayHi() {
            alert('Hi');
        }
    </script>
</head>

/* .cs */
ScriptManager.RegisterStartupScript(this.Page, this.GetType(),
    "JustAlert", "SayHi();", true);
果然可行。原來呼叫前端的 javascript 都沒問題,那是否也可以在 UpdatePanel 裡觸發整個頁面的 PostBack 呢?
ScriptManager.RegisterStartupScript(this.Page, this.GetType(),
    "JustAlert", string.Format("__doPostBack('{0}','');", this.ClientID), true);
測試後,還蠻好用的,能夠順利的讓整個頁面都 PostBack。

這讓我想到一個情境。如果我的頁面很複雜很複雜,其中會有一個按鈕。當使用者按下這個按鈕後,會在後端先去做初步的判斷,決定是否要進一步去執行後續的動作,如果判斷沒必要繼續往後執行,則會在前端跳出訊息。但因為畫面很複雜很複雜(想像一下嘛,總有一天你會遇到),當被判斷沒必要往後執行時,也不希望造成整個頁面的 PostBack 而重載所有物件。

這情境下,我可以把確認按鈕,放到 UpdatePanel 裡面,並在 Button 事件裡,先去判斷是否有必要繼續往後執行。如果有必要,就會執行上面所說的,在 UpdatePanel 裡面觸發前端的 __doPostBack() 來達到頁面 PostBack 的效果。相反地,如果判斷沒有必要往後執行,則是透過 ScriptManager.RegisterStartupScript() 來產生前端 javascript 的 alert( ) 效果。

以下是測試的範例程式:
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>   
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="ScriptManager1" runat="server">
        </asp:ScriptManager>   
        <asp:Label ID="Label1" runat="server" Text=""></asp:Label>
        <div>
        <img src="https://encrypted-tbn2.google.com/images?q=tbn:ANd9GcSloWls9-zDJUF2RvunIn3-5zB8HJ-PTGJ3tc_jXIMJcbxBDvu7DQ" border="0"></img>

        </div>
        <asp:RadioButtonList ID="rbl" runat="server" 
            RepeatDirection="Horizontal">
            <asp:ListItem Value="1">未滿18歲</asp:ListItem>
            <asp:ListItem Value="2">滿 18 歲</asp:ListItem>
        </asp:RadioButtonList>

        <asp:UpdatePanel ID="UpdatePanel1" runat="server">
            <ContentTemplate>
                <asp:Button ID="Button1" runat="server" onclick="Button1_Click" Text="確認" />
            </ContentTemplate>
        </asp:UpdatePanel>

    
    </div>
    </form>
</body>

public partial class ChangeMsgShow4 : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        //取得是誰觸發了 PostBack
        string strTarget = "" + Request.Form["__EVENTTARGET"];
        //判斷觸發 PostBack 的物件名稱是否為 MySpecial
        if (strTarget == "MySpecial")
        {
            //執行自訂的後續執行方法
            DoSpecial();
        }
    }

    private void DoSpecial()
    {
        Label1.Text = "看蜘蛛人有這麼麻煩嗎?";
    }

    protected void Button1_Click(object sender, EventArgs e)
    { 
        switch(rbl.SelectedValue)
        {
            case "1":
                //判斷不需要繼續往後執行,所以只在前端 alert 一些訊息
                ScriptManager.RegisterStartupScript(this.Page, this.GetType(),
                    "JustAlert", "alert('孩子,等待是值得的!');", true);
                break;

            case "2":
                //判斷後有必要往後執行,所以在前端先跳出一段訊息後,接著觸發 __doPostBack,並
                //把觸發 PostBack 的物件來源名稱設定為 MySpecial
                ScriptManager.RegisterStartupScript(this.Page, this.GetType(),
                        "JustAlert", "alert('恭喜你已經成年!'); __doPostBack('MySpecial','');", true);
                break;

            default:
                //判斷不需要繼續往後執行,所以只在前端 alert 一些訊息
                ScriptManager.RegisterStartupScript(this.Page, this.GetType(),
                    "JustAlert", "alert('請摸著良心選擇!');", true);
                break;

        }
    }
}

以前 Asp.Net 的前端與後端,總像是牛郎織女一樣,久久才會見面一次。但透過 Ajax 的技術,讓這兩人隨時都可以明來暗去,樂鬧許多。
參考:
01:RegisterStartupScript Method (control, type, key, script, addScriptTags)

2012年8月14日 星期二

在 WinForm 使用 HtmlEncode

今天想在 VS2010  WinForm 的專案下,使用 HtmlEncode( ) 方法對使用者輸入的內容進行編碼,上網 Google 了一下,是要使用 :

System.Web.HttpUtility.HtmlEncode(str)
啥!還是不能用,於是再 Google 了一次,原來還要加入參考: System.Web.dll
於是接著進行很簡單的加入參考的動作,但...我的清單裡竟然找不到 System.Web ,不會吧~

後來在「visual studio 2010 winform程序不能添加对system.web的引用」有提到,如果在 WinForm 環境下要加入 System.Web 參考,需先確認開發環境的 Target FrameWork 是否為 .Net FrameWork 4,很不巧的,我打開 Project ->Project Properties->Target framework,發現自己的設定竟然是:.Net Framework 4 Client Profile。於是將他調整到 .Net Framework 4 後,在加入參考的候選清單中,就出現了 System.Web 了。


只是有點好奇,為何 .Net Framework 4 Client Profile 與  .Net Framework 4 會存在著如此不同的差異呢?原來,.Net Framework 的可轉散佈檔目前的檔案大小已經肥到 231 MB(公斤) ,一般來說,工程師所寫的程式,都遠比他小很多很多。所以出現了 Client Profile 的解決方案,也就是所謂的瘦身專案。既然是瘦身,可想而知有些不常用的功能,預設就不會加進來。

可以在「Assemblies in the .NET Framework Client Profile」查閱到瘦身後的 Framework 可參考的  Assemblies。而我所需要的 System.Web,其實並不在裡面。依目前的需求來看,我是沒辦法使用瘦身專案了,只能選擇原先的 .Net Framework 4。不過即使是胖胖的,只要樂觀開朗,也可以過的很開心。至少,你吃下去的幸福,就比別人多了一點,當然享受到的快樂也就比別人多一些!


參考:
01: visual studio 2010 winform程序不能添加对system.web的引用
02: Assemblies in the .NET Framework Client Profile
03: .NET Framework 4 与 .NET Framework 4 Client Profile
04: [VS2010] .NET Framework 4.0 Client Profile

2012年7月24日 星期二

複製資料到有自動增值欄位的資料表

我一直以為,在 SQL Server 複製資料表A 到資料表 B 不是件難事,直到遇到「有自動增值欄位的資料表」。

舉例來說,以下是兩個一模一樣的資料結構 TB1 與 TB2 :
CREATE TABLE [dbo].[TB1](
 [SN] [int] IDENTITY(1,1) NOT NULL,
 [SName] [nvarchar](50) NULL
) ON [PRIMARY]


CREATE TABLE [dbo].[TB2](
 [SN] [int] IDENTITY(1,1) NOT NULL,
 [SName] [nvarchar](50) NULL
) ON [PRIMARY]


當我執行
insert into TB2 select * from TB1
會遇到下面錯誤訊息:
位於資料表 'TB2' 的識別欄位其外顯值只有當使用了資料行清單且 IDENTITY_INSERT 為 ON 時才能指定。

為了能夠解決上述問題,必須暫時讓「有自動增值的欄位」變成沒有自動增值。可以到管理介面去變更資料表的定義,如下:(但記得匯完資料之後要記得改回去)

另外也可以透過程式來完成:
--表示在有自動增長欄位的情況下還允許新增
SET IDENTITY_INSERT  TB2 ON 

--要注意,不可以用 select * from TB1,
--必需詳細的指出欄位
insert into TB2 (SN,SName)
select SN,SName from TB1

--關閉在有自動增長欄位的情況下還允許新增
SET IDENTITY_INSERT  TB2 OFF 


需特別注意的地方有二:
一、透過 IDENTITY_INSERT ON/OFF 來完成
二、不可以用 select * from ,需明確指出各個欄位名稱,如 select SN, SName from

自己實際遇到的資料表內容,其實非常巨大。當我使用第一種方式去設定時,匯出資料沒問題,但當我要將「自動增長欄位」的欄位改回「是」的時候,SQL Server 就會跳出訊息,告訴我偵測到大量的資料,警告我會花很多時間,問我要不要等待,當然選要,結過,他跑一陣子之後就發出逾時的錯誤訊息而中斷工作,而自動增長欄位的設定也沒有改回去。遇到這情況,就一定得改用剛剛介紹的第二種方式來處理。


參考:
01:要將資料新增至有自動增值欄位的資料表中

2012年7月13日 星期五

物件為何要 new 完之後才可以使用?

關於「物件為何要 new 完之後才可以使用?」這個問題,我在 Java 的書上看到作者將程式語法搭配記憶體的配置,解釋的很清楚,也分享一下。

MyObj obj=new MyObj(10);

把上述語法拆開來,分成三個部份。

首先是: MyObj obj
宣告了 obj 這個變數,會在記憶體中,如下圖,先配置一塊空間給 obj。而這塊記憶體空間,目前是空的(null),所以我們現在並沒辦法存取 obj 裡的資料。

接著是: new MyObj(10)
這個動作,會在記憶體另外配置一塊空間,而參數10,也一併會寫入這塊記憶體空間。

最後是:MyObj obj = new MyObj(10);
將 MyObject 的記憶體位址(0x1a2b3c4d),寫到 obj 的記憶體空間裡。日後,從 obj 就可以存取 MyObject 這塊記憶體裡的資料了。



參考:
01:Java SE 6全方位學習

2012年7月6日 星期五

透過 jQuery 取得 Asp.Net 裡 CheckBoxList 被勾選的文字(二)



先前曾經在「透過 jQuery 取得 Asp.Net 裡 CheckBoxList 被勾選的文字」文章記錄了當時的解決方法,最近再次遇到同樣的問題,試著換另一種做法來處理。

CheckBoxList 在網頁上的原始碼如下:

<table id="cb" border="0">
<tr>
 <td><input id="cb_0" type="checkbox" name="cb$0" />
 <label for="cb_0">paladin</label>
 </td>
</tr><tr>
 <td><input id="cb_1" type="checkbox" name="cb$1" />
 <label for="cb_1">ltt</label>
 </td>
</tr><tr>
 <td><input id="cb_2" type="checkbox" name="cb$2" />
 <label for="cb_2">lee</label>
 </td>
</tr>
</table>


在上面程式可以發現,CheckBox 的內容,是由後面緊接著的 Label 來定義。這次透過 jQuery 所提供的 .next() 函數,去找到所挑選物件的下一個兄弟姊妹,剛好 Label 正是 CheckBox 的第一順位兄弟姊妹。換言之,只要利用:

$("#cb_0").next().text()

就可以得到第一個 CheckBox 的值 :「paladin」 了。而最初目的:透過 jQuery 取得 Asp.Net 裡 CheckBoxList 被勾選的文字,就可以改寫如下:

<script>
    function ShowData() {
        var str = "";
        $("input[type=checkbox]").filter(":checked").each(function () {
            str = str + $(this).next().text() + ',';
        });

        alert(str);
    }

    $(function () {

        $("input[type=checkbox]").click(function () {
            ShowData();
        });           
    });
</script>

比較起來,這個寫法似乎比過去的簡潔許多。

但如果 CheckBoxList 的 RepeatLayout 屬性設為 Flow,這方法是否依然可行呢? RepeatLayout=Flow 的 HTML Code 如下:
<span id="cb_flow">
 <input id="cb_flow_0" type="checkbox" name="cb_flow$0" />
 <label for="cb_flow_0">paladin</label><br />
 <input id="cb_flow_1" type="checkbox" name="cb_flow$1" />
 <label for="cb_flow_1">ltt</label><br />
 <input id="cb_flow_2" type="checkbox" name="cb_flow$2" />
 <label for="cb_flow_2">lee</label>
</span>

CheckBox 後面的第一個兄弟姊妹,正是我們所要的值,所以這方法依然還是行的通。
參考:
01:透過 jQuery 取得 Asp.Net 裡 CheckBoxList 被勾選的文字
02:.next()

2012年7月5日 星期四

jQuery 的 :checked 在 Opera 不正常



在 jQuery 裡,如果想要知道 checkbox 有幾個被選取,可以使用

$("input:checked").size()

但我發現這方法,適用於 IE、Firefox、Chrome ,卻不適用 Opera 瀏覽器。我試著用一段測試程式如下:


<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" type="text/javascript"></script>
    <script>
        function DoCheck() {
            alert($("input:checked").size());            
        }
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <input type="button" id="btn" value="check" onclick="DoCheck();" />
    <div>
    <input id="cb1" type="checkbox" name="cb"  />111 <br />
    <input id="cb2" type="checkbox" name="cb"  />222 <br />
    <input id="cb3" type="checkbox" name="cb"  />333 <br />
    </div>
    </form>
</body>
</html>

在 Opera 測試時,任意更改 checkbox 的選取組合,會發現 alert($("input:checked").size()); 所得到的結果,竟然會出錯,一開始還以為自己眼花,但沒想到是真的。在 jQuery 的 BUG TRACKER,可以看到目前已經有人提出同樣的問題了。

慶幸的,是目前還有一個各個瀏覽器都還支援的寫法:

$("input").filter(":checked").size()

所以,在 jQuery 尚未提出修正之前,還必須先以 .filter() 的方式來取代。

Opera 版本:11.62
jQuery 版本:1.7.2

2012年7月4日 星期三

UrlPathEncode 用於檔案下載出現亂碼的問題

近日在閱讀 郝冠軍 所著的「ASP.NET本質論」,裡面提到了有關 UrlEncode 與 UrlPathEncode 的差別。我對於裡面有關 UrlPathEncode 的定義比較有興趣,因為他寫著:

UrlEncode 首先使用回應中的編碼對內容進行編碼,編碼後的字節數組再看成是ASCII 字符,
其中A~Z、a~z、0~9、-、_、.、!、*、\、 (、) 被認為是安全的字符,不需要特殊編碼。
其他字符需要經過字符編碼,空格被編碼為+,剩下的被編碼為% 引導的十六進製表示方法。

UrlPathEncode 僅僅編碼Url 的Path 部分
UrlPathEncode 首先使用UTF8 編碼對字符串進行轉換,將轉換後的結果看成ASCII 串,然後,
將其中的空格替換為%20。

但自己實際上去實作一下發現下面結果:

一、輸入字串來源是 URL :http://paladinprogram.blogspot.tw/index.aspx?q=心 亞.jpg
UrlEncode →http%3a%2f%2fpaladinprogram.blogspot.tw%2findex.aspx%3fq%3d%e5%bf%83+%e4%ba%9e.jpg
UrlPathEncode →http://paladinprogram.blogspot.tw/index.aspx?q=心 亞.jpg

二、輸入字串來源非 URL :心 亞.jpg
UrlEncode →%e5%bf%83+%e4%ba%9e.jpg
UrlPathEncode →%e5%bf%83%20%e4%ba%9e.jpg

UrlPathEncode 如果是針對 URL 字串,則只會對 Path 的部份進行處理,但如果非字串的話,則全部都會被編碼。在編碼的時候,遇到空白字元的處理上,UrlEncode 是把他用 + 來取代,而 UrlPathEncode 則是用 %20 來取代。

接著,該書的作者,透過 UrlPathEncode 對空白編碼處理的特性,把他應用在處理檔案下載時,檔案名稱是亂碼的問題。可以參考:编码与解码。這方法在 IE 、 Chrome 上可以完美解決,但是在 FireFox 上卻還是不行。於是再翻出自己以前所整理過的一篇文章「檔案下載或開啟時出現亂碼檔名」,看看現在會不會激發出新的解法,蠻慶幸地,又有另一種解法可以選擇了。

這次處理亂碼檔名的過程中,有了 2 個方向去處理:
1.各家瀏覽器,如果下載檔名直接給他中文,除了 IE 之外,其餘都可以正常。所以使用區別瀏覽器的作法。
2.對於下載檔名,如果名字中間有空白,FireFox 會發生截斷的現象,但是透過引號將檔名包起來,則可以正常[ ref:檔案下載solution]。

於是新的寫法如下:

protected void btnDownload_Click(object sender, EventArgs e)
{
    string str = "江 苏.doc";
    if (Context.Request.Browser.Browser == "IE")
        str = Context.Server.UrlPathEncode(str);
    else
        str = string.Format("\"{0}\"", str);
          
    Context.Response.AddHeader("Content-Disposition", string.Format("attachment; filename={0}", str));
            
}


與先前舊寫法最大的不同,就是屏棄了

Response.HeaderEncoding = System.Text.Encoding.GetEncoding("big5");

的寫法,打破了僅支援繁體中文的限制了。

測試程式:下載

參考:
01:ASP.NET本質論
02:编码与解码
03:檔案下載或開啟時出現亂碼檔名
04:檔案下載solution

2012年7月3日 星期二

清除 Visual Studio 的 Recent Projects


在 Visual Studio 開發環境中,每次一啟動就會跑出 Recent Projects,他列出了最近所開發的幾筆專案。但有時候,連自己隨意測試的專案或是過期不想要的專案都還是一直列在上面,看了礙眼卻又不知道怎麼把他移除。

剛好於「How to remove recent projects from Visual Studio Start Page」找到不錯的方法,就是直接去修改 Registry。

1.先關閉目前已開啟的 Visual Studio
2.在開始的命令列,執行  regedit editor
3.在登錄編輯程式,找到

(適用於 VS2005)
HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\8.0\ProjectMRUList


(適用於 VS2008)
HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\9.0\ProjectMRUList



(適用於 VS 2010)
HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\10.0\ProjectMRUList

並將其 Delete,即可將不需要的專案刪除即可。

參考:
01:How to remove recent projects from Visual Studio Start Page
02:How do you REMOVE or DELETE Items from Recent Projects list in VS2005 RC?

2012年7月1日 星期日

觀功念恩的二個層次


週六下午的共學課程,有個主題是「念父母恩分享及回饋」。鵬伍師姊在現場播放了「母親的勇氣」電視廣告,影片中,表達了父母對子女的:堅忍、勇敢、愛。這段影片,彷彿就像一隻溫暖的手,撥弄著每個人感動的心弦。

自己在音樂「Goodbye Police」出來時,也開始無法忍住...

在如亨法師的三合一教授裡有提到,我們常常說要對同行、親人做「觀功念恩」,但你真的有做對嗎?在此,法師將我們的觀功念恩分為兩個層次:一個是虛弱無力,一個是對我有何饒益、對我有何啟發。

先說說第一個層次:虛弱無力。

「我看到一位師兄,很會消文,很精進,好隨喜這位師兄喔。」

法師認為,這種觀功念恩是非常薄弱的,更恐怖的,是他很容易變成觀過念怨。為什麼呢?如果今天你心情好,自然會很隨喜這位師兄,但如果你今天心情不好,或 是在趕時間,同樣的事情,就會變成:「這位師兄話很多,每次都講好久,讓我們的進度都往後延」。自己細細思索,的確表面上是對別人觀功念恩,但效果其實是 很局限、很表面。法師也提到,這種觀功念恩,是屬於「應酬式」的觀功念恩。

第二個層次:對我有何饒益、對我有何啟發?

當你看到很會消文的師兄後,開始反省自己,有沒有辦法去效學他,每天多花些時間來研讀廣論,或是透過共學的方式來提昇自己。

在小組討論中,我舉了一個例子。譬如今天看到一位師姊桌上放了一瓶里仁有機豆漿,如果我是用虛弱無力的觀功念恩,可能就會是:

「哇!好隨喜師姊護持了里仁有機食品ㄟ!」

如果今天剛好意念不夠清淨,起了比較心,可會跑出下面幾句:

「現在一瓶多少?...80 ?喔喔,我在大潤發看到的豆漿都比這便宜好多,有機食品真的貴森森」

如此的對話應答,真的是觀功念恩嗎?

在第二種層次,去思維自己看到別人的功德,對自己有何饒益,有何啟發。那可能就會變成:

看到有機豆漿,就會想到那群在有機環境下快樂工作的農夫,還有在那生存的許許多多的有情眾生,這些環境能夠得以保留,是師父當初的堅持,完全是對萬物眾生 的慈悲,對這片土地的感恩,這願力是多麼的宏大啊!佛陀幾千年前所說過的話,所傳承的教法,至今都還饒益著我們,指引著我們。今天我也要以行動去支持,去 護持,去里仁買一瓶有機豆漿。

的確,如果我們的觀功念恩,都能用第二個層次去實踐,對於自己的影響與啟發,實在是多很多。

2012年6月28日 星期四

那些年,我們一起背的經書





2012年6月16日,竹區兒童讀經班在新竹縣新豐鄉的山崎國小禮堂合辦了一場讀經班結業式。這些日子裡,每一個參與的父母、小朋友、老師、義工,都注入了一分心業力,因為有你們,我們才有所成長,也因為有你們,我們才得以感動。

上面是當天所播放的回顧影片,正如影片最後所說:

我們是何等的光榮
可以站在
改變人生的起跑點上

又是如此地歡喜
能與一群無私付出的師生
攜手並肩而行





各區讀經班教室網址:http://educational.blisswisdom.org/content/view/279/280/
2012年5月5日新竹支苑母親節奉茶站照片

2012年6月15日 星期五

preventDefault 與 returnValue



記得剛開始認識 「event.preventDefault();」 的時候,感覺在程式裡面有機會把它找幾個位置擺放,是一件很幸福的事情,這種感覺就好像能在家裡擺個花瓶是一樣的(沒多大用處,就好看而已)。沒多久,我又認識了「event.returnValue=false;」,雖然不缺,但內心深處還是有個寂寞的影子,於是我踰越了道德的那條線。表面上看似春風如意,直到有一天,我發現 IE容不下「event.preventDefault();」,FireFox 與「event.returnValue=false;」互為水火,讓我開始重新思考,誰才是我最後的選擇。後來我發現,探究到最核心,其實,只有回到最初錯誤的開始:「return false;」,才能真正見容於 IE 與 FireFox !

多年後的今天,我終於領悟,從頭到尾,愛我與接受我的,只有 Chrome 與 Opera,這些日子他們一直都在,一直都在我身邊不離不棄,只能說:人間自有真情在啊!!

語法/瀏灠器支援情況IEFireFoxChromeOpera
event.preventDefault(); X
event.returnValue=false; X
return false;

瀏灠器測試版本:
IE:8.0.7601.17514
FireFox:13.0
Chrome:19.0.1084.56
Opera:11.62

Ref:
01:解决firefox不支持window.event.returnValue = false
02:JavaScript 中的attachEvent与addEventListener方法
03:createEvent-dispatchEvent and preventDefault example

2012年6月14日 星期四

CTE Recursive

於 MSDN 「Recursive Queries Using Common Table Expressions」所說,遞迴的 CTE 必須包含兩個部份,一個是固定部份,另一個則是遞迴部份。而整個遞迴 CTE 的架構長的如下所示:


WITH cte_name ( column_name [,...n] )
AS
(
CTE_query_definition –- 固定部份的定義.
UNION ALL
CTE_query_definition –- 遞迴部份的定義,且參考了 cte_name
)
-- 使用 CTE 的宣告
SELECT * FROM cte_name
舉一個範例來說明,假設某家公司從 BOSS 開始,下面分了 AP 與 CT 兩個事業處,而每個事業處底下又分了好幾個部門。
create table #tmp(CompanyID int not null primary key,
ParentCompanyID int null,
CompanyName nvarchar(50) )

insert into #tmp (CompanyID,ParentCompanyID,CompanyName)
values
(1,NULL,'BOSS')

insert into #tmp (CompanyID,ParentCompanyID,CompanyName)
values
(2,1,'AP')

insert into #tmp (CompanyID,ParentCompanyID,CompanyName)
values
(3,1,'CT')

insert into #tmp (CompanyID,ParentCompanyID,CompanyName)
values
(4,2,'E100')

insert into #tmp (CompanyID,ParentCompanyID,CompanyName)
values
(5,2,'E200')

insert into #tmp (CompanyID,ParentCompanyID,CompanyName)
values
(6,3,'K100')

insert into #tmp (CompanyID,ParentCompanyID,CompanyName)
values
(7,3,'K200')

insert into #tmp (CompanyID,ParentCompanyID,CompanyName)
values
(8,3,'K300')

select * from #tmp

有了以上的基本資料後,接下來透過 CTE 的遞迴語法來獲得公司的樹狀結果。

;with CompanyTree(ParentCompanyID,CompanyID,CompanyName,CompanyLevel)
as
(
 select ParentCompanyID,CompanyID,CompanyName,0 as CompanyLevel from #tmp 
 where ParentCompanyID is null
 union all
 select c.ParentCompanyID,c.CompanyID,c.CompanyName,p.CompanyLevel+1 from #tmp c
 inner join CompanyTree p on c.ParentCompanyID=p.CompanyID
)
select * from CompanyTree OPTION (Maxrecursion 20)

drop table #tmp

上面 CTE 的語法中,固定部份是:
select ParentCompanyID,CompanyID,CompanyName,0 as CompanyLevel from #tmp
where ParentCompanyID is null

這語法很明顯是把父子關係裡我們所謂的根節點 Root 找出來,所以他的查詢條件是找父親索引 (ParentCompanyID) 為 NULL 的資料。自訂欄位 CompanyLevel 則是用來顯示樹狀結構的深度,由於在固定部份所取得的都是 Root 資料,所以樹狀深度都是 0,所得到的結果用 T0 來表示 .

接下來,則是透過 UNION ALL 將遞迴部份的資料加進來。

select c.ParentCompanyID,c.CompanyID,c.CompanyName,p.CompanyLevel+1 from #tmp c
inner join CompanyTree p on c.ParentCompanyID=p.CompanyID


我們用 T1 來表示第 1 次迴圈的結果集合。由目前的範例,我們會獲得

主要是因為在固定部份 T0 我們一開始得到的是



所以透過 c.ParentCompanyID=p.CompanyID
(或視為 #tmp.ParentCompanyID = CompanyTree.CompanyID)
的關聯條件就可以得到 AP 與 CT 兩筆資料。這一次的關聯條件,翻譯成白話,就好像是問說:原始資料裡面,有誰的爸爸叫做 1 的阿?而翻譯成SQL 條件,就是:

#tmp.ParentCompanyID=1


,於是就會跑出 AP 與 CT 這兩個結果。

接著進行第 2 次迴圈後,則 T2 所得到的結果,分別是 :

AP 的
與 BP的

這兩部份的加總,也就是 E100、E200、K100、K200。


而遞迴的特性,會一直持續的往下探尋,直到找不到資料為止,也就是從 T1 … Tn
目前的範例,則是將  T1,T2 這 2 層的資料回傳。
最後的樹狀結果加總 T0 … Tn 所有資料 ,如下所示:


或許一開始會有個疑問,為什麼一定要用 UNION ALL ,為什麼一定要用 inner join,但當你試著用其他選擇,例如改成 UNION 後,會得到以下錯誤訊息:

Recursive common table expression 'CompanyTree' does not contain
a top-level UNION ALL operator.


改成 left join 後會得到

Outer join is not allowed in the recursive part of a
recursive common table expression 'CompanyTree'.


就可以發現,其他的選擇,會造成 CTE 遞迴的語法錯誤,沒得商量。

此外,CTE 其實還有一個比較特別的參數設定可以使用。由於其強大的遞迴功能,為了避免使用者因為邏輯上的錯誤而造成了無限迴圈,他提供使用者自行定義要往下探尋幾層的限制。假設你已經知道自己的樹狀結構不會超過 10 層,那你可以在程式裡面加上  OPTION (Maxrecursion 10),當迴圈超過10層時,程式就會出現錯誤訊息而終止,不會跑無限迴圈。
超過10 層的錯誤訊息:

The statement terminated. The maximum recursion 10 has been exhausted
before statement completion.


完整程式如下:
;with CompanyTree(ParentCompanyID,CompanyID,CompanyName,CompanyLevel)
as
(
 select ParentCompanyID,CompanyID,CompanyName,0 as CompanyLevel from #tmp 
 where ParentCompanyID is null
 union all
 select c.ParentCompanyID,c.CompanyID,c.CompanyName,p.CompanyLevel+1 from #tmp c
 inner join CompanyTree p on c.ParentCompanyID=p.CompanyID
)
select * from CompanyTree OPTION (Maxrecursion 10)

這個 Maxrecursion 或許你不會想去設定,但必須要注意,即便你沒設定,但它的預設值卻幫你設定好了,預設是 100 ,也就是說,當你的探尋深度有可能超過100 時,記得要將這個參數調整為設合您的大小。他的設定範圍為 0 ~ 32767,0 則表示沒有限制,也就是說可以超過 32767,但請注意,當你的迴圈深度達到這麼深時,整個的執行效能是非常差的。


Ref:
01:Recursive Queries Using Common Table Expressions

02:一般資料表運算式(Common Table Expressions, CTE)

2012年6月11日 星期一

關於 SQL 的 Common Table Expression


Common Table Expression (CTE) 是 SQL Server 2005 開始加入的新成員,千萬別跟 ETC 搞混囉(雖然把名字倒過來唸還真的一模一樣)!!

會對 CTE 開始有興趣,是因為以前在撰寫比較複雜且多層的子查詢時,自己當下是很清楚要寫什麼,可是經過一段時間後,自己已經有點不懂當時的想法,更不用說其他接手維護的工程師了。舉例來說,以前寫的好幾層子查詢,可能長得如下:

select A1 from A where A2 in
 (
        select B1 from B where B2 in
                    ( select C1 from C where C2 like '%test%')
 )

透過 CTE,可以讓我們不用這麼辛苦,可以暫時先把每個子查詢的結果存放在一個記憶體變數中,而結果會如下:
;with tC as
      (select C1 from C where C2 like '%test%')
,tB as
      ( select B from B where B2 in (select * from tC))
select A1 from A where A2 in (select * from tB)

在 Insert 的用法:

create table #tmp (Country nvarchar(200),OkNum int)

;with T as
(select Country,OkNum from AsiaData )
insert into #tmp select Country,OkNum from T

select * from #tmp
drop table #tmp
在 Group 的用法:
;with T as
(
   select Country,OkNum from AsiaData union select Country,OkNum from EuropeData
)
select Country,sum(isnull(OkNum,0)) OkNum from T
group by Country
個人覺得,使用 CTE,程式碼不見得會寫得比較少,但至少對閱讀上來說,的確是比較容易理解。在使用上,除了一般所撰寫的 T-SQL 查詢之外,還可將 CTE 用於使用者自訂的函數( Function)、預存程序( Stored Procedure)、檢視( View )、觸發程序( Triggers )。

CTE 語法的基本架構如下(Ref 01:Using Common Table Expressions):
--定義 CTE
;WITH CTE_運算式名稱 [ ( column_name [,...n] ) ]
AS
( CTE_查詢_定義 )

--使用 CTE
SELECT <column_list> FROM CTE_運算式名稱;

在定義 CTE 時,如果沒有指定欄位名稱,則結果的欄位名稱預設會跟 CTE_查詢_定義 裡的結果一樣;如果有指定欄位名稱,則欄位名稱的值會跟 CTE_查詢_定義 裡出現的結果順序一樣,且 CTE 的欄位名稱是不可重複的。舉例來說:
;with T (T1,T2) as
     (select C2,C1 from C )

這裡要注意,CTE 的結果裡,T1 欄位所呈現的值會是 資料表 C 的 C2 欄位,原因是 CTE 的結果欄位是依據 CTE_查詢_定義 的順序來決定。(Ref 02:SQL with)

使用 CTE ,有個比較特別的現象,他並不像 View 或 暫存資料表一樣可以一直被引用,實際上,他只能被引用一次。也就是說,當你定義完 CTE 的結構後,你必須在後面接著馬上使用他,使用完了之後,他就不見了。
;with EuropeData (Country,OkNum) as
(
   select top 5 Country,OkNum from AsiaData

)
-- 第一次查詢
select top 5 Country,OkNum from EuropeData
-- 第二次查詢
select top 5 Country,OkNum from EuropeData

在上面的範例中,一開始我定義了 CTE 的運算名稱為 EuropeData ,其實這名稱也剛好是我資料庫裡的某一個資料表,只是我刻意把 CTE 的名稱取的跟我既有的資料表名稱一致,只是 CTE 的結果,其實是從 AsiaData 而來。當定義完我的 CTE 之後,連續 2 次執行查詢,則可以在結果中發現,第一次的查詢,資料是從 AsiaData 而來。但第二次的查詢,因為自訂 CTE 的記憶體空間已被回收,所以會抓到原先資料庫已經存在的 EuropeData 資料表。

CTE 除了上述的介紹之外,他還有另一個蠻有趣的主題:遞迴,這功能可以讓我們在處理行父子關係的資料表、組織圖,更有效率,於後面再詳述。

Ref:

01.Using Common Table Expressions
02.SQL with
03.SQL中使用WITH AS提高性能-使用公用表表达式(CTE)简化嵌套SQL
04.一般資料表運算式(Common Table Expressions, CTE)
05.T-SQL -- COMMON TABLE EXPRESSION (CTE) 教學重點筆記
06.SQL 2005 T-SQL Enhancement: Common Table Expression
07.Performance Effect of Common Table Expressions in SQL Server 2005

2012年5月31日 星期四

ASP.Net 使用 Form 認證設定使用者登入資訊

在 ASP.Net 專案,如果使用的認証方式為 Form 認證,當確認了登入者身份後, 會將使用者是否已經經過認證的訊息寫到 Cookie裡,而 Cookie 名稱為 ASPXAUTH。透過以下方法,可以看到 Cookie 的值。

Request.Cookies[“ASPXAUTH”].Value

或是用下面這段迴圈去抓出目前的 Cookie 資訊:
StringBuilder sb = new StringBuilder();
        
for (int i = 0; i < Request.Cookies.Count; i++)
{
     sb.Append(string.Format("<br>Name:{0} Values:{1}", Request.Cookies[i].Name,Request.Cookies[i].Value));
}

lb_Cokies_info.Text=sb.ToString();

當然,你所看到的 Cookie 值是經過編碼加密後的一串文字。
剛剛提到,確認並通過了使用者身份後,會將資訊寫到 Cookie 裡,那這寫入的動作,是誰做?不用想太多,當然是程式設計師自己去做啦~

介紹目前自己常常看到的三種寫入認證 Cookie 方式:

一、RedirectFromLoginPage()
舉例來說,假設使用者 paladin 填寫完帳號密碼,並檢查無誤後,就可以用下面語法將 “paladin” 這帳號訊息寫到 ASPXAUTH Cookie裡。所以RedirectFromLoginPage 方法裡的第一個參數,是用來存放被認可的使用者帳號資訊。

第二個參數還蠻值得玩味的,官方說法是表示建立持久性 Cookie (跨瀏覽器工作階段儲存的 Cookie) (Ref.http://msdn.microsoft.com/zh-tw/library/bk50ykcd.aspx) )。經實際測試後,所謂的跨瀏覽器並不是跨 IE、Chrome、Firefox,而是指是否允許在另開 IE 瀏覽器時使用同一份 Cookie,所以IE 裡的 ASPXAUTH Cookie 與 Chrome 或 Firefox 都不一樣,他們都有各自的 Cookie。但如果你是使用開啟新索引標籤的方式,則不管值為何,都是使用同一份 Cookie,每一個標籤都抓的到。

此外,當第二個參數設為 true 時,預設 Cookie 的存活時間約 30 分鐘,也就是說,當你把所有瀏覽器都關閉時,在30分鐘內都還可以存取到有效的 Cookie 值。但如果參數設為 false,則當視窗都關閉後,Cookie也就會自動失效。
using System.Web.Security;

FormsAuthentication.RedirectFromLoginPage(“paladin”, false);

此外,使用RedirectFromLoginPage()方法時,顧名思義當他執行完後,應該會把頁面轉到(Redirect)其他頁面去吧,實際上也真是如此。當執行完後,他會先檢查目前的 URL 裡是否有設定 ReturnUrl,如果有,就會將頁面轉到  ReturnUrl 所定義的頁面。舉例來說,如果你的驗證登入的頁面是:



http://paladin.love.girl.tw/login.aspx?ReturnURL=hello_baby.aspx

當執行 RedirectFromLoginPage() 方法後,你的頁面就會轉到

http://paladin.love.girl.tw/hello_baby.aspx



但如果你忘了或不想在 URL 上面指定 ReturnURL 參數,那也沒關係,他會根據目前 FormsAuthentication.DefaultUrl 所定義的值來決定歸處,但預設值是 default.aspx。這裡需要注意的是,FormsAuthentication.DefaultUrl 只能取得他的值,卻不可直接設定他,如果真要改變的話,需在 web.config 裡的 defaultUrl 調整(Ref.FormsAuthentication.DefaultUrl 屬性)。


<authentication mode="Forms">
  <forms loginUrl="member_login.aspx"
    defaultUrl="index.aspx" />
</authentication>

二、SetAuthCookie()
如果對於 ReturnURL 還耿耿於懷,或是希望自己能夠完全主導認證後頁面轉向的控制權,則可以使用 SetAuthCookie() 方法來完成。他的使用方式跟前面的 RedirectFromLoginPage() 非常相似,舉例來說:


FormsAuthentication.SetAuthCookie(“paladin”, false);

第一個參數是將登入者的帳號寫到 ASPXAUTH Cookie 裡,第二個參數也跟RedirectFromLoginPage() 一樣,用來表示是否建立持久性 Cookie。最大的不同,在於SetAuthCookie() 執行完後,不會將你目前的頁面帶離,而是留給程式設計師自行決定。

三、FormsAuthenticationTicket
前面兩種作法,都算是輕巧簡便型,他們把很多複雜的設定都封裝起來,所以用起來很方便,但從另一個角度來說,也就失去許多原本可以設定的選項,變得比較沒有彈性。如果希望自己撰寫個比較有彈性的寫法,可以利用 FormsAuthenticationTicket 類別來實作。以一段程式來說明:

      bool isPersistent = false;

        FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
            1,
            "paladin",
            DateTime.Now,
            DateTime.Now.AddMinutes(30),
            isPersistent,
            "Admin",
            FormsAuthentication.FormsCookiePath
            );

        // Encrypt the ticket.
        string encTicket = FormsAuthentication.Encrypt(ticket);


        HttpCookie authenticationCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);

        //將HttpCookie是否永久存在的屬性與FormsAuthenticationTicket綁在一起
        if (isPersistent)
            authenticationCookie.Expires = ticket.Expiration;

        // Create the cookie.
        Response.Cookies.Add(authenticationCookie);


首先建立了一個 FormsAuthenticationTicket 類別,建構式裡有7個參數可以設定。
1.設定 ticket 的版本
2.設定要存放的使用者帳號
3.設定 ticket 產生的時間
4.設定 ticket 過期時間
5.設定這個 ticket 是否是持續的
6.允許使用者任意輸入的文字(但不是無限制長度,Cookie 能保存的文字長度有限制)
7.Cookie 存放路徑

這裡的參數設定,主要針對第六個參數要特別注意。通常我們確認了使用者身份,且將使用者的帳號記錄下來後,還有可能會進一步去將他的群組資料也放進去。例如使用者是「Admin」還是「Guest」,或是同時具備多種群組身份。第六個參數允許你存放的是字串格式,可以拿來存放群組資訊。如果準備存放多種群組角色時,一般都可以使用 | 或其他分隔字元來區別,例如:Admin|Guest 。但當你在使用時,需特別注意,因為 Cookie 的長度並非無限制,如果將太多的資訊(例如:分機、住址...)都全部往裡面寫,那就有可能會造成錯誤,所以盡可能寫比較重要且精簡的內容,過多的相關資訊,可以考慮存放索引值就好,需要用到時再去資料庫反查。


最後,我們可以透過以下方式取得先前所存放的使用者資訊:





//取得使用者帳號
        if (User.Identity.IsAuthenticated)
        {
            lbUserInfo.Text = string.Format("目前登入者帳號為:{0}", User.Identity.Name);
        }
        else
        {
            lbUserInfo.Text = "目前尚未登入";
        }

//取得使用者自訂資料
string cookieName = FormsAuthentication.FormsCookieName;
HttpCookie authCookie = Context.Request.Cookies[cookieName];
if (authCookie == null) return;
FormsAuthenticationTicket authTicket =FormsAuthentication.Decrypt(authCookie.Value);
Response.Write(authTicket.UserData);



以上一切的一切,都是假設您是使用 Form 認證才會成立的,所以在 web.config 中,必須確認有以下的設定喔。
<authentication mode="Forms">

參考:
01.概略解釋 Forms Authentication 的運作

2012年5月27日 星期日

一念之間的差異

盧智芳於 Cheers 提到,她在國三畢業典禮前夕,校長到每個班級對畢業生講話,他一開始什麼都沒說,只在黑板上畫了一個簡單的圖,然後說:在人生中,畢業典禮就像這個角的頂點,一開始,每個人的出發點都一樣;但是隨著時間的推移,有一個因素會讓彼此的差異愈來愈大,愈來愈大。這個因素是什麼?就是一個人的觀念。要是觀念一開始是錯的,以後就全錯了。

記得禪聞法師也曾提到:

不對的方法,做再多,都是錯,浪費時間。
對的方法,慢慢做,才會進步,不見得時間會花比較多。

所以,一開始能夠找到正確的觀念,正確的方法,是決定你圓滿的關鍵。這裡強調的是圓滿,而不是成功。因為成功有可能只是表象,例如你成功獲得了選舉,卻使用了見不得人的手段,雖說你成功了,但卻不圓滿。

侯吉諒於其「一念之間」文章中有提過:

「人很容易受到環境的影響,因此需要一定程度的內在修養,才能不受外來惡戾之氣的影響,能知不足,則不敢不努力,能以平常心看得破一時際遇的興衰,則世間榮辱可以不驚不畏,能無所住而生其心,則不會有偏見,能以善待人,於是物物大好,事事好極了,不必求福而自然萬得福。」


雖說是外來惡戾之氣的影響,但說到底,還是自己內心調伏的問題。會左右我們選擇的因素,常常是因為「我無甲意輸的感覺!(台語)」。因為害怕,所以採取了許多明知不可為而為的作法,最後反而輸的更慘,付出更大的代價。


在強調運動精神的公平競賽中,競爭結果的輸贏並不是壞事。它訂出公平的標準,讓參賽者分出高下。贏的人很清楚知道哪一方面的表現,適合自己發揮長才;輸的人也可以藉此檢討自己努力不夠、或是方法有問題,還是根本就是把自己放錯了位置,不該參加這個賽局。

如果不能接受自己落在輸局的處境,進而透過檢討反省找到新的方向,就會讓自己執著在「輸」的負面感覺裡,變得憤怒而失去理性。

在該篇文章中,有幾個很棒的觀點,節錄於後:

  1. 願意成全別人,是更實際的贏家
    分享一個精彩的故事:
    左宗棠,並非天生贏家。他也有過輸的紀錄。據說,非常擅長下圍棋的他,曾經在一次出兵前的空檔,路過一個號稱「天下第一」的棋王住處,於是他主動下馬挑戰,當場贏了三盤棋,爽快地離開趕赴戰場之前,還特別糗了一下主人「天下第一」的封號。

    為朝廷打完勝仗,班師回朝途中,左宗棠又再度路過棋王住處,意猶未盡地進去下棋,想要再度衛冕成功,沒想到這一回卻連輸三盤。棋王微笑地說:「上一次你來找我下棋的時候,正好是要率兵出征,我不能讓你因為輸棋而挫失銳氣。現在你打勝仗回來,我就當仁不讓了。」

    棋盤上的輸贏,也許有真有假。人生裡的輸贏,也未必就此見真章。但只要擁有足夠的自信,能夠體察別人的需要,願意成全別人,即使表面上輸了,並不是真的輸,反而可能是更實際的贏家。棋王,贏了友誼,也贏了自己。
  2. 輸贏不是跟別人的比較,而是對自我的挑戰
    戰勝自己一直想「贏過別人」的念頭,是層次更高的一種贏法。
  3. 輸贏不是單線的,而是多面的
    學業或事業很成功的人,可能忽略了人際關係或家庭幸福。評估輸贏時,別忘了各個面向的均衡。
  4. 輸贏不是表面的,而是一體的
    輸贏是一體兩面的,贏到某些東西的時候,可能正失去其他的東西。


參考:
01: Cheers 130期,一念之間的差異
02:侯吉諒:一念之間
03:面對自我挑戰-輸與贏在一念之間(每週一讀)

2012年5月22日 星期二

.Net 1.1 的有痛升級

於 .net 1.1 ,專案名稱 UpTestWeb ,預設在 /bin 資料夾就會產生 UpTestWeb.dll。當要升級為 .net 2.0 以上時,一般都習慣使用升級精靈來自動將專案轉換為新的 .net 版本。目前我則是將 .net 1.1 升級為 .net 4.0,升級完成後,一開始執行都沒甚麼大問題,但當有需要修改程式時,才發現原先自己定義的一些類別,不管怎麼改,程式都不會去執行剛剛修改的地方,還是照著他自己的舊寫法去跑,真的還蠻奇怪的。

理論上,於 .net 4.0 時,/bin 資料夾並不會出現跟專案名稱一樣的 dll 檔,但檢視我升級後的專案,裡頭卻有一個 UpTestWeb.dll ,於是我把他砍了,接著一堆錯誤就出來了,眼睛很花,頭...很沈重。

在網路上看到許多前輩討論 .net 1.1 升級的經驗談,其中於 shang 的一篇文章「 从ASP.NET 1.1升级到2.0 」提到:「将所有独立的代码文件和AssemblyInfo.cs都被移到 App_Code 目录下」,發現自己少了這個步驟。原來,如果將與 .aspx 無關的 .cs 都移到 /App_Code ,於 .net 2.0 以後才可以讓其他程式碼存取。基本上,我們自己所定義的類別程式,都應該放在 /App_Code,若是已經編譯過的 dll 檔,則是擺放在 /Bin 下面,如此其他程式才能參考的到(Ref.ASP.NET 網站中的共用程式碼資料夾)。

在這次升級的過程中,我大致做了以下動作:

01:將與 .aspx 無關的 .cs 都移到 /App_Code
02:把 .aspx 裡的Codebehind 改成 CodeFile,同時也要調整 Inherits。舉例來說:
<%@ Page language="c#" Codebehind="Default.aspx.cs" AutoEventWireup="false" Inherits="UpTestWeb._Default" %>

換成

<%@ Page language="c#" CodeFile="Default.aspx.cs" AutoEventWireup="false" Inherits="_Default" %>

03:把 .aspx 的
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >

換成

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

04:將 .aspx.cs 裡的 namespace XXX { ... } 拿掉, 並將
public class xxx : System.Web.UI.Page 改成
public partial class xxx : System.Web.UI.Page

05:於 .aspx.cs 裡,類似 protected System.Web.UI.WebControls.Label lblUserName; 這種的宣告都砍掉。

參考:


01:ASP.NET 網站中的共用程式碼資料夾

02:A Beginner's Guide to ASP.NET Application Folders

03:从ASP.NET 1.1升级到2.0

04:KB-ASP.NET 2.0 網站部署的變革

2012年4月20日 星期五

hyperlink 可以 disabled 嗎?

記得以前在網頁上,如果想要把一個按鈕變成不可點擊,只需要將該按鈕的屬性設為  disabled  就可以了。只是現在我是有很多個超連結(hyperlink),也希望透過設定 disabled 就能把該超連結變成無效且不可點擊。

於是我試著寫出以下的測試程式:

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>    
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <a href="#" onclick="alert('A1');" >A1</a> <br />
    <a href="#" onclick="alert('A2');" >A2</a> <br />   
    <a href="#" onclick="alert('A3');" disabled="disabled" >A3</a>
    </div>

    <div>
    <input type="button" value="B1" onclick="alert('B1');" /> <br />
    <input type="button" value="B2" onclick="alert('B2');" /> <br />
    <input type="button" value="B3" onclick="alert('B3');" disabled="disabled" />
   
    </div>
    </form>
</body>
</html>

這似乎沒甚麼難的,只是... 當我開啟 firefox、chrome 等瀏覽器時,原先被設為 disabled 的超連結竟然還可以被點選,且觸發 onclick 事件。這種現象在 Button 身上並不會因瀏覽器不同而有所差異,目前只有超連結才會。

為了避免這不一致的運作行為,我參考了 Disable anchors in Chrome/WebKit/Safari 的作法,去判斷每一個超連結,如果屬性有設 disabled,就避免觸發 click 事件。

所以加上一段 jQuery Code 去處理所有 disabled 的超連結後,就可以適用於各瀏覽器了。


<script>
    $(function () {
        //避免 disabled 的 hyperlink 被觸發, chrome,firefox 需要這段處理
        $("a").click(function () {
            if ($(this).attr("disabled") == "disabled") {

                event.preventDefault();
            }

        });
    });
</script>
 
參考:
01: Disable anchors in Chrome/WebKit/Safari
02: jQuery .attr(“disabled”, “disabled”) not working in Chrome
03:.prop() vs .attr()

2012年3月13日 星期二

雖然你已經沒了,但我還是會難過

走在大賣場的餅乾區,隨手一拿,幾乎大部分的零食所標示的反式脂肪酸都寫 0,就連泡麵也都以顯著的標語寫著:『本產品絕不含防腐劑』,人家都已經做得這麼有誠意,只差沒跪在你面前發誓,難道你不給台灣廠商一個機會嗎?

我還特別挑了一盒孔雀餅乾。它不僅反式脂肪酸是 0 ,而且還是台灣製造,重點是它還增重 45 公克,就這樣,它上了我的推車,入了我家冰箱,也進了我的肚子。



在偶然的機會裡,說真的,機會不高。我拿起塵封已久的康健雜誌,翻到第 158 期的一篇:『零反式脂肪酸,真的嗎?』它裡面有提到:

自政府於 2008 年規定,如果產品原料未使用氫化加工工序,且含量在 0.3 克/ 100 克以下,反式脂肪酸一欄即可標示為「」。

0  +  0  +  0  ≠   0


這數學式還真的走入了你我的日常生活當中,你會對這完美的數據感到高興,還是更顯不安呢?往好處想,世界衛生組織建議每人每天的攝取量以不超過 2 公克為限,0 與 2 之間,似乎還有著一些些的距離...

但是切記,別買「樂天小熊餅乾」給你們家寶貝吃,因為在康健雜誌於 2011年11月8日 ~11月11日 所抽查的報告中,它的反式脂肪酸是 4.09 。



參考:
01:康健雜誌 158 期
02:零反式脂肪,真的嗎?
03:小心隱形的油脂殺手-反式脂肪!

不會吧!又掛了

[InvalidOperationException: 由於該物件目前的狀態,導致作業無效。]
   System.Web.HttpValueCollection.ThrowIfMaxHttpCollectionKeysExceeded() +2692482
   System.Web.HttpValueCollection.FillFromEncodedBytes(Byte[] bytes, Encoding encoding) +61
   System.Web.HttpRequest.FillInFormCollection() +148

[HttpException (0x80004005): URL 編碼型式資料無效。]
   System.Web.HttpRequest.FillInFormCollection() +206
   System.Web.HttpRequest.get_Form() +68
   System.Web.HttpRequest.get_HasForm() +8743911
   System.Web.UI.Page.GetCollectionBasedOnMethod(Boolean dontReturnNull) +97
   System.Web.UI.Page.DeterminePostBackMode() +63
   System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +133


出現上面錯誤訊息時,正想要設個中斷點來看看到底哪邊發生錯誤,沒想到,連 Page_Load 事件都還沒進去,且遺言也還沒交待清楚,畫面就已經死掉了。

原來,在 2011 年12月,於德國的 Chaos Communication Congress 會議中,有人展示了透過少量的 Http Request 就可以讓網頁站台變得很忙碌,沒多久就可以讓網站忙到無法再回應新的 Http Reguest 請求而達到阻斷服務攻擊(Denial of Service)的效果。

隨後,在保哥的文章中找到一篇相關文章,於其技術摘要說明裡指出:


我們在開發 ASP.NET 網站時通常都會使用 Request.Form 物件來取得透過瀏覽器 POST 過來的資料,而此物件型別為 NameValueCollection 類別,該類別儲存 Key 的方式是透過雜湊運算的方式計算出來的 (hash-table data-structures),主要目的就是為了用極快的速度找到該 Key 的對應資料,不過此類別自行實做了有別於 .NET Framework 內建的雜湊演算法,它會將所有傳入的 Key 都先轉換為大寫,並使用名為 DJBX33X (Dan Bernstein's times 33, XOR) 的雜湊演算法進行計算,這將導致此演算法有機會遭到 Meet-in-the-middle 攻擊。

駭客能利用這個弱點實做出輕易可達成的 雜湊碰撞攻擊 (Hash collision attacks),此類攻擊可透過傳入大量的 POST 資料並且夾帶許多特別計算過的 Key 值,讓這些 Key 值在這些 Hash-table 中產生出相同的雜湊值,如此一來在這個 Hash-table 中就會出現許多擁有相同雜湊值的 Key,這便是「雜湊碰撞」的由來。而這類雜湊碰撞的情況正是 DJBX33X 演算法的致命傷,當一個 NameValueCollection 中存在著過多的「雜湊碰撞」就會導致電腦花上許多時間對該 Hash-table 進行運算,所以會讓 ASP.NET 執行緒花上數十分鐘到數小時的時間來操作相對應的 Key 值,如此一來便會耗盡網站伺服器的 CPU 資源,進而達到阻斷服務的目的。


就在 Chaos Communication Congress 會議之後,微軟很迅速的將這弱點進行了補強,也提供了 windows update 的服務,基本上,只要你的電腦有乖乖接受每個微軟的安全更新,理論上都已經可以避免如上所說的攻擊手法了,只是,微軟做了甚麼補強?

微軟開始重訂了一個遊戲規則。要求大家 Post 到後台的物件項目不可以超過 1000 個。包括以下:


1.<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
2.<input type="hidden" id="h_field" runat="server">
3.有已挑選值的 DropDownList (如果下拉清單裡面沒有任何資料,則不算)
4.有已挑選值的RadioButton(如果 RadioButton 裡面沒有任何被選取的資料,則不算)
5.有已挑選值的CheckBox(如果 CheckBox 裡面沒有任何被選取的資料,則不算)
6.有已挑選值的ListBox(如果 ListBox 裡面沒有任何被選取的資料,則不算)

自己詳細檢查了一下出錯的那一頁程式,上面的輸入控制項的確玲郎滿目啊!!

不過,剛開始對於微軟這種補強方法,還真的有點失落,心裡滴咕著,這算甚麼補強嘛,是補弱吧~那真的有需要使用到這麼多欄位需求時,難道得跟客戶說:


事實並非如此的。雖然微軟預設只提供上限 1000 ,但如果有更大的需求時,可以在 web.config 裡的 <appSettings> 加上 :

<add key="aspnet:MaxHttpCollectionKeys" value="2500" />

數值 value 可依實際你所想要允許的最大數量來決定。

Denial of Service 攻擊,的確是一種很難克服的難題。就算你的程式本身沒有問題,還是很有機會被攻擊,更何況被有心人士找到漏洞呢。相信日後這種攻擊手法,會越來越常見。這次微軟非常迅速的發佈更新,讓所有的 .Net 網站可以降低被攻擊的機率,真的值得讚賞。只是,我並沒有因此而得到特別的喜悅。因為我是接到來電說:「paladin,快來看,你的系統當掉了!」。


參考:

01:ASP.NET 發現重大資安弱點影響範圍涵蓋 ASP.NET 1.1 ~ 4.0
02:具有大量表單金鑰、檔案或 JSON 裝載成員的 ASP.NET 請求失敗,並產生例外狀況
03:ASP.NET Security Update Shipping Thursday, Dec 29th