MVC 的八个扩展点
Asp.net MVC中常用的八个扩展点并举例说明。
一、ActionResult
ActionResult代表了每个Action的返回结果。asp.net mvc提供了众多内置的ActionResult类型,如:ContentResult,ViewResult,JsonResult等,每一种类型都代表了一种服务端的Response类型。我们什么时候需要使用这个扩展点呢?
假如客户端需要得到XML格式的数据列表:
public void GetUser() {var user = new UserViewModel(){Name = "richie",Age = 20,Email = "abc@126.com",Phone = "139********",Address = "my address"};XmlSerializer serializer = new XmlSerializer(typeof(UserViewModel));Response.ContentType = "text/xml";serializer.Serialize(Response.Output, user); }
我们可以在Controller中定义一个这样的方法,但是这个方法定义在Controller中有一点别扭,在MVC中每个Action通常都需要返回ActionResult类型,其次XML序列化这段代码完全可以重用。经过分析我们可以自定义一个XmlResult类型:
public class XmlResult : ActionResult {private object _data;public XmlResult(object data){_data = data;}public override void ExecuteResult(ControllerContext context){var serializer = new XmlSerializer(_data.GetType());var response = context.HttpContext.Response;response.ContentType = "text/xml";serializer.Serialize(response.Output, _data);} }
这时候Action就可以返回这种类型了:
public XmlResult GetUser() {var user = new UserViewModel(){Name = "richie",Age = 20,Email = "abc@126.com",Phone = "139********",Address = "my address"};return new XmlResult(user); }
同样的道理,你可以定义出其他的ActionResult类型,例如:CsvResult等。
二、Filter
MVC中有四种类型的Filter:IAuthorizationFilter,IActionFilter,IResultFilter,IExceptionFilter
这四个接口有点拦截器的意思,例如:当有异常出现时会被IExceptionFilter类型的Filter拦截,当Action在执行前和执行结束会被IActionFilter类型的Filter拦截。
通过实现IExceptionFilter我们可以自定义一个用来记录日志的Log4NetExceptionFilter:
public class Log4NetExceptionFilter : IExceptionFilter {private readonly ILog _logger;public Log4NetExceptionFilter(){_logger = LogManager.GetLogger(GetType());}public void OnException(ExceptionContext context){_logger.Error("Unhandled exception", context.Exception);} }
最后需要将自定义的Filter加入MVC的Filter列表中:
public class FilterConfig {public static void RegisterGlobalFilters(GlobalFilterCollection filters){filters.Add(new Log4NetExceptionFilter());} }
为了记录Action的执行时间,我们可以在Action执行前计时,Action执行结束后记录log:
public class StopwatchAttribute : ActionFilterAttribute {private const string StopwatchKey = "StopwatchFilter.Value";private readonly ILog _logger= LogManager.GetLogger(typeof(StopwatchAttribute));public override void OnActionExecuting(ActionExecutingContext filterContext){filterContext.HttpContext.Items[StopwatchKey] = Stopwatch.StartNew();}public override void OnActionExecuted(ActionExecutedContext filterContext){var stopwatch = (Stopwatch)filterContext.HttpContext.Items[StopwatchKey];stopwatch.Stop();var log=string.Format("controller:{0},action:{1},execution time:{2}ms",filterContext.ActionDescriptor.ControllerDescriptor.ControllerName,filterContext.ActionDescriptor.ActionName,stopwatch.ElapsedMilliseconds)_logger.Info(log);} }
ActionFilterAttribute是一个抽象类,它不但继承了IActionFilter, IResultFilter等Filter,还继承了FilterAttribute类型,这意味着我们可以将这个自定义的类型当作Attribute来标记到某个Action或者Controller上,同时它还是一个Filter,仍然可以加在MVC的Filter中起到全局拦截的作用。
三、HtmlHelper
在Razor页面中,如果需要写一段公用的用来展示html元素的逻辑,你可以选择使用@helper标记,例如:
@helper ShowProduct(List<ProductListViewModel.Product> products, string style) {<ul class="list-group">@foreach (var product in products){<li class="list-group-item @style"><a href="@product.Href" target="_blank">@product.Name</a></li>}</ul> }
这一段代码有点像一个方法定义,只需要传入一个list类型和字符串就会按照定义的逻辑输出html:
<h2>Product list using helper</h2> <div class="row"><div class="col-md-6">@ShowProduct(Model.SportProducts, "list-group-item-info")</div><div class="col-md-6">@ShowProduct(Model.BookProducts, "list-group-item-warning")</div> </div> <div class="row"><div class="col-md-6">@ShowProduct(Model.FoodProducts, "list-group-item-danger")</div> </div>
这样抽取的逻辑只对当前页面有效,如果我们想在不同的页面公用这一逻辑如何做呢?
在Razor中输入@Html即可得到HtmlHelper实例,例如我们可以这样用:@Html.TextBox("name")。由此可见我们可以将公用的逻辑扩展在HtmlHelper上:
public static class HtmlHelperExtensions {public static ListGroup ListGroup(this HtmlHelper htmlHelper){return new ListGroup();} }public class ListGroup {public MvcHtmlString Info<T>(List<T> data, Func<T, string> getName){return Show(data,getName, "list-group-item-info");}public MvcHtmlString Warning<T>(List<T> data, Func<T, string> getName){return Show(data,getName, "list-group-item-warning");}public MvcHtmlString Danger<T>(List<T> data, Func<T, string> getName){return Show(data,getName, "list-group-item-danger");}public MvcHtmlString Show<T>(List<T> data, Func<T, string> getName, string style){var ulBuilder = new TagBuilder("ul");ulBuilder.AddCssClass("list-group");foreach (T item in data){var liBuilder = new TagBuilder("li");liBuilder.AddCssClass("list-group-item");liBuilder.AddCssClass(style);liBuilder.SetInnerText(getName(item));ulBuilder.InnerHtml += liBuilder.ToString();}return new MvcHtmlString(ulBuilder.ToString());} }
有了上面的扩展,就可以这样使用了:
<h2>Product list using htmlHelper</h2> <div class="row"><div class="col-md-6">@Html.ListGroup().Info(Model.SportProducts,x=>x.Name)</div><div class="col-md-6">@Html.ListGroup().Warning(Model.BookProducts,x => x.Name)</div> </div> <div class="row"><div class="col-md-6">@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参数中。
假如我们跟客户的有一个约定,客户端会POST一个XML格式的数据到服务端,MVC并不能准确认识到这种数据请求,也就不能将客户端的请求数据绑定到Action方法的参数中。所以我们可以实现一个XmlModelBinder:
public class XmlModelBinder:IModelBinder {public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext){try{var modelType = bindingContext.ModelType;var serializer = new XmlSerializer(modelType);var inputStream = controllerContext.HttpContext.Request.InputStream;return serializer.Deserialize(inputStream);}catch{bindingContext.ModelState.AddModelError("", "The item could not be serialized");return null;}}}
有了这样的自定义ModelBinder,还需要通过在参数上加Attribute的方式启用这一ModelBinder:
public ActionResult PostXmlContent([ModelBinder(typeof(XmlModelBinder))]UserViewModel user) {return new XmlResult(user); }
我们使用PostMan发送个请求试试:
刚才我们显示告诉MVC某个Action的参数需要使用XmlModelBinder。我们还可以自定义一个XmlModelBinderProvider,明确告诉MVC什么类型的请求应该使用XmlModelBinder:
public class XmlModelBinderProvider: IModelBinderProvider {public IModelBinder GetBinder(Type modelType){var contentType = HttpContext.Current.Request.ContentType.ToLower();if (contentType != "text/xml"){return null;}return new XmlModelBinder();} }
这一Provider明确告知MVC当客户的请求格式为text/xml时,应该使用XmlModelBinder。
public class MvcApplication : System.Web.HttpApplication{protected void Application_Start(){ModelBinderProviders.BinderProviders.Insert(0, new XmlModelBinderProvider());//... }}
有了XmlModelBinderProvier,我们不再显示标记某个Action中的参数应该使用何种ModelBinder:
public ActionResult PostXmlContent(UserViewModel user) {return new XmlResult(user); }
七、自定义ControllerFactory实现依赖注入
MVC默认的DefaultControllerFactory通过反射的方式创建Controller实例,从而调用Action方法。为了实现依赖注入,我们需要自定义ControllerFactory从而通过IOC容器来创建Controller实例。
以Castle为例,需要定义WindsorControllerFactory,另外还要创建ContainerInstaller文件,将组建注册在容器中,最后通过ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(container));将MVC的ControllerFactory指定为我们自定义的WindsorControllerFactory。
为了简单起见,这一Nuget包可以帮助我们完成这一系列任务:
Install-Package Castle.Windsor.Web.Mvc
上面提到的步骤都会自动完成,新注册一个组件试试:
public class ProvidersInstaller:IWindsorInstaller{public void Install(IWindsorContainer container, IConfigurationStore store){container.Register(Component.For<IUserProvider>().ImplementedBy<UserProvider>().LifestylePerWebRequest());}}
Controller就可以进行构造器注入了:
private readonly IUserProvider _userProvider;public ServiceController(IUserProvider userProvider) {_userProvider = userProvider; }public ActionResult GetUserByIoc() {var user = _userProvider.GetUser();return new XmlResult(user); }
八、使用Lambda Expression Tree扩展MVC方法
准确来说这并不是MVC提供的扩展点,是我们利用Lambda Expression Tree写出强类型可重构的代码。以ActionLink一个重载为例:
public static MvcHtmlString ActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, object routeValues, object htmlAttributes);
在Razor页面,通过@Html.ActionLink("Line item 1", "OrderLineItem", "Service", new { id = 1 })可以生成a标签。这一代码的缺点在于Controller和Action都以字符串的方式给出,这样的代码在大型的软件项目中不利于重构,即便Controller和Action字符串编写错误,编译器也能成功编译。
我们可以利用Lambda Expression Tree解析出Controller和Action的名称。理论上所有需要填写Controller和Action字符串的方法都可以通过这一方法来实现。具体实现步骤参考Expression Tree 扩展MVC中的 HtmlHelper 和 UrlHelper。下面给出两种方法的使用对比:
<div class="row"><h2>Mvc way</h2><ul><li>@Html.ActionLink("Line item 1", "OrderLineItem", "Service", new { id = 1 }) </li><li>@Html.ActionLink("Line item 2", "OrderLineItem", "Service", new { id = 2 })</li><li>@Url.Action("OrderLineItem","Service",new {id=1})</li><li>@Url.Action("OrderLineItem","Service",new {id=2})</li></ul> </div><div class="row"><h2>Lambda Expression tree</h2><ul><li>@Html.ActionLink("Line item 1", (ServiceController c) => c.OrderLineItem(1))</li><li>@Html.ActionLink("Line item 2", (ServiceController c) => c.OrderLineItem(2))</li><li>@Url.Action((ServiceController c)=>c.OrderLineItem(1))</li><li>@Url.Action((ServiceController c)=>c.OrderLineItem(2))</li></ul> </div>
Demo下载
MVC 的八个扩展点相关推荐
- [转载]玩转Asp.net MVC 的八个扩展点
http://www.ayjs.net/post/274.html 转载于:https://www.cnblogs.com/cnki/p/5250294.html
- ASP.NET MVC中你必须知道的13个扩展点
ScottGu在其最新的博文中推荐了Simone Chiaretta的文章13 ASP.NET MVC extensibility points you have to know,该文章为我 ...
- 13个不可不知的ASP.NET MVC扩展点
ASP.NET MVC设计的主要原则之一是可扩展性.处理管线(processing pipeline)上的所有(或大多数)东西都是可替换的.因此,如果您不喜欢ASP.NET MVC所使用的约定(或缺乏 ...
- 非常有必要了解的Springboot启动扩展点
点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 来源:r6d.cn/r4P7 背景 Spring的核心思想 ...
- beanfactorypostprocessor_Spring源码分析(六)容器的扩展点(BeanFactoryPostProcessor)
之前的文章我写了BeanDefinition的基本概念和合并,其中很对次提到了容器的扩展点,这篇文章就写这方面的知识.这部分的内容主要涉及到官网的1.8小节.按照官网介绍来说,容器的扩展点可以分为三类 ...
- Tuscany SCA V1.0中的扩展机制和启动过程中的扩展点[11月29日更新]
2007年9月24日Tuscany SCA 发布了V1.0版本的实现 .本文讲述的内容使用的就是基于这个版本的,代码下载地址 http://incubator.apache.org/tuscany/s ...
- 三万字盘点Spring/Boot的那些常用扩展点
Spring对于每个Java后端程序员来说肯定不陌生,日常开发和面试必备的.本文就来盘点Spring/SpringBoot常见的扩展点,同时也来看看常见的开源框架是如何基于这些扩展点跟Spring/S ...
- 盘点Spring/Boot的那些常用扩展点
Spring对于每个Java后端程序员来说肯定不陌生,日常开发和面试必备的.本文就来盘点Spring/SpringBoot常见的扩展点,同时也来看看常见的开源框架是如何基于这些扩展点跟Spring/S ...
- MyBatis的扩展点(plugins)
2019独角兽企业重金招聘Python工程师标准>>> 1.mybatis扩展点plugins mybatis的扩展是通过拦截器Interceptor来实现的,本质上就是JDK的动态 ...
最新文章
- 用 C 语言开发一门编程语言 — 语法解析器
- 我的Android进阶之旅------gt;怎样在多个LinearLayout中加入分隔线
- python turtle画圆、循环法_实验与作业(Python)-03 Python程序实例解析(函数、循环、range、turtle)...
- 软件工程启程篇章:C#和四则运算生成与运算
- 最小生成树、最短路径树
- idea配置jfinal_JFinal 开箱评测,这次我是认真的
- 广安a货翡翠,自贡a货翡翠
- N76E003驱动WS2811实现渐变色、跑马灯
- 人人商城小程序 用户登录授权接口 wx.getUserProfile后,个别用户出现无法登录的问题
- Ubuntu安装Sopcast
- xLang 的类型转换
- 【华为机试真题 JAVA】字符串子序列II-100
- DataV平面地图组件全新升级_新功能带你抢先看
- StringBuffer换行
- (转)一致性哈希算法原理
- 几种常见的RAID工作模式讨论
- Python自动抢购脚本,学废了双十一双十二帮女票抢购心爱的礼物,隔壁女孩都馋哭了。
- 【游戏程序设计】完整二维游戏开发-飞机大战
- 零基础学Python:Matplotlib用法
- linux dd报错,安装Mac版变色龙使用dd命令遇到Resource busy 错误