2012年11月26日 星期一

ASP.Net MVC中的Filter

image

     MVC中的Filter對於整體開發來說總是有著畫龍點睛的效果;一個網站中的Filter並不會很多,但是每個Filter總是能夠完美的發揮它關鍵性的角色,它其實是一個很特別的東西,你可以決定將它套用到整個網站,或是細到某個Action。無論是使用MVC目前現成的Filter或是自行開發Filter,套用Filter其實在簡單不過,因為只需要將其當成一般的屬性標籤,直接將它放在某個Controller或是某個Action上頭就能達到效果。

     Filter如果是想要套用到整個網站,需進行Global的註冊行為,其位置就在App_Start資料夾底下的FilterConfig.cs中,我們打開FilterConfig.cs來看看裡面的內容。

    public class FilterConfig
    {
        public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(new HandleErrorAttribute());
        }
    }

    在靜態方法RegisterGlobalFilters中,僅有一行程式碼,該程式就是將HandleErrorAttribute這個MVC提供的Filter進行Global的註冊,亦即,HandleError這個Filter會套用到整個網站。

  在MVC的專案中,我們可以看到有一個資料夾名為:Filters,該資料夾裡面所放置的就是開發人員自行撰寫的Filter。在預設範本程式碼中,我們可以看到Filters資料夾裡面有一個InitializeSimpleMember.cs檔案,打開該檔案其程式碼如下:

   [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
   public sealed class InitializeSimpleMembershipAttribute : ActionFilterAttribute
   {

       private static SimpleMembershipInitializer _initializer;

       private static object _initializerLock = new object();

       private static bool _isInitialized;

       public override void OnActionExecuting(ActionExecutingContext filterContext)
       {
           // Ensure ASP.NET Simple Membership is initialized only once per app start
           LazyInitializer.EnsureInitialized(ref _initializer, ref _isInitialized, ref _initializerLock);
       }

       private class SimpleMembershipInitializer
       {
           public SimpleMembershipInitializer()
           {
               Database.SetInitializer(null);

               try
               {
                   using (var context = new UsersContext())
                   {
                       if (!context.Database.Exists())
                       {
                           // Create the SimpleMembership database without Entity Framework migration schema

                           ((IObjectContextAdapter)context).ObjectContext.CreateDatabase();

                       }
                   }

                   WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "UserName", autoCreateTables: true);
               }

               catch (Exception ex)
               {
                   throw new InvalidOperationException("The ASP.NET Simple Membership database could not be initialized. For more information, please see http://go.microsoft.com/fwlink/?LinkId=256588", ex);

               }
           }
       }
   }

       該程式碼的目的很簡單:當資料庫檔案不存在的時候,自動建立一個資料庫檔案供會員管理使用。由此解了我們前幾篇曾經提到的疑問,為什麼按下了網頁上的註冊之後,在App_Data的實體路徑下會多出兩個和資料庫相關的檔案。其原由就是出在這個Filter。而這個Filter套用在AccountController。

    [Authorize]
    [InitializeSimpleMembership]
    public class AccountController : Controller
    {

            …省略…
    }

        所以當使用者按下了會與AccountController產生戶動的任何頁面元件時,這個Filter就會自動啟動並進行一連串的動作。其實自行撰寫MVC的Filter很簡單,但是要先來瞭解MVC的Filter其內部的運作模式,這樣一來就不會完全摸不著頭緒了。

      其實,一個Action從Request被Router導過來一直到Action處理完畢並將最後一棒交遞給View,其流程圖如下:


actionfilter2_thumb
 
      我們可以看到幾個較特別的步驟
  • OnActionExecuting(Action處理前)
  • OnActionExecuted(Action處理後)
  • OnResultExecuting(結果View處理前)
  • OnResultExecuted(結果View處理後)

    在Filter的開發上,主要有四大類,而上述的四個步驟就是其中兩大類要處理的目標。
  • Authorization(針對驗證/授權處理)
  • Action(針對Action處理時機作特製: Executing/Executed)
  • Result(針對View處理時機作特製: Executing/Executed)
  • Exception(針對例外作處理)

    這四大類各有各自要實作的介面,下圖描述那一類該實作那種介面:

actionfilter_2
 
     要開發一個自定義的Filter,僅需繼承ActionFilter再依自己要針對四大類那一類進行處理就去實作該類的介面。舉個例來說,如果我想要自行定義一個驗證相關的ActionFilter,則程式碼概略如下:

public class LogAttibute:ActionFilterAttribute,IActionFilter
{
     public override void OnActionExecuting(ActionExecutingContext filterContext)
     {  …省略… }
}

   而要套用這個自定義的Filter很簡單;若要是想要套用在HomeController上,則簡略程式碼如下:

[LogAttribute]
public class HomeController : Controller
{ …省略…}

    若想要將這個自定義的Filter套用到全網站,則需要App_Start資料夾底下的FiltersConfig.cs中加入以下程式碼:
 
    public class FilterConfig
    {
        public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(new HandleErrorAttribute());

            filters.Add(new LogAttribute());
        }
    }

總結:在MVC中Filter是一個不可或缺的工具,善用它可以事半功倍。
 
  

沒有留言:

張貼留言