顯示具有 Enterprise Library 標籤的文章。 顯示所有文章
顯示具有 Enterprise Library 標籤的文章。 顯示所有文章

2011年8月16日 星期二

使用 ExceptionCallHandler 進行例外處理

緣由

當我們開發應用程式時,難免(一定)會有bug。無論是使用者輸入不正確的資料,環境/平台因素等,只要有 bug 就是開發人員的錯。

開發人員難道只有被罵的份嗎?當然不能坐以待斃。我們開發人員必須留下錯誤的痕跡,以快速地找到錯誤的原因。

因此,我們都必須寫像下面的 Code

try
{
  //執行商業邏輯
}
catch (Exception)
{
  //記錄例外資訊到檔案或資料庫
  Console.WriteLine("對不起!發生錯誤,請洽...");
}

要如何進行例外的記錄(log)呢?

解決1:使用 EHAB

解決的方法,我都是使用 Enterprise Library 中的Exception Handling Application Block。

程式碼的部份很簡單。只需要呼叫ExceptionPolicy.HandleException 如下:

public virtual void Go2()
{
  try
  {
    throw new NotImplementedException();
  }
  catch (Exception ex)
  {
    ExceptionPolicy.HandleException(ex, "Policy");
  }
}

但config 設定的話,新手初看就很複雜了。這裡我並不想多做介紹,有興趣的話可以看這一篇

這裡有個問題,如果每一段程式都需要 try…cache 再來處理例外的話,程式碼就變的很難看。

解決2:集中一個地方處理例外

要在同一個地方處理例外其實很常見。例如在 ASP.NET 上可以在 Global.aspx.cs 中的 Application_Error 中處理

protected void Application_Error(object sender, EventArgs e)
{
    //處理 Application 所有拋出的 exception
    Exception exception = Server.GetLastError();
    ExceptionPolicy.HandleException(exception, "LogAllInFile");
}

雖然集中在一個地方處理很方便,但能做的事情也很有限,只能使用相同的 Policy 來處理例外。並且一旦到如 Global.asax 這裡來處理時,就失去了各自處理的彈性。

有沒有一個方便的解決方法,既可以各自處理,又不用每個地方寫 try..cache 這樣的 Code 呢?

解決3:使用 ExceptionCallHandler

接下來就是今天的主題了。PIAB 可以幫我們解決這樣的問題。第一步,是要讓物件可以被注入。因此物件的類別宣告不可以為sealed,Method 也必須是 virtual。

 public class Go
  {
    public virtual void Go1()
    {
      throw new NotImplementedException();
    }

第二步是在 Method 上進行 Attribute 的註記。如下:

[ExceptionCallHandler("Policy")]
public virtual void Go1()
{
  throw new NotImplementedException();
}

其中,”Policy”是Exception Policy 的名稱。

當然,不是只有這樣就可以搞定的。我們必須在實例化物件前開始進行複雜的 injection(注射)以達到我們的目的。第三步:

static void Main(string[] args)
{
  var container = new UnityContainer();
  container.AddNewExtension<EnterpriseLibraryCoreExtension>();
  container.AddNewExtension<Interception>();
  container.RegisterType<ICallHandler, ExceptionCallHandler>("ExceptionCallHandler",
      new InjectionConstructor(
          new ResolvedParameter(typeof(ExceptionPolicyImpl), "Policy")));

  container.RegisterType<Go>(
    new InterceptionBehavior<PolicyInjectionBehavior>(),
    new Interceptor<VirtualMethodInterceptor>());

  var g = container.Resolve<Go>();
  g.Go1();
}

討論

使用 ExceptionCallHandler 來解決似乎方便多了,因為可以在不同的 Method 上註記當錯誤發生時,使用不同的 exception policy 處理之。

但壞處是物件的 Method 必須都是 virtual,讓 IoC 有機會注入不同的行為。

2011年6月1日 星期三

Enterprise Library on NuGet

NuGet 也可以直接安裝 Enterprise Library 了。

問題

舉例來說,之前在使用 Enterprise Library Exception Handling Application Block 時,需要在專案上加入下列參考

Microsoft.Practices.EnterpriseLibrary.Common
Microsoft.Practices.EnterpriseLibrary.ExceptionHandling

沒有經驗的人,很難一次引用正確。

解決方式:NuGet

Add Library Package Reference

image

安裝指定的 package

image

完成!還比想像中多了一些 dll,這些 unity 在用的 dll,什麼時候會用到我也不知道。

image

可惜的是,引用 package 並不會直接產生 .config 的設定。原因很簡單,它不知道我們要設定些什麼。

Enterprise Library 的設定太靈活了,要設定的話,還是乖乖地使用 Enterprise Library Configuration Editor 來設定吧。

2010年5月12日 星期三

如何使用 In 的查詢語法,而且防止 Sql Injection

有同事問到,如果需要以 in 的 sql 語法查詢時,要如何防止Sql Injection?

問題需要一步步的解決。首先寫個範例,是查詢在 AdventureWorks 的資料表是否有指定的資料表名稱。程式如下。

範例一

   1: class Program
   2:   {
   3:     static void Main(string[] args)
   4:     {
   5:       Database db = DatabaseFactory.CreateDatabase();
   6:  
   7:       string query = "select name, object_id from sys.tables where name in ('Store','ProductPhoto','ProductProductPhoto','StoreContact')";
   8:       var tables = db.ExecuteSqlStringAccessor<Table>(query);
   9:       foreach (var table in tables)
  10:         Console.WriteLine("{0}:{1}", table.Object_Id, table.Name);
  11:     }
  12:   }
  13:  
  14:   class Table
  15:   {
  16:     public int Object_Id { get; set; }
  17:     public string Name { get; set; }
  18:   }

範例一中使用了 Enterprise Library 5 中的 DAAB 來讀取資料,並將符合條件的資料表的 name , object_id 列表出來。

現在呢?需要將查詢的資料表名稱參數化,作成一個 Method,方便 UI 呼叫。

範例二

   1: static void Main(string[] args)
   2:     {
   3:  
   4:       string tableNames = "'Store','ProductPhoto','ProductProductPhoto','StoreContact'";
   5:       var tables = GetTables(tableNames);
   6:       foreach (var table in tables)
   7:         Console.WriteLine("{0}:{1}", table.Object_Id, table.Name);
   8:     }
   9:  
  10:     private static IEnumerable<Table> GetTables(string strTableNames)
  11:     {
  12:       Database db = DatabaseFactory.CreateDatabase();
  13:       
  14:       string query = "select name, object_id from sys.tables where name in (" + strTableNames + ")";
  15:       var tables = db.ExecuteSqlStringAccessor<Table>(query);
  16:       return tables;
  17:     }
  18:  
  19:     class Table
  20:     {
  21:       public int Object_Id { get; set; }
  22:       public string Name { get; set; }
  23:     }

要查詢的資料表名稱以符合 sql 語法查詢的方式GetTables 方法。GetTables 的實作中,直接以組字串的方式組成了 sql script。這樣寫,執行起來並沒有錯誤。卻是標準的Sql Injection 入侵範例。尤其是呼叫 GetTables 的 Layer 是網頁時,就更需要擔心了。

我們改成寫下面的範例三。

範例三

   1: class Program
   2:   {
   3:     static void Main(string[] args)
   4:     {
   5:       string[] tableNames = { "Store","ProductPhoto","ProductProductPhoto","StoreContact"};
   6:       var tables = GetTables(tableNames);
   7:       foreach (var table in tables)
   8:         Console.WriteLine("{0}:{1}", table.Object_Id, table.Name);
   9:     }
  10:  
  11:     private static IEnumerable<Table> GetTables(string[] tableNames)
  12:     {
  13:       Database db = DatabaseFactory.CreateDatabase();
  14:       
  15:       List<string> tableName = (from t in tableNames
  16:                                select string.Format("'{0}'", t)).ToList();
  17:       string strTableNames = string.Join(",", tableName.ToArray());
  18:       string query = "select name, object_id from sys.tables where name in (" + strTableNames + ")";
  19:       var tables = db.ExecuteSqlStringAccessor<Table>(query);
  20:       return tables;
  21:     }
  22:   }
  23:  
  24:   class Table
  25:   {
  26:     public int Object_Id { get; set; }
  27:     public string Name { get; set; }
  28:   }

要查詢的資料表名稱以陣列的方式傳入 GetTables 方法。GetTables 的實作中,仍然以組字串的方式組成了 'Store,ProductPhoto,ProductProductPhoto,StoreContact' 的字串,再以 in 的sql 查詢語法來下 sql script.

這樣寫,執行起來並沒有錯誤。Sql Injection 的可能性也不高,因為 tableNames 的字串陣列需要被重組。

但,仍是是組字串的方式來組合  sql script,容易被當成 sql injection 的攻擊漏洞,CAT.NET 還是會檢查出來的。

範例四

   1: class Program
   2:   {
   3:     static void Main(string[] args)
   4:     {
   5:       string[] tableNames = { "Store", "ProductPhoto", "ProductProductPhoto", "StoreContact" };
   6:       var tables = GetTables(tableNames);
   7:       foreach (var table in tables)
   8:         Console.WriteLine("{0}:{1}", table.Object_Id, table.Name);
   9:     }
  10:  
  11:     private static IEnumerable<Table> GetTables(string[] tableNames)
  12:     {
  13:       Database db = DatabaseFactory.CreateDatabase();
  14:  
  15:       string strTableNames = string.Join(",", tableNames.ToArray());
  16:       string query = "select name, object_id from sys.tables where name in ( select value from dbo.fn_split(@strTableNames, ','))";
  17:  
  18:       IRowMapper<Table> rowMapper = MapBuilder<Table>.BuildAllProperties();
  19:       var accessor = new SqlStringAccessor<Table>(db, query, new MyParameterMapper(), rowMapper);
  20:       var tables = accessor.Execute(strTableNames);
  21:       return tables;
  22:     }
  23:  
  24:   }
  25:  
  26:   class Table
  27:   {
  28:     public int Object_Id { get; set; }
  29:     public string Name { get; set; }
  30:   }
  31:  
  32:   public class MyParameterMapper : IParameterMapper
  33:   {
  34:     public void AssignParameters(DbCommand command, object[] parameterValues)
  35:     {
  36:       DbParameter parameter = command.CreateParameter();
  37:       parameter.ParameterName = "@strTableNames";
  38:       parameter.Value = parameterValues[0];
  39:       command.Parameters.Add(parameter);
  40:     }
  41:   }

而 dbo.fn_split 列在下面

   1: if exists (select * from dbo.sysobjects where id = OBJECT_ID(N'[dbo].[fn_Split]') and xtype in (N'FN', N'IF', N'TF'))
   2: drop function [dbo].[fn_Split]
   3: GO
   4:  
   5: SET QUOTED_IDENTIFIER OFF 
   6: GO
   7:  
   8: SET ANSI_NULLS OFF 
   9: GO
  10:  
  11: CREATE  FUNCTION fn_Split(@text varchar(8000), @delimiter varchar(20) = ' ')
  12: RETURNS @Strings TABLE
  13: (    
  14:   position int IDENTITY PRIMARY KEY,
  15:   value varchar(8000)   
  16: )
  17: AS
  18: BEGIN
  19: DECLARE @index int 
  20: SET @index = -1 
  21:  
  22: WHILE (LEN(@text) > 0) 
  23:   BEGIN  
  24:     SET @index = CHARINDEX(@delimiter , @text)  
  25:     IF (@index = 0) AND (LEN(@text) > 0)  
  26:       BEGIN   
  27:         INSERT INTO @Strings VALUES (@text)
  28:           BREAK  
  29:       END  
  30:  
  31:     IF (@index > 1)  
  32:       BEGIN   
  33:         INSERT INTO @Strings VALUES (LEFT(@text, @index - 1))   
  34:         SET @text = RIGHT(@text, (LEN(@text) - @index))  
  35:       END  
  36:     ELSE 
  37:       SET @text = RIGHT(@text, (LEN(@text) - @index)) 
  38:     END
  39:   RETURN
  40: END
  41: GO
  42:  
  43: SET QUOTED_IDENTIFIER OFF 
  44: GO
  45:  
  46: SET ANSI_NULLS ON 
  47: GO

這樣的方法就相當長了。但卻也相當好用。尤其是 fn_split 可用來拆解字串成為新的 table 並在 sql 中使用。

SqlStringAccessor 是 Enterprise Library 5 中 DAAB 的新成員,目的是建立出新的 DTO。

2010年4月26日 星期一

一堆的新版本出來了。Enterprise Library 5.0, Office 2010, Moss 2010

一個沒注意,一堆新版本就冒出來。

Enterprise Library 5.0 , EL 5.0 on MSDN
Office 2010 及 Moss 2010 則必須到 MSDN subscriptions 下載

這下子,連上次的 Visual Studio 2010,可以讓我忙整年了。

2009年2月5日 星期四

Enterprise Library Logging Application Block: Timespan format

Enterprise Library 大概是我相當堅持要使用的 open source library 吧。
尤其是 logging 與 exception handling 這兩個。

當要記錄訊息時,最常用的就是 rolling flat file trace listener

image

然而,記錄的檔案,時間標記的格式,預設卻是UTC  時間,根台北時間差8小時。
怎麼辦呢?google 大神又發揮了神蹟,原來在 Text Formatter 有東西可以改。

image

改成 [{timestamp(local:yyyy-MM-dd HH:mm:ss)}] {message} , 就符合我的需求了

image

 

see also http://blogs.msdn.com/tomholl/archive/2006/01/22/516055.aspx

Share with Facebook