December 26, 2010

Data Transfer Object使用心得及時機

Data Transfer Object (DTO)一詞最早出現於何處筆者並不確定,但大部份對DTO的研究常會參考Martin Folwer的著作Patterns of Enterprise Application Architecture其中Data Transfer Object章節。事實上,許多開發人員可能早就已經使用它而不自知,以下是筆者在使用DTO上的心得

December 25, 2010

泛型Singleton Pattern

關於Singleton Pattern,可參考GoF寫的Design Patterns一書,中文版書名為物件導向設計模式,在此不詳述。當我們要讓某個類別具有singleton能力時,我們可能會透過以下程式碼來實現
public sealed class GoFSingleton
    {
        private static readonly GoFSingleton _instance = new GoFSingleton();

        private GoFSingleton()
        {
        }

        public static GoFSingleton Instance
        {
            get
            {
                return _instance;
            }
        }
    }
接下來即可在程式碼中以GoFSingleton gof = GoFSingleton.Instance;呼叫singleton物件

如果系統小,用到singleton可能只有一兩支class,那麼以上的singleton程式碼就只會有一兩支code duplication出現在系統內。
但當系統需要大量使用到singleton pattern時註1,duplication的情況就會比較多。以下提供一範例可使用泛型來實作出Singleton Pattern
public sealed class Singleton<T> where T : class, new()
    {
        private static readonly T _instance = new T();

        private Singleton()
        {
        }

        public static T Instance
        {
            get
            {
                return _instance;
            }
        }
    }
使用方式:SingletonObject obj = Singleton<SingletonObject>.Instance;

範例程式下載:SingletonPatternSample.zip

備註

1.關於Singleton Pattern濫用,Joshua Kerievsky在其著作Refactoring to Patterns(中文書名為重構-向範式前進)中Inline Singleton章節有詳細的說明

2.有關Singleton Pattern於threading的議題,可參考網友內向人推薦的好文Implementing the Singleton Pattern in C#

December 16, 2010

好用的error logging framework - ELMAH

看到RiCo兄一文介紹elmah這支error logging framework,覺得還蠻不錯的,自己也安裝來玩看看。elmah能將所有exception都log起來,除了可以把整個例外訊息log成XML,或存在資料庫中,也可以將訊息以email寄給管理者。

關於如何安裝elmah,可參考Introductory article by Simone BusoliLogging Error Details with ELMAHELMAH - Error Logging Modules And Handlers三篇文章,以下為試用後的一些心得

December 1, 2010

NHibernate - 外部設定檔範例

前篇文章說明了NHibernate的設定檔設定方式,將設定細節寫在Web.config或App.config中,然而將NHibernate設定置於Web.config/App.config中可能會造成設定檔過於肥大,因為可能還有其它framework的設定檔,本篇文章將說明以外部設定檔的方式設定NHibernate,將細部設定自Web.config/App.config抽離。

 1.於專案中建立hibernate.cfg.xml檔案[註1]並將原本置放在*.config中hibernate-configuration區段移到hibernate.cfg.xml,並將其檔案屬性內的複製到輸出目錄設為永遠複製,因為NHibernate預設會讀取bin下的hibernate.cfg.xml,若不將hibernate.cfg.xml一同輸出至bin裡,NHibernate將讀取不到設定檔。


hibernate.cfg.xml 檔案內容如下
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
    <session-factory>
        <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
        <property name="connection.driver_class">NHibernate.Driver.NpgsqlDriver</property>
        <property name="dialect">NHibernate.Dialect.PostgreSQL82Dialect</property>
        <property name="connection.connection_string">Server=10.0.0.10;Port=5432;Database=dbname;User ID=username;Password=password;</property>
        <property name="proxyfactory.factory_class">NHibernate.ByteCode.LinFu.ProxyFactoryFactory, NHibernate.ByteCode.LinFu</property>
        <mapping assembly="NHibernateTest.Data"/>
    </session-factory>
</hibernate-configuration>
2.在呼叫任何NHibernate功能前於程式碼中加入以下片段
Configuration cfg = new Configuration();
cfg.Configure();
經由以上設定即可完成NHibernate設定。如果我們不想每次都要將hibernate.cfg.xml  輸出至bin呢?NHibernate也提供指定設定檔位置的方式讀取設定檔。假設hibernate.cfg.xml 置放在web ap根目錄下,我們可將程式碼修改為以下片段,即可讀取設定檔資訊[註2]
Configuration cfg = new Configuration();
cfg.Configure(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "hibernate.cfg.xml"));

備註
1.名稱需固定為hibernate.cfg.xml,否則run time會出現找不到設定檔的錯誤訊息
2.此時設定檔名稱可自訂

November 30, 2010

NHibernate - 設定檔範例 for SQL Server/Oracle/PostgreSQL

以下提供SQL Server  2008Oracle 10gPostgreSQL 8.4的設定檔範例,需設定在Web.config/App.config
1.設定configSections區段
    <configSections>
        <section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />
    </configSections> 
2.設定hibernate-configuration區段
(a) SQL Server 2008
     <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
        <session-factory>
            <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
            <property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
            <property name="dialect">NHibernate.Dialect.MsSql2008Dialect</property>
            <property name="connection.connection_string">Data Source=10.0.0.10;Initial Catalog=dbname;Persist Security Info=True;User ID=username;Password=password;</property>

            <property name="proxyfactory.factory_class">NHibernate.ByteCode.LinFu.ProxyFactoryFactory, NHibernate.ByteCode.LinFu</property>
            <mapping assembly="NHibernateTest.Data"/>
        </session-factory>
    </hibernate-configuration>
(b) Oracle 10g
    <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
        <session-factory>
            <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
            <property name="connection.driver_class">NHibernate.Driver.OracleClientDriver</property>
            <property name="dialect">NHibernate.Dialect.Oracle10gDialect</property>
            <property name="connection.connection_string">Data Source=XXX;Persist Security Info=True;User ID=username;Password=password;</property>

            <property name="proxyfactory.factory_class">NHibernate.ByteCode.LinFu.ProxyFactoryFactory, NHibernate.ByteCode.LinFu</property>
            <mapping assembly="NHibernateTest.Data"/>
        </session-factory>
    </hibernate-configuration>
(c) PostgreSQL 8.4
    <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
        <session-factory>
            <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
            <property name="connection.driver_class">NHibernate.Driver.NpgsqlDriver</property>
            <property name="dialect">NHibernate.Dialect.PostgreSQL82Dialect</property>
            <property name="connection.connection_string">Server=10.0.0.10;Port=5432;Database=dbname;User ID=username;Password=password;</property>

            <property name="proxyfactory.factory_class">NHibernate.ByteCode.LinFu.ProxyFactoryFactory, NHibernate.ByteCode.LinFu</property>
            <mapping assembly="NHibernateTest.Data"/>
        </session-factory>
    </hibernate-configuration>
也可將連線字串的設定移到connectionStrings區段中,如以下範例
     <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
        <session-factory>
            <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
            <property name="connection.driver_class">NHibernate.Driver.NpgsqlDriver</property>
            <property name="dialect">NHibernate.Dialect.PostgreSQL82Dialect</property>
            <property name="connection.connection_string_name">PostgreSQL</property>
            <property name="proxyfactory.factory_class">NHibernate.ByteCode.LinFu.ProxyFactoryFactory, NHibernate.ByteCode.LinFu</property>
            <mapping assembly="NHibernateTest.Data"/>
        </session-factory>
    </hibernate-configuration>
    <connectionStrings>
        <add name="PostgreSQL" connectionString="Server=10.0.0.10;Port=5432;Database=dbname;User ID=username;Password=password;"/>
    </connectionStrings> 
備註
1.筆者測試用的PostgreSQL版本為8.4,但在NHibernate 2.1.2支援的dialect只到8.2,可設定的dialect可參考http://nhforge.org/doc/nh/en/index.html#configuration-optional-dialects
2.<mapping assembly="NHibernateTest.Data"/>為對應到資料庫所使用的domain entities編譯檔

November 25, 2010

NHibernate - 啟用設定檔與物件關聯對應檔的Intellisense

NHibernate的設定檔及物件關聯對應檔(object/relational mapping file,副檔名為*.hbm.xml)皆為XML格式的檔案。

對應檔的schema其實頗複雜的,如果要用手刻可能會瘋掉,好在NHibernate提供了兩支XML Schema - nhibernate-configuration.xsdnhibernate-mapping.xsd可供我們加入Visual Studio啟動其intellisense的功能
nhibernate-configuration.xsd是NHibernate設定檔的schema
nhibernate-mapping.xsd是NHibernate物件關聯對應檔的schema
這兩個檔案可於NHibernate的sourceforge連結http://sourceforge.net/projects/nhibernate/files/NHibernate/2.1.2GA/NHibernate-2.1.2.GA-bin.zip/download下載

下載後可於壓縮檔內的Required_Bins資料夾下找到這兩支xsd檔

接下來將這兩支xsd檔放至路徑C:\Program Files (x86)\Microsoft Visual Studio 9.0\Xml\Schemas下,重新啟動VS後,於編輯設定檔與物件關聯即可使用intellisense

以上說明使用的系統環境為Windows 7 64bit, Visual Studio 2008, NHibernate 2.1.2

DataGridView的ScrollBars屬性設定後無作用?

系統環境:Windows 7 x64, Visual Studio 2008
問題描述
DataGridViewScrollBars屬性設定為BothDock設為Fill,下邊連接著一個StatusStripDock設定為Bottom,執行程式後當DataGridView寬度大於Form寬度時,horizontal scrollbar卻無法顯示出來


解決方法
將StatusStrip在版面上的配置移到最下層才不會蓋住DataGridView,執行後可正確顯示出horizontal scrollbar



November 18, 2010

NHibernate - 相關資源

最近因為想嘗試以NHibernate來設計專案中的Data Access Layer,所以陸續記錄一下收集到的資源

NHibernate官方網址:http://nhforge.org/

NHibernate下載位址:http://sourceforge.net/projects/nhibernate/

NHibernate線上手冊:http://nhforge.org/doc/nh/en/index.html

NHibernate線上手冊(PDF、CHM及HTML三種格式)下載位址:http://sourceforge.net/projects/nhibernate/files/NHibernate/2.1.2GA/NHibernate-2.1.2.GA-reference.zip/download 

NHibernate How to:http://nhforge.org/wikis/howtonh/default.aspx

NHibernate Contrib下載位址:http://sourceforge.net/projects/nhcontrib/

NHibernate Burrow:http://nhforge.org/wikis/burrow/default.aspx

Fluent NHibernate:http://fluentnhibernate.org/

November 16, 2010

去除UTF-8 BOM的方法

今天在處理一份格式為UTF-8的XML資料時,用XDocument去剖析這份XML,明明格式"看起來"就沒問題,但是一執行XDocument.Parse就出現"在根層次的資料無效。 第 1 行,位置 1。"的錯誤訊息,可是第一行第一個字元不就是個小於符號(<)嗎?。研究了一下發現了這錯誤訊息很有可能是所剖析的XML其二進位資料被塞入了BOM (what is BOM?)
檢查了一下這份XML發現的確被塞入了三個bytes的UTF-8 BOM

接下來就是想辦法把它拿掉了,以下提供一範例參考自http://efreedom.com/Question/1-2070661/Change-XML-String-XDocumentParse-Reads並做修改
                byte[] bytes = Encoding.UTF8.GetBytes(xml);
                if (bytes[0] == 0xef && bytes[1] == 0xbb && bytes[2] == 0xbf)
                {
                    string byteOrderMarkUtf8 = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble());
                    xml = xml.Remove(0, byteOrderMarkUtf8.Length);
                }
 與參考網址不同的地方是if判斷式的寫法,因為實測之後發現若XML不含BOM字元的話XML的第一個字元會被截掉而造成錯誤,故改以檢查byte方式進行判斷
其中的Encoding.UTF8.GetPreamble() 用意是在取得UTF-8 BOM的二進位字元(EF BB BF)

XML結構描述定義(XSD)轉物件類別的方法(2) - 使用Xsd2Code

接續前一篇文章XML結構描述定義(XSD)轉物件類別的方法(1) - 使用xsd.exe,本篇要介紹另一個XSD轉物件類別的工具 - Xsd2Code

Xsd2Code是一個Codeplex上的Visual Studio Add-in,安裝後可直接在VS中右鍵選取*.xsd檔進行轉換成類別的動作,如下

點選Run Xsd2Code generation會出現如下畫面

 在Options標籤頁畫面中所做的設定將會影響到輸出的類別內容,以下針對小弟有測試過的幾個設定做簡單的說明
CustomUsings:新增除了預設以外會使用到的命名空間
Language:產出類別的語言,C#VB.NET
NameSpace:產出類別的命名空間
CollectionObjectType:針對XSD內設定為unbouned的元素輸出時要使用的集合型別(Arrary、List、IList, etc.)
EnableInitializeFields:初始化欄位值,例如私有變數。初始化會發生在建構式或屬性(做lazy loading/deferred loading時用)裡。
PropertyParams->AutomaticProperties:自動實作屬性,這是C# 3.0開始有支援的一個feature,如public string Name {get;set;},而不必透過私有變數來存取屬性。使用這個設定前,TargetFramework一定要設為NET30以上版本

以下為使用Xsd2Code產生的類別範例
 // ------------------------------------------------------------------------------
//  <auto-generated>
//    Generated by Xsd2Code. Version 3.4.0.38967
//    <NameSpace>Xsd2Entities</NameSpace><Collection>List</Collection><codeType>CSharp</codeType><EnableDataBinding>False</EnableDataBinding><EnableLazyLoading>False</EnableLazyLoading><TrackingChangesEnable>False</TrackingChangesEnable><GenTrackingClasses>False</GenTrackingClasses><HidePrivateFieldInIDE>False</HidePrivateFieldInIDE><EnableSummaryComment>False</EnableSummaryComment><VirtualProp>False</VirtualProp><IncludeSerializeMethod>False</IncludeSerializeMethod><UseBaseClass>False</UseBaseClass><GenBaseClass>False</GenBaseClass><GenerateCloneMethod>False</GenerateCloneMethod><GenerateDataContracts>False</GenerateDataContracts><CodeBaseTag>Net35</CodeBaseTag><SerializeMethodName>Serialize</SerializeMethodName><DeserializeMethodName>Deserialize</DeserializeMethodName><SaveToFileMethodName>SaveToFile</SaveToFileMethodName><LoadFromFileMethodName>LoadFromFile</LoadFromFileMethodName><GenerateXMLAttributes>False</GenerateXMLAttributes><EnableEncoding>False</EnableEncoding><AutomaticProperties>False</AutomaticProperties><GenerateShouldSerialize>False</GenerateShouldSerialize><DisableDebug>False</DisableDebug><PropNameSpecified>Default</PropNameSpecified><Encoder>UTF8</Encoder><CustomUsings></CustomUsings><ExcludeIncludedTypes>False</ExcludeIncludedTypes><EnableInitializeFields>True</EnableInitializeFields>
//  </auto-generated>
// ------------------------------------------------------------------------------
namespace Xsd2Entities {
    using System;
    using System.Diagnostics;
    using System.Xml.Serialization;
    using System.Collections;
    using System.Xml.Schema;
    using System.ComponentModel;
    using System.Collections.Generic;


    public partial class BooksForm {
    
        private List<BookForm> bookField;
    
        public BooksForm() {
            this.bookField = new List<BookForm>();
        }
    
        public List<BookForm> book {
            get {
                return this.bookField;
            }
            set {
                this.bookField = value;
            }
        }
    }

    public partial class BookForm {
    
        private string authorField;
    
        private string titleField;
    
        private string genreField;
    
        private float priceField;
    
        private System.DateTime pub_dateField;
    
        private string reviewField;
    
        private string idField;
    
        public string author {
            get {
                return this.authorField;
            }
            set {
                this.authorField = value;
            }
        }
    
        public string title {
            get {
                return this.titleField;
            }
            set {
                this.titleField = value;
            }
        }
    
        public string genre {
            get {
                return this.genreField;
            }
            set {
                this.genreField = value;
            }
        }
    
        public float price {
            get {
                return this.priceField;
            }
            set {
                this.priceField = value;
            }
        }
    
        public System.DateTime pub_date {
            get {
                return this.pub_dateField;
            }
            set {
                this.pub_dateField = value;
            }
        }
    
        public string review {
            get {
                return this.reviewField;
            }
            set {
                this.reviewField = value;
            }
        }
    
        public string id {
            get {
                return this.idField;
            }
            set {
                this.idField = value;
            }
        }
    }
}

November 15, 2010

XML結構描述定義(XSD)轉物件類別的方法(1) - 使用xsd.exe

在實作某些以XML為基礎的標準時,我們常會以XmlDocumentXmlWriter或.NET Framework3.5起支援的 XDocument類別來幫助我們產生符合的XML,通常一個well-defined的標準幾乎也都會附上其所參考的結構描述定義檔(*.xsd)

相較於使用XmlDocumentXmlWriterXDocument建立XML的方式,在實作較為大型的標準時,如果我們能以物件導向的方式將資料設定給某個物件後再將此物件序列化成XML,這樣的作法是否較為"美觀"也比較好維護/除錯。不過前提是,我們需要有一份(或以上)的XML結構描述定義檔。以下將分兩篇文章介紹兩種方法,可以將XSD轉換為物件類別

系統環境:Windows 7 x64, Visual Studio 2008
使用.NET Framework內建的xsd.exe工具(開啟Visual Studio 2008 命令提示字元)
xsd.exe工具詳細的用法可參考XML Schema Definition Tool (Xsd.exe)
利用此工具,我們可以一行command line就可以將XSD檔(參考http://msdn.microsoft.com/en-us/library/ms764613(VS.85).aspx)轉換出一支類別檔。
結構描述定義檔內容如下


  

  
    
      
      
  

  
    
      
      
      
      
      
      
    
    
  
執行以下指令

 /c指的是輸出類別檔 ,/o指的是輸出類別檔存放的位置。

產出的class如下
//------------------------------------------------------------------------------
// 
//     這段程式碼是由工具產生的。
//     執行階段版本:2.0.50727.4952
//
//     對這個檔案所做的變更可能會造成錯誤的行為,而且如果重新產生程式碼,
//     變更將會遺失。
// 
//------------------------------------------------------------------------------

using System.Xml.Serialization;

// 
// 此原始程式碼由 xsd 版本=2.0.50727.3038 自動產生。
// 


[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.3038")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:books")]
[System.Xml.Serialization.XmlRootAttribute("books", Namespace="urn:books", IsNullable=false)]
public partial class BooksForm {
    
    private BookForm[] bookField;
    
    [System.Xml.Serialization.XmlElementAttribute("book", Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public BookForm[] book {
        get {
            return this.bookField;
        }
        set {
            this.bookField = value;
        }
    }
}

[System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "2.0.50727.3038")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="urn:books")]
public partial class BookForm {
    
    private string authorField;
    
    private string titleField;
    
    private string genreField;
    
    private float priceField;
    
    private System.DateTime pub_dateField;
    
    private string reviewField;
    
    private string idField;
    
    [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public string author {
        get {
            return this.authorField;
        }
        set {
            this.authorField = value;
        }
    }
    
    [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public string title {
        get {
            return this.titleField;
        }
        set {
            this.titleField = value;
        }
    }
    
    [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public string genre {
        get {
            return this.genreField;
        }
        set {
            this.genreField = value;
        }
    }
    
    [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public float price {
        get {
            return this.priceField;
        }
        set {
            this.priceField = value;
        }
    }
    
    [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, DataType="date")]
    public System.DateTime pub_date {
        get {
            return this.pub_dateField;
        }
        set {
            this.pub_dateField = value;
        }
    }
    
    [System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
    public string review {
        get {
            return this.reviewField;
        }
        set {
            this.reviewField = value;
        }
    }
    
    [System.Xml.Serialization.XmlAttributeAttribute()]
    public string id {
        get {
            return this.idField;
        }
        set {
            this.idField = value;
        }
    }
}
 接下來我們即可以物件的方式來操作並透過XmlSerializer將物件序列化為XML,如下
            BooksForm bf = new BooksForm();

            BookForm[] bfArray = new BookForm[]
            {
                new BookForm(){ author = "Brad Abrams, Krzysztof Cwalina", title= "Framework Design Guidelines", price = 44.45F, pub_date = new DateTime(2008,11,1)},
                new BookForm(){ author = "Judith Bishop", title = "C# 3.0 Design Patterns", price = 26.39F, pub_date = new DateTime(2008,1,11)},
                new BookForm(){ author = "Dino Esposito, Andrea Saltarello", title = "Microsoft .NET Architecting Applications for the Enterprise (PRO-Developer)", price = 28.72F, pub_date = new DateTime(2008,12,23)}
            };

            bf.book = bfArray;
            XmlSerializer serializer = new XmlSerializer(bf.GetType());
            using (MemoryStream stream = new MemoryStream())
            {
                serializer.Serialize(stream, bf);
                Console.Write(Encoding.UTF8.GetString(stream.ToArray()));
            }
            
            Console.ReadKey(true);
最終產出的XML如下

備註
  1. 若要轉換的xsd還有參考到別的xsd檔,請記得一併加入指令中做處理
  2. 多個xsd一起做轉換時,若有互相參考或繼承,會有轉換順序的問題,請將基底的xsd放在最前依序排列做轉換,如xsd D:\base.xsd D:\derivedxsd1.xsd :D\derivedxsd2.xsd /c

November 12, 2010

使用XDocument驗証XML結構(範例)

using System.Xml.Linq;
using System.Xml.Schema;

XDocument xDoc = XDocument.Parse("要驗証的XML字串");
XmlSchemaSet schemas = new XmlSchemaSet();
schemas.Add(string.Empty, "XML結構描述檔位址(*.xsd)");
xDoc.Validate(schemas, null);

如果驗証失敗可以try-catch取得XmlSchemaValidationException內的錯誤訊息或是在XDoc.Validate的第二個參數中使用delegate交給特定function來處理

November 5, 2010

如何驗証GUID格式是否有效

GUID很常用, 尤其是拿來當primary key,我們很常用GUID來取得DB中相對應的資料。在大部份的情況下我們不會讓使用者輸入GUID來取得對應資料。

預設在.NET中以System.Guid.NewGuid()可以產生一長度為36的GUID字串,就像在SQL Server中將預設值設為newid()產出的GUID字串一樣,如a7c1e7ea-9e6b-4067-9278-0a1f78bbbb44
剛好手邊的案子就需要使用者輸入GUID來跟系統做認證授權並取得資料,使用者輸入的GUID長度或格式若有誤,頂多找不到資料,但因字串長,比對格式或長度有沒有錯眼力不夠好可能也得花些時間所以就會希望有個防呆機制,驗証GUID格式是否符合預期,並適時回應給使用者。

一個quick-and-dirty的解法,就是用try-catch方式,將使用者輸入的GUID字串帶入System.Guid的建構式中,若Guid類別無法被初始化,就會丟出exception並被catch到,如下code snippet
public bool IsGuidValid(string inputGuid)
        {
            try
            {
                Guid guid = new Guid(inputGuid);
                return true;
            }
            catch
            {
                return false;
            }
        }
這是最快的方式,卻也濫用了try-catch

比較好的做法是使用Regular Expression去檢查GUID的格式,如下
public static bool IsGuidValid(string inputGuid)
        {
            string pattern = @"^[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}$";
            return Regex.IsMatch(inputGuid, pattern);
        }
而在.NET 4.0中,提供了新的Guid.TryParseGuid.TryParseExact方法讓我們可以判斷GUID是否有效,可以簡化上述的兩個做法

November 4, 2010

Web測試錄製器無法使用?

系統環境:Windows 7 x64, Visual Studio 2008
問題描述:在VS新增Web測試後並嘗試瀏覽本機網站後,發現Web測試錄製器被disabled,並顯示"Web測試錄製器必須從Visual Studio內部啟動"

但有趣的是我的的確確是從VS啟動的

明查暗訪之後發現開啟VS時需以系統管理員身份開啟VS並載入方案/專案即可正常使用


不過每次都要以系統管理員身份開啟VS再載方案有點麻煩,直接以系統管理員身份開啟方案/專案檔不就得了,但實際會發現右鍵選單看不到這個選項

可以將啟動VS方案/專案檔的VSLauncher.exe(目錄為C:\Program Files (x86)\Common Files\microsoft shared\MSEnv,以Windows 7 x64為例)設為以系統管理員身份執行

接著執行方案/專案即可以系統管理員身份開啟

October 29, 2010

使用AutoMapper簡化Data Transfer Object與Business Entity的對應程式碼

在一個layered system中,我們常會使用Data Transfer Object (DTO)在layer與layer間傳遞資料,如Presentation Layer (PL)與Business Logic Layer (BLL)。至於什麼是DTO,可參考筆者另一篇文章Data Transfer Object使用心得及時機,在此也列出Martin Fowler在其書"Patterns of Enterprise Application Architecture"中對DTO的定義,有與趣的朋友可以去google一下或翻一下這本書(在Chapter 15)
An object that carries data between processes in order to reduce the number of method calls.
由於資料傳遞透過DTO,我們將會遇到兩種需要做資料對應的情況
  1. DTO由PL傳遞至Service Layer (SL),於SL將DTO對應至Business Entity後,再把Business Entity送至Data Access Layer (DAL)處理
  2. 資料由DAL取出給BLL後,於SL將Business Entity對應至DTO後回傳至PL
基於以上兩種情況,我們常會看到類似下列的程式碼 (第二種資料對應類型)
UserInfo user = GetUserInfoFromRepository("pete");
UserInfoDto dto = new UserInfoDto();
dto.UserId = user.UserId;
dto.UserAccount = user.UserAccount;
dto.UserPassword = user.UserPassword;
dto.Role = user.Role;
dto.UserName = user.UserName;
return dto;
偏偏這種資料對應不會只存在於一個頁面中,相同的程式碼要copy/paste不僅累人,萬一要是眼花少copy到一行系統不能work搞不好還得debug半天

如果說有個工具或元件能幫我們做這檔事不是樂得輕鬆嗎? 在此推薦一個好用元件AutoMapper
有了它,在最理想的情況下只要一行程式碼(好吧,外加一行建立Mapper物件的程式碼) 就可以輕輕鬆鬆達到DTO與Business Entity的對應
以下為一簡單的範例程式碼
Mapper.CreateMap<UserInfo, UserInfoDto>()
UserInfoDto dto = Mapper.Map<UserInfo, UserInfoDto>(GetUserInfoFromRepository("pete"));
據官方說法,重覆的對應,其Mapper.CreateMap只要宣告一次就可以了,並建議在把它放在Global.asax裡在應用程式啟動時就建立Mapping。當然它還有很多更進階的用法以及資料對應時會產生的一些問題,筆者在此就不多做說明了。

October 26, 2010

使用Command Line方式在IIS6佈署網站

年底到,一些要結案的系統也要開始寫安裝手冊。手邊有一個Web AP,手動佈署不複雜,但要寫成詳細點的step-by-step安裝步驟卻也是得cut好幾張圖。由於這個系統沒有複雜到一定要用IIS的UI管理工具來設定,所以就試試把它寫成一支batch檔,以command line的方式來執行系統安裝,省去了一些安裝步驟的說明,順便學學adsutil.vbs這支好用的command-line script。

以下引述MSDN對於adsutil.vbs的解釋
Adsutil.vbs is an IIS administration utility that uses Microsoft Visual Basic Scripting Edition (VBScript) with Active Directory Service Interfaces (ADSI) to manipulate the IIS configuration. This script should be run using CScript, which is installed with Windows Script Host.

簡單地說,它是一個IIS的管理工具,透過CScript指令可以command line的方式對IIS做設定。adsutil.vbs的位置在C:\Inetpub\AdminScripts下,以下為我用來佈署用的的command line

@echo off 
echo 開始安裝... 
echo 1.設定首頁 
cscript /nologo C:\Inetpub\AdminScripts\adsutil.vbs SET W3SVC/1/ROOT/DefaultDoc Default.aspx

echo 2.設定預設網站路徑 
cscript /nologo C:\Inetpub\AdminScripts\adsutil.vbs SET W3SVC/1/ROOT/Path C:\Inetpub\wwwroot\MyWebApp 

echo 3.設定.NET Framework使用版本 
%windir%\microsoft.net\framework\v2.0.50727\aspnet_regiis -s W3SVC/1/ROOT 

echo 安裝完成 
pause

上述的command line主要有三個步驟,設定預設網站首頁、路徑及將.NET Framework使用版本設為v2.0.50727

W3SVC/1/ROOT為預設的網站,由於我只需要安裝一個Web AP在系統上,所以我直接使用預設的網站。如果需要額外新增網站或虛擬目錄來設定,可以參考iisweb.vbsiisvdir.vbs兩支command-line script

如果想知道adsutil.vbs提供哪些參數可以設定,可以用
cscript /nologo C:\Inetpub\AdminScripts\adsutil.vbs ENUM_ALL W3SVC/1/ROOT來列出可設定的參數

因為參數不少,也可以下列command line輸出成文字檔
cscript /nologo C:\Inetpub\AdminScripts\adsutil.vbs ENUM_ALL W3SVC/1/ROOT > D:\param.txt

以下為執行結果

October 9, 2010

呼叫Web Service出現HTTP狀態417:Expectation Failed的解決方法

最近在呼叫部署在客戶端的Web Service時會出現以下錯誤訊息

研究了一下HTTP 1.1的規格書,這個錯誤出現於當server(或proxy server)無法辨識client送出的Expect標頭
解決方法可以在Web.config的<configuration>區段中加入以下設定
<system.net>
  <settings>
    <servicePointManager expect100Continue="false" />
  </settings>
</system.net>
或於呼叫Web Service前加入以下程式碼
System.Net.ServicePointManager.Expect100Continue = false;

參考
http://tools.ietf.org/html/rfc2616#section-10.4.18
http://tools.ietf.org/html/rfc2616#section-14.20
http://www.cnblogs.com/yukaizhao/archive/2009/07/15/httpwebrequest_return_417_expectation_failed.html
http://social.msdn.microsoft.com/Forums/en-US/devdocs/thread/60cd6e6a-4157-4811-8ed3-1e46f9022ea8

October 8, 2010

使用WebClient讀取資料出現403錯誤訊息

今天在測試讀取客戶端某支xml時,透過瀏覽器存取都沒問題,可以正常顯示資料。但透過WebClient讀取卻回傳了HTTP 403的錯誤訊息,猜想可能是被server端的防火牆給擋掉了,瀏覽器能讀取應該是有送出防火牆認可的HTTP header,所以應該只要能模擬瀏覽器送出的header應該就可以解決這個問題了。

去古哥爬了一下後發現有網友也有相同問題,解決的方式可以在WebClient讀取資料前新增模擬瀏覽器的header如下
WebClient client = new WebClient();
client.Headers.Add(HttpRequestHeader.UserAgent, "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;");

August 9, 2010

PostgreSQL資源

最近因為要開發的產品決定採用PostgreSQL,記錄一下會參考到的一些網址。
  1. PostgreSQL官網: http://www.postgresql.org/
  2. 下載位址: http://www.enterprisedb.com/products/pgdownload.do#windows
  3. 線上說明文件: http://www.postgresql.org/docs/
  4. Client端管理介面pgAdmin: http://www.pgadmin.org/download/windows.php
  5. .Net data provider for Postgresql: http://pgfoundry.org/projects/npgsql/
  6. Enterprise Library Contrib Data Provider for Postgresql: http://entlibcontrib.codeplex.com/wikipage?title=PostgreSQLDataProvider41&referringTitle=Home

July 30, 2010

產生英數字夾雜密碼

不知有無更快方式,在此先紀錄一下,主要是利用ASCII Code加轉型產生密碼。
/// <summary>
/// 產生英數字夾雜密碼
/// </summary>
/// <param name="length">密碼長度</param>
/// <returns></returns>
public static string GeneratePassword(int length)
{
    Collection<int> asciiCode = new Collection<int>();

    // 數字的ASCII code
    for (int i = 48; i <= 57; i++)
    {
        asciiCode.Add(i);
    }

    // 英文A~Z的ASCII code
    for (int i = 65; i <= 90; i++)
    {
        asciiCode.Add(i);
    }

    // 英文a~z的ASCII code
    for (int i = 97; i <= 122; i++)
    {
        asciiCode.Add(i);
    }  

    StringBuilder password = new StringBuilder();
    Random random = new Random();
    int count = asciiCode.Count;

    for (int i = 0; i < length; i++)
    {
        password.Append(Convert.ToChar(asciiCode[random.Next(count - 1)]).ToString());
    }

    return password.ToString();
}

May 29, 2010

在ThickBox開啟的iframe內設定物件focus的方法

最近專案剛好有個需求,要在彈跳視窗開啟後使用者可以藉由上下鍵或空白鍵去控制開啟後視窗的scrollbar藉以瀏覽頁面。彈跳視窗是使用ThickBox來實作,但ThickBox開啟後focus是直接在ThickBox上,而不是 ThickBox 裡的iframe。

研究了一下ThickBox的source code,發現ThickBox會呼叫函式tb_showIframe()來載入iframe。
所以可以在tb_showIframe()裡加入
 $("#TB_iframeContent").focus();
 $("#TB_iframeContent").contents().find("body").focus();

這樣ThickBox 開啟時focus馬上就可以落在iframe內的物件了。

經測試,$("#TB_iframeContent").focus();必須加入tb_showIframe(),在Firefox(v 3.6.3)Chrome(v 5.0.375.55)才可正常執行;若不加$("#TB_iframeContent").focus();僅可在IE(v 8)正常執行。

備註
以上提及的瀏覽器版本皆為小弟所使用的瀏覽器版本

March 1, 2010

如何使IIS顯示自訂組態區段

開發及測試環境: Windows 7 Enterprise & Windows 2008 Server R2 Enterprise, IIS 7

習慣上我們常會直接修改Web.config檔來變更網站的設定資料。如果要透過IIS的管理介面來修改的話,可以使用它提供的「設定編輯器」來針對網站設定做變更,變更後的設定將寫回Web.config。

但點進了「設定編輯器」,卻發現自訂組態區段沒出現在上頭,也找不到可以新增自訂組態區段的地方。

研究了一下發現,IIS將其組態區段設定放置C:\Windows\System32\inetsrv\config\schema路徑下,我們可以看到在此資料夾下有幾個XML檔案。
而欲顯示自訂組態,需先有一份描述自訂組態的綱要檔存放在C:\Windows\System32\inetsrv\config\schema,如下(也可參考其它綱要檔的撰寫方式)。
<?xml version="1.0" encoding="utf-8"?>
<configSchema>
  <sectionSchema name="MailSetting">
    <attribute name="SmtpHost" type="string"></attribute>
    <attribute name="SmtpPort" type="int" defaultValue="25" />
    <attribute name="UseSsl" type="bool" defaultValue="false"></attribute>
    <attribute name="UseHtml" type="bool" defaultValue="true"></attribute>
    <attribute name="Sender" type="string"></attribute>
    <attribute name="SenderDisplayName" type="string"></attribute>
    <attribute name="UserName" type="string"></attribute>
    <attribute name="Password" type="string"></attribute>
  </sectionSchema>
</configSchema>

寫好自訂組態的網要檔後,再到C:\Windows\System32\inetsrv\config下修改applicationHost.config檔案,在<configuration>下<configSections>中加入如下XML片段
<section name="MailSetting" overrideModeDefault="Allow" allowDefinition="Everywhere" />
如此,即可在IIS中看到自訂組態區段了。


備註
1.經以上設定後,勿在Web.config再設定相同的自訂組態區段,否則將會出現重複定義區段的錯誤訊息


參考資料
http://mvolo.com/blogs/serverside/archive/2007/08/04/IISSCHEMA.EXE-_2D00_-A-tool-to-register-IIS7-configuration-sections.aspx
http://www.leastprivilege.com/InstallingAnIIS7Extension.aspx
http://learn.iis.net/page.aspx/242/extending-iis7-schema-and-accessing-the-custom-sections-using-mwa/

January 14, 2010

HttpUtility.UrlEncode, HttpUtility.UrlPathEncode, HttpServerUtility.UrlEncode及HttpServerUtility.UrlPathEncode

在網路上看了官方資料和一些其它網友的比較說明,在這做個紀錄

以下是MSDN針對這四個function做的說明
HttpUtility.UrlEncode HttpUtility.UrlPathEncode
Server.UrlEncode Server.UrlPathEncode

大略說明一下它們之間的差異
  1. HttpUtility.UrlEncode預設是使用UTF8編碼,而Server.UrlEncode是使用系統預設編碼;Server.UrlEncode會使用系統預設編碼做為參數代入並呼叫HttpUtility.UrlEncode做編碼動作,這點可由.NET Reflector來驗証
  2. Server.UrlPathEncode會呼叫HttpUtility.UrlEncode做編碼動作,而HttpUtility.UrlEncode預設使用UTF8編碼,所以Server.UrlPathEncode會以UTF8編碼,這點可由.NET Reflector來驗証
  3. HttpUtility.UrlEncode將空格編碼為+,而HttpUtility.UrlPathEncode將空格編碼為%20
  4. HttpUtility.UrlEncode會針對完整的URL做編碼,包含Query String;Server.UrlPathEncode則不對Query String做編碼
  5. HttpUtility.UrlEncode及HttpUtility.UrlPathEncode為靜態函式,而Server.UrlEncode及Server.UrlPathEncode需在ASP.NET中以Page.Server(或直接以Server)取得HttpServerUtility物件後才可以使用[註1]
  6. HttpUtility.UrlEncode會針對冒號(:)斜線(/)做編碼,如http://會編碼成http%3a%2f%2f。所以HttpUtility.UrlEncode不宜對URL的路徑部份做編碼,以免執行如Response.Rediret功能時,無法導向正確網址,應改由HttpUtility.UrlPathEncode對URL路徑部份做編碼[註2]

測試範例

UrlEncodeTest.aspx
 <asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="Redirect to UrlEncodeTest.aspx using HttpUtility.UrlEncode" />
 <p>
 <asp:Button ID="Button2" runat="server" OnClick="Button2_Click" Text="Redirect to UrlEncodeTest.aspx using HttpUtility.UrlPathEncode" />
 </p>
 <p>
 <asp:Button ID="Button3" runat="server" OnClick="Button3_Click" Text="Redirect to UrlEncodeTest.aspx?param1=參數值1&amp;param2=value2 using HttpUtility.UrlEncode" />
 </p>
 <p>
 <asp:Button ID="Button4" runat="server" OnClick="Button4_Click" Text="Redirect to UrlEncodeTest.aspx?param1=參數值1&amp;param2=value2 using HttpUtility.UrlPathEncode" />
 </p>
 <p>
 <asp:Button ID="Button5" runat="server" OnClick="Button5_Click" Text="Redirect to UrlEncodeTest.aspx?param=我 是 空 白 using HttpUtility.UrlEncode" />
 </p>
 <p>
 <asp:Button ID="Button6" runat="server" OnClick="Button6_Click" Text="Redirect to UrlEncodeTest.aspx?param=我 是 空 白 using HttpUtility.UrlPathEncode" />
 </p>

UrlEncodeTest.aspx.cs
private string url = "http://localhost:10710/UrlEncodeTest.aspx";

protected void Page_Load(object sender, EventArgs e)
{
    if(!string.IsNullOrEmpty(Request.QueryString["param"]))
    {
        Response.Write(Request.QueryString["param"] + "<br>");
    }

    if(!string.IsNullOrEmpty(Request.QueryString["param1"]))
    {
        Response.Write(Request.QueryString["param1"] + "<br>");
    }

    if(!string.IsNullOrEmpty(Request.QueryString["param2"]))
    {
        Response.Write(Request.QueryString["param2"] + "<br>");
    }
}

protected void Button1_Click(object sender, EventArgs e)
{
    Response.Redirect(HttpUtility.UrlEncode(url));
}

protected void Button2_Click(object sender, EventArgs e)
{
    Response.Redirect(HttpUtility.UrlPathEncode(url));
}

protected void Button3_Click(object sender, EventArgs e)
{
    url += "?param1=參數值1&param2=value2";
    Response.Redirect(HttpUtility.UrlEncode(url));
}

protected void Button4_Click(object sender, EventArgs e)
{
    url += "?param1=參數值1&param2=value2";
    Response.Redirect(HttpUtility.UrlPathEncode(url));
}

protected void Button5_Click(object sender, EventArgs e)
{
    url += "?param=我 是 空 白";
    Response.Redirect(HttpUtility.UrlEncode(url));
}

protected void Button6_Click(object sender, EventArgs e)
{
    url += "?param=我 是 空 白";
    Response.Redirect(HttpUtility.UrlPathEncode(url));
}

備註
  1. 當然,您也可以在類別庫專案中以HttpContext物件來取得HttpServerUtility,如System.Web.HttpContext.Current.Server
  2. 小弟在研究MSDN時發現Server.UrlPathEncode的範例似乎有誤,測試發現Server.UrlPathEncode並不會對冒號(:)或斜線(/)做編碼,而範例顯示是會編碼的