net MVC 的八个扩展点
net MVC 的八个扩展点
MVC模型以低耦合、可重用、可维护性高等众多优点已逐渐代替了WebForm模型。能够灵活使用MVC提供的扩展点可以达到事半功倍的效果,另一方面Asp.net MVC优秀的设计和高质量的代码也值得我们去阅读和学习。
本文将介绍Asp.net MVC中常用的八个扩展点并举例说明。
一、ActionResult
ActionResult代表了每个Action的返回结果。asp.net mvc提供了众多内置的ActionResult类型,如:ContentResult,ViewResult,JsonResult等,每一种类型都代表了一种服务端的Response类型。我们什么时候需要使用这个扩展点呢?
假如客户端需要得到XML格式的数据列表:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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类型:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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就可以返回这种类型了:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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列表中:
|
1
2
3
4
5
6
7
|
public class FilterConfig{ public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new Log4NetExceptionFilter()); }} |
为了记录Action的执行时间,我们可以在Action执行前计时,Action执行结束后记录log:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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标记,例如:
|
1
2
3
4
5
6
7
8
9
|
@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:
|
1
2
3
4
5
6
7
8
|
<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上:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
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()); }} |
有了上面的扩展,就可以这样使用了:
|
1
2
3
4
5
6
7
8
|
<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:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
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
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
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没什么区别:
|
1
2
3
|
[Required][AgeValidator]public int? Age { get; set; } |
不过我们有时候可能有这种需求:某个验证规则要针对Model中多个属性联合起来判断,所以上面的方案无法满足需求。这时候只需Model实现IValidatableObject接口即可:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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方法的参数中。
|
1
2
3
4
5
|
public ActionResult InputAge(UserViewModel user){ //... return View();} |
对于这样的一个Action,如果是Post请求,MVC会尝试将Form中的值赋值到user参数中,如果是get请求,MVC会尝试将QueryString的值赋值到user参数中。
假如我们跟客户的有一个约定,客户端会POST一个XML格式的数据到服务端,MVC并不能准确认识到这种数据请求,也就不能将客户端的请求数据绑定到Action方法的参数中。所以我们可以实现一个XmlModelBinder:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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:
|
1
2
3
4
|
public ActionResult PostXmlContent([ModelBinder(typeof(XmlModelBinder))]UserViewModel user){ return new XmlResult(user);} |
我们使用PostMan发送个请求试试:

刚才我们显示告诉MVC某个Action的参数需要使用XmlModelBinder。我们还可以自定义一个XmlModelBinderProvider,明确告诉MVC什么类型的请求应该使用XmlModelBinder:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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(); }} |
|
1
|
|
这一Provider明确告知MVC当客户的请求格式为text/xml时,应该使用XmlModelBinder。
|
1
2
3
4
5
6
7
8
9
|
public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { ModelBinderProviders.BinderProviders.Insert(0, new XmlModelBinderProvider()); //... } } |
有了XmlModelBinderProvier,我们不再显示标记某个Action中的参数应该使用何种ModelBinder:
|
1
2
3
4
|
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包可以帮助我们完成这一系列任务:
|
1
|
Install-Package Castle.Windsor.Web.Mvc |
上面提到的步骤都会自动完成,新注册一个组件试试:
|
1
2
3
4
5
6
7
|
public class ProvidersInstaller:IWindsorInstaller { public void Install(IWindsorContainer container, IConfigurationStore store) { container.Register(Component.For<IUserProvider>().ImplementedBy<UserProvider>().LifestylePerWebRequest()); } } |
Controller就可以进行构造器注入了:
|
1
2
3
4
5
6
7
8
9
10
11
12
|
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一个重载为例:
|
1
|
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。下面给出两种方法的使用对比:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<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下载:https://git.oschina.net/richieyangs/MVCExtension.Points
祝大家春节快乐,猴年大吉!
net MVC 的八个扩展点的更多相关文章
- 玩转Asp.net MVC 的八个扩展点
MVC模型以低耦合.可重用.可维护性高等众多优点已逐渐代替了WebForm模型.能够灵活使用MVC提供的扩展点可以达到事半功倍的效果,另一方面Asp.net MVC优秀的设计和高质量的代码也值得我们去 ...
- Asp.net MVC 的八个扩展点
http://www.cnblogs.com/richieyang/p/5180939.html MVC模型以低耦合.可重用.可维护性高等众多优点已逐渐代替了WebForm模型.能够灵活使用MVC提供 ...
- MVC 的八个扩展点
Asp.net MVC中常用的八个扩展点并举例说明. 一.ActionResult ActionResult代表了每个Action的返回结果.asp.net mvc提供了众多内置的ActionResu ...
- MVC自定定义扩展点之ActionNameSelectorAttribute+ActionFilterAttribute 在浏览器中打开pdf文档
仅仅演示 了ASP.MVC 5 下为了在在浏览器中打开pdf文档的实现方式之一,借此理解下自定义ActionNameSelectorAttribute+ActionFilterAttribute 类的 ...
- MVC中你必须知道的13个扩展点
MVC中你必须知道的13个扩展点 pasting 转:http://www.cnblogs.com/kirinboy/archive/2009/06/01/13-asp-net-mvc-extensi ...
- [转]ASP.NET MVC中你必须知道的13个扩展点
本文转自:http://www.cnblogs.com/ejiyuan/archive/2010/03/09/1681442.html ScottGu在其最新的博文中推荐了Simone Chiaret ...
- 13个不可不知的ASP.NET MVC扩展点
13个不可不知的ASP.NET MVC扩展点 ASP.NET MVC设计的主要原则之一是可扩展性.处理管线(processing pipeline)上的所有(或大多数)东西都是可替换的.因此,如果您不 ...
- MVC 常用扩展点:过滤器、模型绑定等
MVC 常用扩展点:过滤器.模型绑定等 一.过滤器(Filter) ASP.NET MVC中的每一个请求,都会分配给对应Controller(以下简称"控制器")下的特定Actio ...
- ASP.NET MVC中你必须知道的13个扩展点
ScottGu在其最新的博文中推荐了Simone Chiaretta的文章13 ASP.NET MVC extensibility points you have to know,该文章为我 ...
随机推荐
- [eclipse] 三个操作技巧
[eclipse] 三个操作技巧 1.快捷键Ctrl+Shift+i:Debug调试中直接获取方法的返回值 在下图代码中,想知道getHost(),则在调试时运行完该句代码后,选中"urlU ...
- 设计模式 - 装饰者模式(Decorator Pattern) Java的IO类 用法
装饰者模式(Decorator Pattern) Java的IO类 用法 本文地址: http://blog.csdn.net/caroline_wendy/article/details/26716 ...
- 【web开发学习笔记】Structs2 Action学习笔记(一个)
1.org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter准备和运行 2. <filter-mapping&g ...
- mormort 土拨鼠,做后端服务那是杠杠的,基于http.sys
http.sys你可以用 mormort 土拨鼠,做后端服务那是杠杠的,基于http.sys并且还是开源的,作者天天更新代码,非常勤奋,官方论坛提问,回答也快其实,稍微看看,就能玩的挺好的
- Android studio导入第三方类库
1.开发过程中想要导入第三方类库和Eclipse也是有差别的,我们导入SlidingMenu这个类库,从github上下载下来解压到项目目录下. 2.然后我们重启我们的android studio就会 ...
- Java Core和HeapDump
什么是Java Core和Heap Dump Java程序运行时,有时会产生Java Core及Heap Dump文件,它一般发生于Java程序遇到致命问题的情况下. 发生致命问题后,Java进程有时 ...
- 关于ubuntu下qt编译显示Cannot connect creator comm socket /tmp/qt_temp.xxx/stub-socket的解决的方法
今天在ubuntu下安装了qtcreator,准备測试一下能否用.果然一測试就出问题了,简单编写后F5编译在gnome-terminal中出现 Cannot connect creator comm ...
- Swift 编程语言新手教程
今天在网上看到一篇很好的教程,分享给大家 原文地址:http://gashero.iteye.com/blog/2075324 文件夹 1 简单介绍 2 Swift入门 3 简单值 4 ...
- [Android学习笔记]获取view的尺寸和坐标
对于UI方面很多时候需要获取它的很多信息,具体情况见view的文档 View文档 http://developer.android.com/training/index.html 常用方法:获取vie ...
- 【CSS3】transform-origin原点旋转
忙乱, 点 -moz-transform-origin: 0 0; -webkit-transform-origin:0 0; -o-transform-origin:0 0; 以右上角给原点 -mo ...