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