Asp.net MVC中常用的八个扩展点并举例说明。

一、ActionResult

ActionResult代表了每个Action的返回结果。asp.net mvc提供了众多内置的ActionResult类型,如:ContentResult,ViewResult,JsonResult等,每一种类型都代表了一种服务端的Response类型。我们什么时候需要使用这个扩展点呢?

假如客户端需要得到XML格式的数据列表:

  1. public void GetUser()
  2. {
  3. var user = new UserViewModel()
  4. {
  5. Name = "richie",
  6. Age = ,
  7. Email = "abc@126.com",
  8. Phone = "139********",
  9. Address = "my address"
  10. };
  11. XmlSerializer serializer = new XmlSerializer(typeof(UserViewModel));
  12. Response.ContentType = "text/xml";
  13. serializer.Serialize(Response.Output, user);
  14. }

我们可以在Controller中定义一个这样的方法,但是这个方法定义在Controller中有一点别扭,在MVC中每个Action通常都需要返回ActionResult类型,其次XML序列化这段代码完全可以重用。经过分析我们可以自定义一个XmlResult类型:

  1. public class XmlResult : ActionResult
  2. {
  3. private object _data;
  4.  
  5. public XmlResult(object data)
  6. {
  7. _data = data;
  8. }
  9.  
  10. public override void ExecuteResult(ControllerContext context)
  11. {
  12. var serializer = new XmlSerializer(_data.GetType());
  13. var response = context.HttpContext.Response;
  14. response.ContentType = "text/xml";
  15. serializer.Serialize(response.Output, _data);
  16. }
  17. }

这时候Action就可以返回这种类型了:

  1. public XmlResult GetUser()
  2. {
  3. var user = new UserViewModel()
  4. {
  5. Name = "richie",
  6. Age = ,
  7. Email = "abc@126.com",
  8. Phone = "139********",
  9. Address = "my address"
  10. };
  11.  
  12. return new XmlResult(user);
  13. }

同样的道理,你可以定义出其他的ActionResult类型,例如:CsvResult等。

二、Filter

MVC中有四种类型的Filter:IAuthorizationFilter,IActionFilter,IResultFilter,IExceptionFilter

这四个接口有点拦截器的意思,例如:当有异常出现时会被IExceptionFilter类型的Filter拦截,当Action在执行前和执行结束会被IActionFilter类型的Filter拦截。

通过实现IExceptionFilter我们可以自定义一个用来记录日志的Log4NetExceptionFilter:

  1. public class Log4NetExceptionFilter : IExceptionFilter
  2. {
  3. private readonly ILog _logger;
  4.  
  5. public Log4NetExceptionFilter()
  6. {
  7. _logger = LogManager.GetLogger(GetType());
  8. }
  9. public void OnException(ExceptionContext context)
  10. {
  11. _logger.Error("Unhandled exception", context.Exception);
  12. }
  13. }

最后需要将自定义的Filter加入MVC的Filter列表中:

  1. public class FilterConfig
  2. {
  3. public static void RegisterGlobalFilters(GlobalFilterCollection filters)
  4. {
  5. filters.Add(new Log4NetExceptionFilter());
  6. }
  7. }

为了记录Action的执行时间,我们可以在Action执行前计时,Action执行结束后记录log:

  1. public class StopwatchAttribute : ActionFilterAttribute
  2. {
  3. private const string StopwatchKey = "StopwatchFilter.Value";
  4. private readonly ILog _logger= LogManager.GetLogger(typeof(StopwatchAttribute));
  5.  
  6. public override void OnActionExecuting(ActionExecutingContext filterContext)
  7. {
  8. filterContext.HttpContext.Items[StopwatchKey] = Stopwatch.StartNew();
  9. }
  10.  
  11. public override void OnActionExecuted(ActionExecutedContext filterContext)
  12. {
  13. var stopwatch = (Stopwatch)filterContext.HttpContext.Items[StopwatchKey];
  14. stopwatch.Stop();
  15.  
  16. var log=string.Format("controller:{0},action:{1},execution time:{2}ms",filterContext.ActionDescriptor.ControllerDescriptor.ControllerName,filterContext.ActionDescriptor.ActionName,stopwatch.ElapsedMilliseconds)
  17. _logger.Info(log);
  18. }
  19. }

ActionFilterAttribute是一个抽象类,它不但继承了IActionFilter, IResultFilter等Filter,还继承了FilterAttribute类型,这意味着我们可以将这个自定义的类型当作Attribute来标记到某个Action或者Controller上,同时它还是一个Filter,仍然可以加在MVC的Filter中起到全局拦截的作用。

三、HtmlHelper

在Razor页面中,如果需要写一段公用的用来展示html元素的逻辑,你可以选择使用@helper标记,例如:

  1. @helper ShowProduct(List<ProductListViewModel.Product> products, string style)
  2. {
  3. <ul class="list-group">
  4. @foreach (var product in products)
  5. {
  6. <li class="list-group-item @style"><a href="@product.Href" target="_blank">@product.Name</a></li>
  7. }
  8. </ul>
  9. }

这一段代码有点像一个方法定义,只需要传入一个list类型和字符串就会按照定义的逻辑输出html:

  1. <h2>Product list using helper</h2>
  2. <div class="row">
  3. <div class="col-md-6">@ShowProduct(Model.SportProducts, "list-group-item-info")</div>
  4. <div class="col-md-6">@ShowProduct(Model.BookProducts, "list-group-item-warning")</div>
  5. </div>
  6. <div class="row">
  7. <div class="col-md-6">@ShowProduct(Model.FoodProducts, "list-group-item-danger")</div>
  8. </div>

这样抽取的逻辑只对当前页面有效,如果我们想在不同的页面公用这一逻辑如何做呢?

在Razor中输入@Html即可得到HtmlHelper实例,例如我们可以这样用:@Html.TextBox("name")。由此可见我们可以将公用的逻辑扩展在HtmlHelper上:

  1. public static class HtmlHelperExtensions
  2. {
  3. public static ListGroup ListGroup(this HtmlHelper htmlHelper)
  4. {
  5. return new ListGroup();
  6. }
  7. }
  8.  
  9. public class ListGroup
  10. {
  11. public MvcHtmlString Info<T>(List<T> data, Func<T, string> getName)
  12. {
  13. return Show(data,getName, "list-group-item-info");
  14. }
  15.  
  16. public MvcHtmlString Warning<T>(List<T> data, Func<T, string> getName)
  17. {
  18. return Show(data,getName, "list-group-item-warning");
  19. }
  20.  
  21. public MvcHtmlString Danger<T>(List<T> data, Func<T, string> getName)
  22. {
  23. return Show(data,getName, "list-group-item-danger");
  24. }
  25.  
  26. public MvcHtmlString Show<T>(List<T> data, Func<T, string> getName, string style)
  27. {
  28. var ulBuilder = new TagBuilder("ul");
  29. ulBuilder.AddCssClass("list-group");
  30. foreach (T item in data)
  31. {
  32. var liBuilder = new TagBuilder("li");
  33. liBuilder.AddCssClass("list-group-item");
  34. liBuilder.AddCssClass(style);
  35. liBuilder.SetInnerText(getName(item));
  36. ulBuilder.InnerHtml += liBuilder.ToString();
  37. }
  38. return new MvcHtmlString(ulBuilder.ToString());
  39. }
  40. }

有了上面的扩展,就可以这样使用了:

  1. <h2>Product list using htmlHelper</h2>
  2. <div class="row">
  3. <div class="col-md-6">@Html.ListGroup().Info(Model.SportProducts,x=>x.Name)</div>
  4. <div class="col-md-6">@Html.ListGroup().Warning(Model.BookProducts,x => x.Name)</div>
  5. </div>
  6. <div class="row">
  7. <div class="col-md-6">@Html.ListGroup().Danger(Model.FoodProducts,x => x.Name)</div>
  8. </div>

效果:

四、RazorViewEngine

通过自定义RazorViewEngine可以实现同一份后台代码对应不同风格的View。利用这一扩展能够实现不同的Theme风格切换。再比如站点可能需要在不同的语言环境下切换到不同的风格,也可以通过自定义RazorViewEngine来实现。

下面就让我们来实现一个Theme切换的功能,首先自定义一个ViewEngine:

  1. public class ThemeViewEngine: RazorViewEngine
  2. {
  3. public ThemeViewEngine(string theme)
  4. {
  5.  
  6. ViewLocationFormats = new[]
  7. {
  8. "~/Views/Themes/" + theme + "/{1}/{0}.cshtml",
  9. "~/Views/Themes/" + theme + "/Shared/{0}.cshtml"
  10. };
  11.  
  12. PartialViewLocationFormats = new[]
  13. {
  14. "~/Views/Themes/" + theme + "/{1}/{0}.cshtml",
  15. "~/Views/Themes/" + theme + "/Shared/{0}.cshtml"
  16. };
  17.  
  18. AreaViewLocationFormats = new[]
  19. {
  20. "~Areas/{2}/Views/Themes/" + theme + "/{1}/{0}.cshtml",
  21. "~Areas/{2}/Views/Themes/" + theme + "/Shared/{0}.cshtml"
  22. };
  23.  
  24. AreaPartialViewLocationFormats = new[]
  25. {
  26. "~Areas/{2}/Views/Themes/" + theme + "/{1}/{0}.cshtml",
  27. "~Areas/{2}/Views/Themes/" + theme + "/Shared/{0}.cshtml"
  28. };
  29. }
  30. }

当我们启用这一ViewEngine时,Razor就会在/Views/Themes/文件夹下去找View文件。为了启用自定义的ViewEngine,需要将ThemeViewEngine加入到ViewEngines

  1. public class MvcApplication : System.Web.HttpApplication
  2. {
  3. protected void Application_Start()
  4. {
  5.  
  6. if (!string.IsNullOrEmpty(ConfigurationManager.AppSettings["Theme"]))
  7. {
  8. var activeTheme = ConfigurationManager.AppSettings["Theme"];
  9. ViewEngines.Engines.Insert(, new ThemeViewEngine(activeTheme));
  10. };
  11.  
  12. //...
  13. }
  14. }

接下来就开始编写不同风格的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. public class AgeValidator: ValidationAttribute
  2. {
  3. public AgeValidator()
  4. {
  5. ErrorMessage = "Please enter the age>18";
  6. }
  7.  
  8. public override bool IsValid(object value)
  9. {
  10. if (value == null)
  11. return false;
  12.  
  13. int age;
  14. if (int.TryParse(value.ToString(), out age))
  15. {
  16. if (age > )
  17. return true;
  18.  
  19. return false;
  20. }
  21.  
  22. return false;
  23. }
  24. }

自定义的AgeValidator使用起来跟MVC内置的ValiatorAttribute没什么区别:

  1. [Required]
  2. [AgeValidator]
  3. public int? Age { get; set; }

不过我们有时候可能有这种需求:某个验证规则要针对Model中多个属性联合起来判断,所以上面的方案无法满足需求。这时候只需Model实现IValidatableObject接口即可:

  1. public class UserViewModel:IValidatableObject
  2. {
  3. public string Name { get; set; }
  4.  
  5. [Required]
  6. [AgeValidator]
  7. public int? Age { get; set; }
  8.  
  9. public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
  10. {
  11. if(string.IsNullOrEmpty(Name))
  12. yield return new ValidationResult("the name can not be empty");
  13.  
  14. if (Name.Equals("lucy"))
  15. {
  16. if(Age.Value<)
  17. yield return new ValidationResult("lucy's age must greater than 25");
  18. }
  19. }
  20. }

六、ModelBinder

Model的绑定体现在从当前请求提取相应的数据绑定到目标Action方法的参数中。

  1. public ActionResult InputAge(UserViewModel user)
  2. {
  3. //...
  4. return View();
  5. }

对于这样的一个Action,如果是Post请求,MVC会尝试将Form中的值赋值到user参数中,如果是get请求,MVC会尝试将QueryString的值赋值到user参数中。

假如我们跟客户的有一个约定,客户端会POST一个XML格式的数据到服务端,MVC并不能准确认识到这种数据请求,也就不能将客户端的请求数据绑定到Action方法的参数中。所以我们可以实现一个XmlModelBinder:

  1. public class XmlModelBinder:IModelBinder
  2. {
  3. public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
  4. {
  5. try
  6. {
  7. var modelType = bindingContext.ModelType;
  8. var serializer = new XmlSerializer(modelType);
  9. var inputStream = controllerContext.HttpContext.Request.InputStream;
  10. return serializer.Deserialize(inputStream);
  11. }
  12. catch
  13. {
  14. bindingContext.ModelState.AddModelError("", "The item could not be serialized");
  15. return null;
  16. }
  17.  
  18. }
  19.  
  20. }

有了这样的自定义ModelBinder,还需要通过在参数上加Attribute的方式启用这一ModelBinder:

  1. public ActionResult PostXmlContent([ModelBinder(typeof(XmlModelBinder))]UserViewModel user)
  2. {
  3. return new XmlResult(user);
  4. }

我们使用PostMan发送个请求试试:

刚才我们显示告诉MVC某个Action的参数需要使用XmlModelBinder。我们还可以自定义一个XmlModelBinderProvider,明确告诉MVC什么类型的请求应该使用XmlModelBinder:

  1. public class XmlModelBinderProvider: IModelBinderProvider
  2. {
  3. public IModelBinder GetBinder(Type modelType)
  4. {
  5. var contentType = HttpContext.Current.Request.ContentType.ToLower();
  6. if (contentType != "text/xml")
  7. {
  8. return null;
  9. }
  10.  
  11. return new XmlModelBinder();
  12. }
  13. }

这一Provider明确告知MVC当客户的请求格式为text/xml时,应该使用XmlModelBinder。

  1. public class MvcApplication : System.Web.HttpApplication
  2. {
  3. protected void Application_Start()
  4. {
  5.  
  6. ModelBinderProviders.BinderProviders.Insert(, new XmlModelBinderProvider());
  7. //...
  8. }
  9. }

有了XmlModelBinderProvier,我们不再显示标记某个Action中的参数应该使用何种ModelBinder:

  1. public ActionResult PostXmlContent(UserViewModel user)
  2. {
  3. return new XmlResult(user);
  4. }

七、自定义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. public class ProvidersInstaller:IWindsorInstaller
  2. {
  3. public void Install(IWindsorContainer container, IConfigurationStore store)
  4. {
  5. container.Register(Component.For<IUserProvider>().ImplementedBy<UserProvider>().LifestylePerWebRequest());
  6. }
  7. }

Controller就可以进行构造器注入了:

  1. private readonly IUserProvider _userProvider;
  2.  
  3. public ServiceController(IUserProvider userProvider)
  4. {
  5. _userProvider = userProvider;
  6. }
  7.  
  8. public ActionResult GetUserByIoc()
  9. {
  10. var user = _userProvider.GetUser();
  11. return new XmlResult(user);
  12. }

八、使用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. <div class="row">
  2. <h2>Mvc way</h2>
  3. <ul>
  4. <li>@Html.ActionLink("Line item 1", "OrderLineItem", "Service", new { id = }) </li>
  5. <li>@Html.ActionLink("Line item 2", "OrderLineItem", "Service", new { id = })</li>
  6. <li>@Url.Action("OrderLineItem","Service",new {id=})</li>
  7. <li>@Url.Action("OrderLineItem","Service",new {id=})</li>
  8. </ul>
  9. </div>
  10.  
  11. <div class="row">
  12. <h2>Lambda Expression tree</h2>
  13. <ul>
  14. <li>@Html.ActionLink("Line item 1", (ServiceController c) => c.OrderLineItem())</li>
  15. <li>@Html.ActionLink("Line item 2", (ServiceController c) => c.OrderLineItem())</li>
  16. <li>@Url.Action((ServiceController c)=>c.OrderLineItem())</li>
  17. <li>@Url.Action((ServiceController c)=>c.OrderLineItem())</li>
  18. </ul>
  19. </div>

Demo下载

MVC 的八个扩展点的更多相关文章

  1. net MVC 的八个扩展点

    net MVC 的八个扩展点 MVC模型以低耦合.可重用.可维护性高等众多优点已逐渐代替了WebForm模型.能够灵活使用MVC提供的扩展点可以达到事半功倍的效果,另一方面Asp.net MVC优秀的 ...

  2. 玩转Asp.net MVC 的八个扩展点

    MVC模型以低耦合.可重用.可维护性高等众多优点已逐渐代替了WebForm模型.能够灵活使用MVC提供的扩展点可以达到事半功倍的效果,另一方面Asp.net MVC优秀的设计和高质量的代码也值得我们去 ...

  3. Asp.net MVC 的八个扩展点

    http://www.cnblogs.com/richieyang/p/5180939.html MVC模型以低耦合.可重用.可维护性高等众多优点已逐渐代替了WebForm模型.能够灵活使用MVC提供 ...

  4. MVC自定定义扩展点之ActionNameSelectorAttribute+ActionFilterAttribute 在浏览器中打开pdf文档

    仅仅演示 了ASP.MVC 5 下为了在在浏览器中打开pdf文档的实现方式之一,借此理解下自定义ActionNameSelectorAttribute+ActionFilterAttribute 类的 ...

  5. MVC中你必须知道的13个扩展点

    MVC中你必须知道的13个扩展点 pasting 转:http://www.cnblogs.com/kirinboy/archive/2009/06/01/13-asp-net-mvc-extensi ...

  6. [转]ASP.NET MVC中你必须知道的13个扩展点

    本文转自:http://www.cnblogs.com/ejiyuan/archive/2010/03/09/1681442.html ScottGu在其最新的博文中推荐了Simone Chiaret ...

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

    13个不可不知的ASP.NET MVC扩展点 ASP.NET MVC设计的主要原则之一是可扩展性.处理管线(processing pipeline)上的所有(或大多数)东西都是可替换的.因此,如果您不 ...

  8. MVC 常用扩展点:过滤器、模型绑定等

    MVC 常用扩展点:过滤器.模型绑定等 一.过滤器(Filter) ASP.NET MVC中的每一个请求,都会分配给对应Controller(以下简称"控制器")下的特定Actio ...

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

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

随机推荐

  1. 最小生成树 (Minimum Spanning Tree,MST) --- Prim算法

    本文链接:http://www.cnblogs.com/Ash-ly/p/5409904.html 普瑞姆(Prim)算法: 假设N = (V, {E})是连通网,TE是N上最小生成树边的集合,U是是 ...

  2. HDU 1325 Is It A Tree?(并查集)

    题目大意: 给你两个节点,前者指向后者(可以认为前者是后者的父节点),然后让你判断是否是一棵树. 解题思路: 先说说这道题和小希的迷宫(HDU1272)那道题的区别,前者给出的两个点是有方向的,而后者 ...

  3. The 15th Zhejiang Provincial Collegiate Programming Contest Sponsored by TuSimple -A Peak

    Peak Time Limit: 1 Second      Memory Limit: 65536 KB A sequence of  integers  is called a peak, if ...

  4. CentOS 7.1 中文正式版下载 - 最流行的免费开源企业级 Linux 服务器操作系统

    如果说 Ubuntu 是现今最受桌面用户欢迎的 Linux 操作系统,那么 CentOS 就是最受公司.企业.IDC 喜爱的 Linux 发行版了.得益于极为出色的稳定性,全球范围内无数著名网站均选用 ...

  5. 【贪心】Codeforces Round #401 (Div. 2) D. Cloud of Hashtags

    从后向前枚举字符串,然后从左向右枚举位. 如果该串的某位比之前的串的该位小,那么将之前的那串截断. 如果该串的某位比之前的串的该位大,那么之前那串可以直接保留全长度. 具体看代码. #include& ...

  6. Problem H: 阶乘和

    #include<stdio.h> int main() { ; ; ; int n; scanf("%d",&n); ;i<=n;i++) { ret= ...

  7. ionic 打包成apk后,所有网络请求404

    无论怎么改 config.xml <allow-navigation href="http://*/*" /> <allow-intent href=" ...

  8. 专访阿里巴巴研究员“赵海平”:Facebook的PHP底层性能优化之路(HipHop,HHVM)

    专访阿里巴巴研究员“赵海平”:Facebook的PHP底层性能优化之路 http://www.infoq.com/cn/articles/interview-alibaba-zhaohaiping

  9. Klaus Aschenbrenner--windbg

    http://www.sqlservercentral.com/blogs/aschenbrenner/?page=1

  10. Swift,类的调用

    1.class或者struct如果没有实例没办法直接调用 (1)设置class后可直接调用 class S{ static var width:Int{ //static代表公有的,都可使用 retu ...