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 的八个扩展点相关推荐

  1. [转载]玩转Asp.net MVC 的八个扩展点

    http://www.ayjs.net/post/274.html 转载于:https://www.cnblogs.com/cnki/p/5250294.html

  2. ASP.NET MVC中你必须知道的13个扩展点

         ScottGu在其最新的博文中推荐了Simone Chiaretta的文章13 ASP.NET MVC extensibility points you have to know,该文章为我 ...

  3. 13个不可不知的ASP.NET MVC扩展点

    ASP.NET MVC设计的主要原则之一是可扩展性.处理管线(processing pipeline)上的所有(或大多数)东西都是可替换的.因此,如果您不喜欢ASP.NET MVC所使用的约定(或缺乏 ...

  4. 非常有必要了解的Springboot启动扩展点

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 来源:r6d.cn/r4P7 背景 Spring的核心思想 ...

  5. beanfactorypostprocessor_Spring源码分析(六)容器的扩展点(BeanFactoryPostProcessor)

    之前的文章我写了BeanDefinition的基本概念和合并,其中很对次提到了容器的扩展点,这篇文章就写这方面的知识.这部分的内容主要涉及到官网的1.8小节.按照官网介绍来说,容器的扩展点可以分为三类 ...

  6. Tuscany SCA V1.0中的扩展机制和启动过程中的扩展点[11月29日更新]

    2007年9月24日Tuscany SCA 发布了V1.0版本的实现 .本文讲述的内容使用的就是基于这个版本的,代码下载地址 http://incubator.apache.org/tuscany/s ...

  7. 三万字盘点Spring/Boot的那些常用扩展点

    Spring对于每个Java后端程序员来说肯定不陌生,日常开发和面试必备的.本文就来盘点Spring/SpringBoot常见的扩展点,同时也来看看常见的开源框架是如何基于这些扩展点跟Spring/S ...

  8. 盘点Spring/Boot的那些常用扩展点

    Spring对于每个Java后端程序员来说肯定不陌生,日常开发和面试必备的.本文就来盘点Spring/SpringBoot常见的扩展点,同时也来看看常见的开源框架是如何基于这些扩展点跟Spring/S ...

  9. MyBatis的扩展点(plugins)

    2019独角兽企业重金招聘Python工程师标准>>> 1.mybatis扩展点plugins mybatis的扩展是通过拦截器Interceptor来实现的,本质上就是JDK的动态 ...

最新文章

  1. 用 C 语言开发一门编程语言 — 语法解析器
  2. 我的Android进阶之旅------gt;怎样在多个LinearLayout中加入分隔线
  3. python turtle画圆、循环法_实验与作业(Python)-03 Python程序实例解析(函数、循环、range、turtle)...
  4. 软件工程启程篇章:C#和四则运算生成与运算
  5. 最小生成树、最短路径树
  6. idea配置jfinal_JFinal 开箱评测,这次我是认真的
  7. 广安a货翡翠,自贡a货翡翠
  8. N76E003驱动WS2811实现渐变色、跑马灯
  9. 人人商城小程序 用户登录授权接口 wx.getUserProfile后,个别用户出现无法登录的问题
  10. Ubuntu安装Sopcast
  11. xLang 的类型转换
  12. 【华为机试真题 JAVA】字符串子序列II-100
  13. DataV平面地图组件全新升级_新功能带你抢先看
  14. StringBuffer换行
  15. (转)一致性哈希算法原理
  16. 几种常见的RAID工作模式讨论
  17. Python自动抢购脚本,学废了双十一双十二帮女票抢购心爱的礼物,隔壁女孩都馋哭了。
  18. 【游戏程序设计】完整二维游戏开发-飞机大战
  19. 零基础学Python:Matplotlib用法
  20. linux dd报错,安装Mac版变色龙使用dd命令遇到Resource busy 错误

热门文章

  1. 使用Product Code卸载MSI安装程序
  2. Java基础如何运用FileOutputStream
  3. Day6 数据清洗(2)
  4. 发布自己的CocoaPods的步骤
  5. RabbitMQ系列(三)RabbitMQ交换器Exchange介绍与实践
  6. 自动更新统计信息的阀值——人为更新统计信息的重要性
  7. Kafka 配置参数汇总及相关说明
  8. widows下 python环境变量配置
  9. systemverilog编译介绍
  10. 【DP】LeetCode 64. Minimum Path Sum