3.手工調用模型綁定
很多情況下我們都是通過形參的方式接收來自http流中的數據,這看似是完美的,但是缺少了很多過程中的控制,所以我們就需要使用手工的方式進行綁定。下面我們通過一個例子來說明,首先打開Views/Home/Index.cshtml頁面,並輸入如下代碼:
1 @{ 2 ViewBag.Title = "Index"; 3 } 4 5 @if (TempData.ContainsKey("msg")) 6 { 7 <h1> 8 @TempData["msg"].ToString() 9 </h1> 10 } 11 12 @using (Html.BeginForm()) 13 { 14 <input type="text" name="id" /> 15 <input type="submit" value="submit" /> 16 }
接着打開HomeController並寫入如下代碼(關於ActionName可以點擊這進行參考):
1 namespace MvcStudy.Controllers 2 { 3 public class HomeController : Controller 4 { 5 private class TestA 6 { 7 public String id { get; set; } 8 } 9 10 public ActionResult Index() 11 { 12 return View(); 13 } 14 15 [HttpPost] 16 [ActionName("Index")] 17 public ActionResult IndexPost() 18 { 19 TestA ta = new TestA(); 20 UpdateModel(ta); 21 TempData["msg"] = ta.id; 22 return View(); 23 } 24 } 25 }
這里我們通過UpdateModel進行手動綁定,最終的結果和采用形參的方式相同,讀者可以進行測試可以發現輸入的值都顯示了,但是讀者一定會奇怪,因為TestA中的id不僅僅存在於表單,同時還存在與RouteData中以及查詢字符串中,我們可以用?id=123來測試這個頁面可以發現並不會修改最終結果,而通過手動調用模型綁定的優點之一就是我們可以控制數據來源,比如我們修改HomeController代碼如下所示:
1 [HttpPost] 2 [ActionName("Index")] 3 public ActionResult IndexPost() 4 { 5 TestA ta = new TestA(); 6 UpdateModel(ta,new FormValueProvider(ControllerContext)); 7 TempData["msg"] = ta.id; 8 return View(); 9 }
這里我們可以發現我們給UpdateModel傳遞了第二個參數,FormValueProvider這表示數據源只能來自於表單中,同樣我們還可以修改成RouteDataValueProvider或者QueryStringValueProvider,具體的效果讀者你自行替換之后,輸入http://localhost:1201/Home/Index/123?id=asdsad測試,可以看看最后顯示的內容是不是來自於我們指定的來源。再使用形參的方式中我們有時會遇到http流中不存在我們需要的值,並且這個形參的類型不能為null,或者我們不希望它為null,這個時候就會出現異常或者賦值為null,而通過UpdateModel則可以通過try…catch…的形式捕獲InvalidOperationException異常從而手動處理,如果讀者不希望通過這種方式也可以像下面這種形式來處理:
1 if (TryUpdateModel(ta, new QueryStringValueProvider(ControllerContext))) 2 { 3 //正確時的操作 4 } 5 else 6 { 7 //異常時的操作 8 }
這樣我們只要通過if判斷即可。
4.自定義值提供器
通過上面的我們發現ASP.NET MVC自帶的模型綁定器已經提供了很多我們所需要的功能,但是有時候我們想某些值不是來自於http流中而是我們自己來填充的,那么這節知識會讓你感興趣,因為下面我們將要自定義一個值提供器來完成我們的需求。首先介紹需要用的接口和類,首先是IValueProvider接口:
1 namespace System.Web.Mvc 2 { 3 // 摘要: 4 // 定義 ASP.NET MVC 中的值提供程序所需的方法。 5 public interface IValueProvider 6 { 7 // 摘要: 8 // 確定集合是否包含指定的前綴。 9 // 10 // 參數: 11 // prefix: 12 // 要搜索的前綴。 13 // 14 // 返回結果: 15 // 如果集合包含指定的前綴,則為 true;否則為 false。 16 bool ContainsPrefix(string prefix); 17 // 18 // 摘要: 19 // 使用指定鍵來檢索值對象。 20 // 21 // 參數: 22 // key: 23 // 要檢索的值對象的鍵。 24 // 25 // 返回結果: 26 // 指定的鍵的值對象。 27 ValueProviderResult GetValue(string key); 28 } 29 }
其中ContainsPrefix用來判斷這個值的前綴是不是我們能夠處理的(因為ASP.NET MVC其實自帶了很多這種值提供器,最后會通過循環調用的方式調用這些提供器,直到有一個返回值。)然后就是GetValue方法就是返回對應的值了,當然光有這個還不夠,還需要一個工廠去創建它,以提供調用,這個類就是ValueProviderFactory,而我們僅僅只需要實現GetValueProvider方法即可,其實就是new一個值提供器並返回,當然你也可以通過這個方法的ControllerContext從而有選擇性的返回一個值提供器,下面我們簡單的舉一個例子來處理ns。
首先我們創建一個Provider文件夾,然后新建一個NSValueProvider類並在文件中寫入如下代碼:
1 namespace MvcStudy.Provider 2 { 3 public class NSValueProvider : IValueProvider 4 { 5 6 public bool ContainsPrefix(string prefix) 7 { 8 return String.Compare("ns", prefix, true) == 0; 9 } 10 11 public ValueProviderResult GetValue(string key) 12 { 13 if (ContainsPrefix(key)) 14 { 15 return new ValueProviderResult("from ns", null, CultureInfo.InvariantCulture); 16 } 17 return null; 18 } 19 } 20 21 public class NSValueProviderFactory : ValueProviderFactory 22 { 23 public override IValueProvider GetValueProvider(ControllerContext controllerContext) 24 { 25 return new NSValueProvider(); 26 } 27 } 28 }
最后打開Global.asax將它注冊:
1 ValueProviderFactories.Factories.Insert(0, new NSValueProviderFactory());
最后我們需要修改HomeController以便能夠看到結果:
1 public ActionResult Index(string ns) 2 { 3 TempData["msg"] = ns; 4 return View(); 5 }
重新編譯之后刷新頁面我們就可以看到如下的結果:

這樣我們就完成了一個值提供器了,看到這個讀者一定會想模型提供器怎么去自定義呢,其實模型綁定器就是依靠這些值提供器完成的,大家想想就可以明白了。
5.模型綁定器
模型綁定器跟值提供器很相似,只是需要做的工作比較多,因為你要負責將一個類的屬性填充,所以比較麻煩。下面是需要實現的接口IModelBinder:
1 namespace System.Web.Mvc 2 { 3 // 摘要: 4 // 定義模型聯編程序所需的方法。 5 public interface IModelBinder 6 { 7 // 摘要: 8 // 使用指定的控制器上下文和綁定上下文將模型綁定到一個值。 9 // 10 // 參數: 11 // controllerContext: 12 // 控制器上下文。 13 // 14 // bindingContext: 15 // 綁定上下文。 16 // 17 // 返回結果: 18 // 綁定值。 19 object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext); 20 } 21 }
這里重點是bindingContext參數,里面包含了很多綁定所需要的值和方法,下面我們舉一個簡單的例子,就是自定義一個模型綁定器負責綁定如下類:
1 public class TestA 2 { 3 public String id { get; set; } 4 }
同時還要規定只能通過name為ns.id獲取值,並不會根據參數的名稱去獲取,下面就是我們實現接口的代碼:
1 namespace MvcStudy.Provider 2 { 3 public class NSModelBinder : IModelBinder 4 { 5 6 public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) 7 { 8 TestA a = (TestA)bindingContext.Model ?? new TestA(); 9 bool isHave = bindingContext.ValueProvider.ContainsPrefix("ns.id"); 10 if (isHave) 11 { 12 a.id = bindingContext.ValueProvider.GetValue("ns.id").AttemptedValue; 13 } 14 else 15 { 16 a.id = "asd"; 17 } 18 return a; 19 } 20 } 21 }
最后一步當然還是需要注冊(Global.asax):
1 ModelBinders.Binders.Add(typeof(MvcStudy.Controllers.HomeController.TestA), new NSModelBinder());
然后我們重新編譯,並在頁面中輸入值並提交,可以發現TestA的id並不是我們輸入的值而是模型綁定器中的值,但是如果我們將Views/Home/Index.cshtml中的文本框的name改成ns.id之后,我們再輸入值,最后顯示的就是我們輸入的值了,由此可以看出來模型綁定器是依賴於值提供器的。
至此關於模型綁定的部分就結束了,下面我們將開始學習模型驗證。
