MVC 的八个扩展点

时间:2022-02-11 18:54:10

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 = ,
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 = ,
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拦截。

MVC 的八个扩展点

通过实现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>

效果:

MVC 的八个扩展点

四、RazorViewEngine

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

MVC 的八个扩展点

下面就让我们来实现一个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(, new ThemeViewEngine(activeTheme));
}; //...
}
}

接下来就开始编写不同风格的View了,重点在于编写的View文件夹组织方式要跟ThemeViewEngine中定义的路径要一致,以ServiceController为例,我们编写ocean和sky两种风格的View:

MVC 的八个扩展点

最后在web.config制定一种Theme:<add key="Theme" value="ocean"/>,ocean文件夹下的View将会被优先采用:

MVC 的八个扩展点

五、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 > )
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<)
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 的八个扩展点

刚才我们显示告诉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(, 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 = }) </li>
<li>@Html.ActionLink("Line item 2", "OrderLineItem", "Service", new { id = })</li>
<li>@Url.Action("OrderLineItem","Service",new {id=})</li>
<li>@Url.Action("OrderLineItem","Service",new {id=})</li>
</ul>
</div> <div class="row">
<h2>Lambda Expression tree</h2>
<ul>
<li>@Html.ActionLink("Line item 1", (ServiceController c) => c.OrderLineItem())</li>
<li>@Html.ActionLink("Line item 2", (ServiceController c) => c.OrderLineItem())</li>
<li>@Url.Action((ServiceController c)=>c.OrderLineItem())</li>
<li>@Url.Action((ServiceController c)=>c.OrderLineItem())</li>
</ul>
</div>

Demo下载

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

  1. net MVC 的八个扩展点

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

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

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

  3. Asp&period;net MVC 的八个扩展点

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

  4. MVC自定定义扩展点之ActionNameSelectorAttribute&plus;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. &lbrack;转&rsqb;ASP&period;NET MVC中你必须知道的13个扩展点

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

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

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

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

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

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

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

随机推荐

  1. 关于我们经常用到的form表单提交

    工作中遇到了太多太多的表单提交问题,曾经学过一个HTML的表单提交给 另外一个HTML页面,对于后台怎么获取有点想不起来了. 今天便做了几个实验,提交订单到后台,来掩饰后台如何接受表单内容: 实验 一 ...

  2. ES6中的解构赋值

    在解释什么是解构赋值前,我们先来看一下, ES5 中对变量的声明和赋值. var str = 'hello word'; 左边一个变量名,右边可以是字符串,数组或对象. ES6 中增加了一种更为便捷的 ...

  3. logging模块--日志文件

    初级的使用配置模式类似与print 默认打印waring等级及以上--通过更改等级来测试代码 logging.debug("debug no china") #调试模式 loggi ...

  4. 使用metasploit中Evasion模块

    简介 几天前我说了kali这次更新我最关心的是metasploit升级到了5.0,5.0中有一个新的模块叫Evasion模块,这个模块可以轻松的创建反杀毒软件的木马,今天我们就来试一试 操作 首先打开 ...

  5. JS 事件绑定、事件监听、事件委托详细介绍

    原:http://www.jb51.net/article/93752.htm 在JavaScript的学习中,我们经常会遇到JavaScript的事件机制,例如,事件绑定.事件监听.事件委托(事件代 ...

  6. Embedded SW uses STL or not

    As the complexity increasing of embedded software, more and more projects/products use C++ as the im ...

  7. PHP获取一篇文章内容中的全部图片,并下载

  8. Android 开发 命名规范(基础回顾)

    标识符命名法标识符命名法最要有四种: 1 驼峰(Camel)命名法:又称小驼峰命名法,除首单词外,其余所有单词的第一个字母大写. 2 帕斯卡(pascal)命名法:又称大驼峰命名法,所有单词的第一个字 ...

  9. Python基础学习总结&lpar;二)

    2.列表简介 Python有内置的一种数据类型列表:list. list是一种有序的集合. 列表由一系列按特定顺序排列的元素组合.用方括号 [ ] 来表示. list里面的元素的数据类型可以不同,比如 ...

  10. ECMall2&period;x模板制作入门系列之2(模板标签&sol;语法)

    ECMall2.x模板制作入门系列之2(模板标签/语法) 今天给大家带来一个模板语法的教程.希望能为ECMall模板制作者提供一份参考资料.如有问题.建议和意见,欢迎提出. 在ECMall模板中,用& ...