Regex 本身已經十分複雜, 但不知道大家有沒有想過, 它的解析引擎和處理機制又是如何實作的呢? 一般來講, Regular Expression 的解析引擎可以分為三種, 一種叫做 DFA (Deterministic Finite Automation, 決定性有限自動機制), 另一種叫做 NFA (Nondeterministic Finite Automation, 非決定性有限自動機制), 還有一種叫做 POSIX NFA。.Net 採用了傳統的 NFA 引擎, 使得它既能兼顧速度與功能, 但缺點就是由於傳統 NFA 只接受它找到的第一個相符比對,它也可能讓其它比對無法被找到 (POSIX NFA 雖然可以找到, 但速度緩慢)
2010/9/8
2010/4/23
[入門] .NET 自訂型別
我可以理解為什麼我老婆開了十年的同樣一部車, 都不知道原來在 N 檔和 D 檔之間切換可以不必去按那個安全鈕; 但是我卻無法理解為什麼有一個已經寫了好幾年 .NET 程式的人, 在這輩子中竟然沒有寫過任何一個自訂型別。對這件事, 我唯一想得到的解釋, 就是從來沒有人跟他說過自訂型別的好用之處。
所以, 這件事也讓我決心把自訂型別特別整理成一個主題。雖然我在本文中會用到一些進階技巧, 對真正的初學者而言恐怕有點吃力, 但是這畢竟是 .NET 中很基礎、很一般的常識, 所以我還是把它歸類到「入門文章」裡面
2010/4/5
萬眾矚目的 HTML 5
如果我們回顧一下 HTML 的歷史,HTML 4 是在 1997 年底制訂的,我們現在普遍使用的 HTML 4.01 發表於 1999 年底。此外,XHTML 1.0 發表於 2000 年初,XHTML 1.1 則發表於 2001 年中。距今差不多十幾年的時間當中,我們並沒有看到太多革命性的改變。
不過,現在我們終於可以看到 HTML 5 正在制定中,而且各種瀏覽器都已經開始支援,包括 IE 9/10、Chrome、FireFox、Opera 等等。
HTML 5 的前身稱為 Web Application 1.0,早在 2004 年中就已經開始制定規格。其原先的目的在於消除網路使用者對於所謂 RIA 工具 (例如 Adobe Flash, Microsoft Silverlight 等) 的依賴,不過現在交由 W3C 接手制定規格後,HTML 5 儼然已成為下一世代網頁標準的正式接班人。
2010/3/28
[入門文章][JavaScript][jQuery] jQuery 從頭學起
本來是不打算寫任何關於 jQuery 的入門文章了, 因為這種資訊在網路上俯拾皆是, 甚至都泛濫了。然而在實際使用時, 發現 jQuery 的精簡語法在彼此之間也實在太相像了, 如果不自己做個筆記以供隨時速查, 總是記不住。所以, 還是寫個一篇或是幾篇拿來放著, 想查閱的時候也方便。
2010/3/2
[Regex] 進階群組建構
Regular Expression (規則運算式, 以下簡稱為 Regex) 實在不是一個易懂的課題; 但是, 如果我們要在一大堆文字裡面挑出部份具有特定規則的隻字片語, Regex 恐怕是我們唯一能用得上的工具
2010/1/25
運用 CSS 讓過長文字以省略符號取代
在某種情況下, 我們可能需要把一段過長的文字從中截斷, 並在截斷處以省略符號 (...) 取代, 表示這段文字其實尚未結束, 只是被省略。如果你的文字是從資格庫中取得, 那麼你可以從資料庫取出時就載斷字串並加上簡略字元後傳回; 然而, 如果你不想把原始資料真的截斷 (若依上文, 你取出的資料是已經被截斷的, 除非重新讀取, 否則勢必拿不到完整的版本), 那麼你可以嘗試我現在要提供的方法, 那就是以 CSS 設定, 達到相同的效果。
請先來看看程式:
<style>
.AutoSkip {
overflow:hidden;
text-overflow:ellipsis;
white-space: nowrap;
}
</style>
<html>
<body>
<div class="AutoSkip" style='width:600px;'>
在某種情況下, 我們可能需要把一段過長的文字從中截斷, 並在截斷處以省略符號 (...) 取代,
表示這段文字其實尚未結束, 只是被省略。如果你的文字是從資格庫中取得, 那麼你可以參考我寫的
「[SQL Puzzle] 載斷字串並加上簡略字元後傳回」一文, 當你取出資料時, 就把文字截斷。
</div>
</body>
</html>
雖然在 <div> 段落中夾著一段很長的文字, 但如果你實際執行上述程式, 你會發現在 600px 處文字確實被截斷, 而且自動加上了省略符號, 變成了如下的樣子:
在某種情況下, 我們可能需要把一段過長的文字從中截斷...
當然, 這個結果和在資料庫中取出時就截斷文字看來有一樣的效果, 但實際上, 採用 CSS 的方法其實並沒有改動到文字; 它只是讓你覺得文字看起來好像被截斷而已。
運用這種方法, 無疑是給了程式設計師更大的彈性, 讓你在組版面時具有更靈活的空間。比方說, 由於資料並沒有被真的截斷, 所以, 假設你打算把資料繫結到 GridView 容器中, 限於版面, 你必須把過長的文字截斷, 但是你另一方面卻又可以把完整的文字繫結到 tooltip 裡面。如果採用第一種做法 (在讀取資料時即予截斷), 就無法做到這一點。
在上述程式中, 我所示範的技巧全部都在 AutoSkip 這個樣式裡了, 所以老實說也沒有什麼太大的學問; 照著寫就對了!
不過經過測試, IE8 與 Chrome 4.0 都可以正常顯示, 在 FireFox 上卻不會出現簡略字元, 而是硬生生把字串剪斷了, 如果那個地方是中文字, 有可能只顯示一半, 不甚美觀。
[SQL] 使用 Stored Procedure 動態組成 SQL 查詢指令
以下示範一個可以動態組成查詢子句的 SQL 指令, 以 MS SQL 的 Stored Procedure 寫成。其中, 如果任一個參數(或全部)傳入 NULL 值, 即代表不限制那個欄位的條件。
別讓網頁圖片降低網站效能 - 談談網頁圖片處理新趨勢
如果你的網頁只是設計來自己玩玩, 或者它很明確的只有少數使用者會上來, 那麼你大可以不必用到我在這裡要介紹的方法。但是, 如果你的網站是供作大量使用者(例如超過上千個 current session), 那麼你一定不得不開始想方設法的提升網站的效能
2010/1/20
多功能 FileUpload 使用者控制項
我寫了一個可以處理檔案上傳的多功能使用者控制項,除了可以處理一般的檔案上傳之外,還可以動態修改上傳路徑、讓使用者決定是否覆蓋舊檔,並動態決定是否限制使用者只能接受圖型檔案
善用擴充方法
雖然我個人認為自從 .Net 2.0 以後引進的 Lambda 運算式在某種程度上破壞了 C# 的嚴謹度, 但是對於一個程式設計師而言, 我們也不能否定它對於程式撰寫所能提供的方便性。擴充方法 (Extension Method) 也是一樣; 如果你還不知道擴充方法是什麼, 那麼當你看完本文之後, 我相信你會感謝微軟提供了這麼好用的功能。
擴充方法是 .Net 3.5 才提供的, 所以在之前的版本中並沒有這東西。它的實作很簡單 (相對於其功能), 絕對超乎所有人的想像。請先看看以下的程式碼:
public static class cls
{
public static string keep10chars(this string str)
{
return (str.Length > 10) ? str.Substring(0, 10) : str;
}
}
撰寫擴充方法就是這麼簡單而已, 以下幾個重點要記住:
- 它必須包在一個宣告為 public static 的類別裡面 (如上例中的 public static class cls); 類別名稱可以隨便取, 並不影響執行及引用。
- 方法必須以 public static [type] 宣告 (如上例中的 public static string)。
- 方法不能沒有參數, 而且其型別必須以 this [type] 宣告 (如上例中的 this string)。
如果你寫在網頁裡, 那麼完整的網頁程式如下:
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Response.Write("12345678901234567890".keep10chars());
}
}public static class cls
{
public static string keep10chars(this string str)
{
return (str.Length > 10) ? str.Substring(0, 10) : str;
}
}
如上例, 當你把這個擴充方法 keep10chars 寫好之後, 你的字串物件就自動多了一個 .keep10chars() 方法可以使用了。在這個例子中, "12345678901234567890".keep10chars() 的輸出是 "1234567890"。意思就是說, 你已經為 string 型別建立了一個擴充方法 keep10chars(), 可以隨時引用。
擴充方法的用法就是這麼簡單。你不需要另外建立一個繼承自 string 的新型別, 而是直接套用在現有的系統型別上面; 也沒有什麼繁瑣的宣告或語法。你說, 這是不是非常方便呢?
擴充方法對於 ASP.NET 程式而言, 還有另一個極為友善的功能, 那就是它可以毫無問題的用在繫結控制項的 tag 裡面。舉個例子, 假設你的 GridView 中有一個 TemplateField, 你可以把擴充方法直接運用在從 Eval 函式撈出來的資料上:
<asp:TemplateField HeaderText="OperatorName" SortExpression="OperatorName">
<EditItemTemplate>
<asp:TextBox ID="TextBox1" runat="server" Text='<%# Bind("OperatorName") %>'></asp:TextBox>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label1" runat="server" Text='<%# Eval("OperatorName").ToString().keep10chars() %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
運用擴充方法, 我們就可以對所繫結的資料進行後處理, 而且是用非常簡單而輕鬆的做法。
不過, 請注意, 我發現我在 .Net 4.5 上建立的 ASP.NET 專案上建立的擴充方法, 總是發生未定義的錯誤。這個網頁有套用 Master Page, 我不知道這是否是引發錯誤的原因; 不過解法很簡單。
我的擴充方法是這樣定的:
namespace Johnny { public static class abc { public static string Gender(this string isMale) { return (1)? "男" : "女"; } } public partial class Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { ...
換句話說, 這個擴充方法 Gender() 是定義在和網頁相同的命名空間之下的。但是, 我還是必須以手動方式在網頁上加上明確的 IMPORT 指示詞, 這個擴充方法才能被找到:
<%@ Page Title="" Language="C#" MasterPageFile="~/..." %> <%@ Import Namespace="Johnny" %> <asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server"> </asp:Content> <asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server"> <h2>... ... <asp:TemplateField ...> <ItemTemplate> <asp:Label ID="lbIsMale" runat="server" Text='<%# Eval("IsMale").Gender() %>' /> </ItemTemplate> </asp:TemplateField>
我不知道為什麼這種問題會發生, 我只知道加上 IMPORT 指示詞就能解決這個問題。
2010/1/19
[UserControl] 在使用者控制項中建立事件處理函式
當你在設計 ASP.NET 網站時, 使用者控制項 (User Controls) 是非常好用的,假設我們把一個 DropDownList 製作成使用者控制項,那麼我們就等於把整個相關機制 (外觀、資料存取及邏輯判斷等) 都封裝在一個可以重複使用的物件裡面,可以在網站的任何地方使用。所以我十分推薦大家適當地使用使用者控制項
2010/1/18
ASP.NET 的電子郵件傳輸工具程式
以下是我已經寫好的 Email 傳送程式, 稍為改一下就可以使用了:
using System; using System.Net.Mail; using System.Text; using System.Web; using System.Web.Configuration; public class Mails { private string _sendFrom = "YourId@YourDomain"; private string _sendBy = "系統管理員"; /// /// 指定 SMTP Server; 預設值為 msa.hinet.net /// public string Host = "msa.hinet.net"; /// /// 指定 SMTP Server Port; 預設值為 25 /// public int HostPort = 25; /// /// 指定寄件人的 Email 位址 /// public string SendFrom { get { return _sendFrom; } set { _sendFrom = value; } } /// /// 指定寄件人的名字 /// public string SendBy { get { return _sendBy; } set { _sendBy = value; } } /// /// 建立寄件人 MailAddress 物件 /// private MailAddress From { get { MailAddress _from = new MailAddress(SendFrom, SendBy); return _from; } } /// /// 指定收件人地址; 可以同時指定多個 Email, 以逗號區隔 /// public string To; /// 指定 CC 地址; 由於可以同時指定多個 Email, 以逗號區隔 /// public string Cc; /// 指定 BCC 地址; 由於可以同時指定多個 Email, 以逗號區隔 /// public string Bcc; /// /// 指定該郵件的標題 /// public string Subject = null; /// /// 指定郵件標題的編碼方式, 預設值為 UTF8 /// public Encoding SubjectEncoding = Encoding.UTF8; private string _body = null; /// /// 指定該郵件的內容 /// public string Body { get { return _body; } set { _body = (IsBodyHtml) ? string.Format("<div style='{0}'>{1}</div>", Style, value) : value; } } /// <summary> /// 指定郵件內容的編碼方式, 預設值為 UTF8 /// </summary> public Encoding BodyEncoding = Encoding.UTF8; /// <summary> /// 指定該郵件是否為 HTML 格式或純文字格式; 預設值為 true /// </summary> public bool IsBodyHtml = true; /// <summary> /// 指定整體信件的樣式; 只有當信件格式為 HTML 時有效 /// </summary> public string Style = "font-family:'微軟正黑體, Verdana,Arial'; width: 90%; font-size: 0.9em;"; private MailMessage mail; public Mails() // Constructor { mail = new MailMessage(); } /// <summary> /// Send mail directly with static data /// </summary> public static void QuickSend(string _Subject, string _Message, string _To, string _Cc = null, string _Bcc = null) { Mails m = new Mails(); m.To = _To; m.Cc = _Cc; m.Bcc = _Bcc; m.Subject = _Subject; m.Body = _Message; m.Send(); } /// <summary> /// 寄出信件 /// </summary> public void Send() { if (To == null) // 防呆 return; mail.From = this.From; string a = mail.From.Address; string u = mail.From.User; foreach (string to in this.To.Split(',')) if (!string.IsNullOrEmpty(to)) mail.To.Add(to); foreach (string cc in this.Cc.Split(',')) if (!string.IsNullOrEmpty(cc)) mail.To.Add(cc); foreach (string bcc in this.Bcc.Split(',')) if (!string.IsNullOrEmpty(bcc)) mail.To.Add(bcc); mail.IsBodyHtml = this.IsBodyHtml; mail.Subject = this.Subject; mail.SubjectEncoding = this.SubjectEncoding; mail.Body = this.Body; mail.BodyEncoding = this.BodyEncoding; SmtpClient client = new SmtpClient(this.Host, this.HostPort); client.Send(mail); } }
要使用這個類別, 最簡單的方法如下:
string subject = txtMailSubject.Text.Trim(); string message = txtMailMessage.Text.Trim(); string to = txtMailTo.Text.Trim(); string cc = txtMailCc.Text.Trim(); string bcc = txtMailBcc.Text.Trim(); Mails.QuickSend(subject, message, to, cc, bcc);
在程式中, Mails.QuickSend 是一個靜態方法, 把相關字串填上去即可進行郵件傳送; 凡是未指定的欄位或屬性, 都採用預設值。如果你不想使用預設值, 則可以改用 Send() 方法, 但是可以自行修改各欄位或屬性的值。
[ADO.NET] 動態 SQL 指令與 SqlDataSource 的 QueryStringParameter 配合查詢
我在「[SQL] 使用 Stored Procedure 動態組成 SQL 查詢指令 」一文中介紹了如何在 Stored Procedure 中動態的組合出 SQL 指令並進行查詢。然而, 如果我們要配合 SqlDataSource 中以進行查詢, 該怎麼做呢?
2009/10/11
在 Visual Studio 中建立及進行單元測試
單元測試 (Unit Test) 是一個很基本的工作, 如果你使用 Visual Studio 做為開發工具的話, 建立單元測試專案是一件再簡單不過的事了。不過, 除非你已經對單元測試很熟悉, 否則有些最基本的概念, 你最好能事先熟悉一下
小心使用「全部取代」
最近在「西瓜的滋味」部落格中作者發表了一篇「小心使用「全部取代」」的文章, 讀來十分有趣。不過這也讓我回想到, 這實在也是一個程式設計者常犯的問題。「全部取代」確實是個很方便的功能, 但也或許是太方便了, 以致於該改的改了, 但不該改的也順便改了。
測試者如果看到類似或同類型的錯誤, 應該舉一反十, 要想到它可能就是「全部取代」所引發的問題。這種問題多半不會只錯一個地方, 會同時出現在不同的地方。而對於開發者而言, 如果可以使用 Refactor 工具來取代, 就盡量別使用「全部取代」這個功能。
在不疑處有疑 - 談開發工具的方便性及陷阱
今天在開發一個小工具時發現一個原本很難發現的小陷阱。是這樣的,我原本是只是寫一個電子郵件的群組管理,群組的命名規則是規畫成以日期命名,例如 20061227, 20061230 等等。但在測試時一時技癢,把以前當 SQA 時的測試精神拿出來用用,就順手把原本不會使用的中文字給打進去測試,結果真的找到問題。打進去的中文字,通通變成了問號。
這種問題應該是很容易發現的,但因為程式設計師熟知程式設計的邏輯,所以不見得會以違反設計精神的樣式去做測試,這裡舉的就是一個典型的例子。那麼,為什麼程式設計師在這裡「應該」不會主動發現這個錯誤呢?因為我在設計時,根本是按一般文字處理的規則去撰寫程式的,所以既然以數字去測試沒問題,以英文也沒問題,那麼以中文或任何符號去測試,也不應該有問題。
該死的是,當你使用像 VS2005 這種方便的開發工具在開發程式時,由於他在使用上變得太方便了,讓我程式寫得太過順手,一些細節就疏忽了。在上述狀況中,我把某一 SQL 字串的參數型態選為 SqlDbType.VarChar,但其實應該是 SqlDbType.NVarChar!在 VS2005 中,這些字都不必用手打進去,而是從 Intelisense 表單中選。一個不小心,就選到了另一個看起來很類似的項目了。
若使用 SqlDbType.VarChar 作為資料型態,和使用 SqlDbType.NVarChar 幾乎沒有什麼不一樣,但是偏偏前者不支援多位元組語系的文字,後者才支援。所以,如果我沒有剛好順手用中文字去測試看看,大概永遠不知道有這個問題存在!
所以,測試者還真的是不要知道程式的設計邏輯比較好。知道得愈多,盲點也愈大,也就愈測不出問題。
無障礙網路空間的測試
行政院研考會所製定的「無障礙網路空間」規範,向來是做政府生意的專案軟體業者最頭痛的問題之一。因為要做政府生意,通常都必須通過這個規範,但是這個規範很繁雜,幾乎至少有一半以上都是軟體設計者不會留意到的細節。所以要寫出符合規定的網站,還真不是件容易的事情。
所以軟體測試者可以不了解這個規範是什麼嗎?
如果你真的不清楚這個規範,甚至連聽都沒聽過,那麼你一定要先去拜訪一下研考會的網站,把那十四條規範以及九十條相關的檢測要點研究一下。有時間的話,把你正在測試的網站丟到「網頁檢測」那一頁,給它鑒定一下。
順便說明一下,你可能不知道上面網頁檢測最下方的「檢測等級」是什麼。研考會的無障礙網路空間可以分為三個等級:
- A
- AA
- AAA
許多政府軟體專案都會要求達到所謂的「3A」 等級,也就是 AAA 等級。至於 A+ 就代表優於 3A 等級的意思。
思慮不周的捉迷藏遊戲
在 ASP.NET 設計環境中,幾乎所有控制項都可以設定 Visibility,也就是顯示/隱藏的開關。此外,還有 Div, Panel, PlaceHolder 等等 Container,也可以設定 Visibility,一次就讓一群控制項隱藏或是消失。
在一般情況下,我認為大部份程式設計師都會使用這點便利性來設計程式。例如,我在網頁上放了一段文字和控制項,是專門用來顯示A商品的,並且以 Div 包起來(命名為 divA)。另外又寫了一段文字是控制項,是專門用來顯示B商品的,同樣用 Div 包起來(命名為 divB)。
接著,我在網頁最上面放了一個 DropDownList,當使用者從裡面挑選了A商品,我就把設定 divA.Visible = True,並設定 divB.Visible = False。而當使用者挑選了B商品,我就把設定 divA.Visible = False,並設定 divB.Visible = True。
[UserControl] 使用者控制項的測試注意事項
使用者控制項是 ASP.NET 中非常重要的一種控制項。基本上,它是一個類別,也可以看作元件,但是它卻提供視覺化 (Visualized) 的設計介面,可以方便你以堆積木的方法一塊一塊的把網頁架構起來。
我十分鼓勵大家多多使用使用者控制項來設計你的網頁。一方面,它很方便而且容易設計,因為它的作法和普通網頁幾乎沒有什麼不一樣。另一方面,你可以藉由它所提供的程式再利用的特色,簡化你的程式設計。再者,你可以把部份程式設計邏輯封裝 (Encaptualize) 起來,當你有需要修改程式的時候,就可以分割處理,用不著全部修改。
不過使用者控制項再方便,它也有它的盲點。我將在這篇文章裡列出測試使用者控制項的注意事項。
- 測試同一網頁中有兩個以上相同的使用者控制項 - 有太多程式設計上不小心忽略的疏忽,會造成同一網頁上出現兩個以上的相同使用者控制項時會出問題,包括 Cookie/Session/Membership 的使用衝突、JavaScript 的重複註冊等等。
- 把使用者控制項放置在 Container 裡面並仔細測試 - 我這裡所講的 Container,當然不是指像 ContainPlaceHoder、Div、Panel 等等,而是特別指具有 Template 功能的 Container 控制項,尤其是像 GridView/DetailsView/FormView 等等。除非你確定你的使用者控制項絕對不可能被放置在這種 Container 控制項裡面,不然你不應該忘記測試這一項。在 ASP.NET 的 Templated Container 控制項中,當你在 Runtime 時期開啟了對應的 Template 時(例如進入編輯狀況),Container 的行為和平常時候是不一樣的,所以問題時常出現在這個地方。
- 承上,特別測試 Container 控制項的連動功能 - 我們知道,在 ASP.NET 2.0 中,你可以很容易的讓 Container 控制項進行連動 (Cascade),尤其是用來作為 Master/Detail 展示的時候。例如,你可以讓一個 GridView 來做為 Master 項目的瀏覽容器,再設計一個 DetailsView 來作為 Details 項目的展示/編輯容器,中間只需要透過在 DataSource 裡面加上 ControlID,就可以讓資料來源隨著另一個控制項的選取項目而連動了。但是問題也正出現在這裡;在許多情況下,沒有連動不會出問題,有連動就會出問題,而且問題總會顯示在顯示 Master 的項目上,但問題本身卻是在顯示 Details 的控制項上。若你在 Container 的 Template 中加上了 User Control, 情況可能更形複雜, 需要額外的測試。