在Asp.net MVC中, Model Binder是生命周期中的一個非常重要的部分。搞清楚Model Binder的流程,能夠幫助理解Model Binder的背后發生了什么。同時該系列文章會列舉MVC中Model Binder的擴展點,以及如何使用這些擴展點。
閱讀目錄:
一. MVC中的Model Binder的工作流程
二. 繼承IModelBinder, 實現CustomeBinder
三. 使用Custom Model Binder的弊端
四. 總結
一, MVC中的Model Binder的工作流程
在MVC中, 當一個請求發送到服務器,先是要經過Route匹配, 找到對應的Controller和Action, 然后才是構建Action中的參數,也就是Model Binder的過程
這個可以從MVC的源碼, ControllerActionInvoker中看出來。
在調用方法GetParameterValue獲取參數的函數中, 會根據參數的描述信息,也就是ParameterDescriptor來獲取對應的IModelBinder, 使用對應的IModelBinder來獲取參數的值。在沒有特殊為該參數指定ModelBinder的情況下, MVC使用默認的Model綁定類DefaultModelBinder.
所以我們擴展的第一處地方是,為參數綁定指定使用我們自定義的Model Binder, 自定義的Model Binder需要繼承IModelBinder.
二, 繼承IModelBinder, 實現CustomeBinder
2.1. MVC代碼中的Session依賴問題
MVC的代碼中,常常能夠看到這樣的代碼:
public ActionResult Index() { var user = Session["UserAccuont"] as UserAccount; //從Session中獲取當前登錄用戶的信息 //send email var email = user.Email; return new EmptyResult(); }
上面代碼,需要從session中取得當前登錄用戶的信息。從session中取值導致了Index方法和Session耦合,也沒辦法進行單元測試。這個時候我們可以定義一個CustomeBinder來解決這個問題,解除Index方法對於Session的依賴。
2.2 自定義UserAccountModelBinder
我們定義的UserAccountModelBinder繼承IModelBinder,實現了BindModel方法。該方法會從Session中取得UserAccount信息來構建Action方法中所需要的參數。
public class UserAccountModelBinder : IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { if (controllerContext.HttpContext.Session["UserAccuont"] != null) { return controllerContext.HttpContext.Session["UserAccuont"]; } return null; } }
2.3 注冊UserAccountModelBinder
在Global.asax.cs文件的Application_Start中, 注冊UserAccountModelBinder。
protected void Application_Start() { //注冊UserAccountModelBinder, 指定UserAccount類型的參數,將會由UserAccountModelBinder來處理。 ModelBinders.Binders.Add(typeof(UserAccount), new UserAccountModelBinder()); ………. }
2.4 修改Index()方法
現在由於UserAccountModelBinder能夠處理從Session中取UserAccount參數,所以我們的Index方法可以改造成這樣:
public ActionResult Index(UserAccount user) { //var user = Session["UserAccuont"] as UserAccount; //從Session中獲取當前登錄用戶的信息 //send email var email = user.Email; return new EmptyResult(); }
和最初代碼不同之處是,我們的從session中取得user值的代碼,不需要在Index方法中。而是由UserAccountModelBinder自動處理了。
2.5 背后的流程
現在來理一理,custome model binder的工作方法和流程。
request –> route解析 –> ControllerActionInvoker找ModelBinder處理參數 –> 根據參數類型尋找綁定的custome model binder, 也就是這里的UserAccountModelBinder –> UserAccountModelBinder 從Session中為Action的參數賦值。
三, 使用Custom Model Binder的弊端
上面的UserAccountModelBinder 的確解決了我們的Session依賴問題,但是這種以類型注冊binder的方式,會帶來潛在的風險:
由於所有使用UserAccount為參數的Action方法,都會由UserAccountModelBinder來處理,也就是從session中取值。如果這個時候,我們的UserAccount的Create和Edit頁面,提交UserAccount會發生什么? 是的,都會被UserAccountModelBinder處理,無法得到提交表單的值。
所以要慎用Custom Model Binder, 比較適合的例子是使用Custom Model Binder來綁定的參數,它的數據源只會來源於一處。上面的UserAccount在應用中,數據源就可能來自兩處,Session和表單提交,所以是不適合Custom Model binder. 一個變通的辦法是,再定義個類型SessionUserAccount.
四, 總結
使用Custom Model Binder能幫助我們解決一部分綁定問題,但是弊端是以類型綁定會導致應用的范圍太廣,帶來意料不到的問題。
下一篇中,我們將使用CustomModelBinderAttribute來解決同樣的問題,如果Custom Model Binder是全火力覆蓋,那么CustomModelBinderAttribute就是定點清除。