有了上面的扩展,就可以这样使用了:
<h2>Product list using htmlHelper</h2> <div> <div>@Html.ListGroup().Info(Model.SportProducts,x=>x.Name)</div> <div>@Html.ListGroup().Warning(Model.BookProducts,x => x.Name)</div> </div> <div> <div>@Html.ListGroup().Danger(Model.FoodProducts,x => x.Name)</div> </div>效果:
四、RazorViewEngine
通过自定义RazorViewEngine可以实现同一份后台代码对应不同风格的View。利用这一扩展能够实现不同的Theme风格切换。再比如站点可能需要在不同的语言环境下切换到不同的风格,也可以通过自定义RazorViewEngine来实现。
下面就让我们来实现一个Theme切换的功能,首先自定义一个ViewEngine:
public class ThemeViewEngine: RazorViewEngine { public ThemeViewEngine(string theme) { ViewLocationFormats = new[] { "~/Views/Themes/" + theme + "/{1}/{0}.cshtml", "~/Views/Themes/" + theme + "/Shared/{0}.cshtml" }; PartialViewLocationFormats = new[] { "~/Views/Themes/" + theme + "/{1}/{0}.cshtml", "~/Views/Themes/" + theme + "/Shared/{0}.cshtml" }; AreaViewLocationFormats = new[] { "~Areas/{2}/Views/Themes/" + theme + "/{1}/{0}.cshtml", "~Areas/{2}/Views/Themes/" + theme + "/Shared/{0}.cshtml" }; AreaPartialViewLocationFormats = new[] { "~Areas/{2}/Views/Themes/" + theme + "/{1}/{0}.cshtml", "~Areas/{2}/Views/Themes/" + theme + "/Shared/{0}.cshtml" }; } }当我们启用这一ViewEngine时,Razor就会在/Views/Themes/文件夹下去找View文件。为了启用自定义的ViewEngine,需要将ThemeViewEngine加入到ViewEngines
public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { if (!string.IsNullOrEmpty(ConfigurationManager.AppSettings["Theme"])) { var activeTheme = ConfigurationManager.AppSettings["Theme"]; ViewEngines.Engines.Insert(0, new ThemeViewEngine(activeTheme)); }; //... } }接下来就开始编写不同风格的View了,重点在于编写的View文件夹组织方式要跟ThemeViewEngine中定义的路径要一致,以ServiceController为例,我们编写ocean和sky两种风格的View:
最后在web.config制定一种Theme:<add key="Theme" value="ocean"/>,ocean文件夹下的View将会被优先采用:
五、Validator
通过在Model属性上加Attribute的验证方式是MVC提倡的数据验证方式,一方面这种方式使用起来比较简单和通用,另一方面这种统一的方式也使得代码很整洁。使用ValidationAttribute需要引入System.ComponentModel.DataAnnotations命名空间。
但是有时候现有的ValidationAttribute可能会不能满足我们的业务需求,这就需要我们自定义自己的Attribute,例如我们自定义一个AgeValidator:
public class AgeValidator: ValidationAttribute { public AgeValidator() { ErrorMessage = "Please enter the age>18"; } public override bool IsValid(object value) { if (value == null) return false; int age; if (int.TryParse(value.ToString(), out age)) { if (age > 18) return true; return false; } return false; } }自定义的AgeValidator使用起来跟MVC内置的ValiatorAttribute没什么区别:
[Required] [AgeValidator] public int? Age { get; set; }不过我们有时候可能有这种需求:某个验证规则要针对Model中多个属性联合起来判断,所以上面的方案无法满足需求。这时候只需Model实现IValidatableObject接口即可:
public class UserViewModel:IValidatableObject { public string Name { get; set; } [Required] [AgeValidator] public int? Age { get; set; } public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { if(string.IsNullOrEmpty(Name)) yield return new ValidationResult("the name can not be empty"); if (Name.Equals("lucy")) { if(Age.Value<25) yield return new ValidationResult("lucy's age must greater than 25"); } } }六、ModelBinder
Model的绑定体现在从当前请求提取相应的数据绑定到目标Action方法的参数中。
public ActionResult InputAge(UserViewModel user) { //... return View(); }对于这样的一个Action,如果是Post请求,MVC会尝试将Form中的值赋值到user参数中,如果是get请求,MVC会尝试将QueryString的值赋值到user参数中。