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之后,我们再输入值,最后显示的就是我们输入的值了,由此可以看出来模型绑定器是依赖于值提供器的。
至此关于模型绑定的部分就结束了,下面我们将开始学习模型验证。