第十七章 提升用户体验 之 使用MVC扩展功能控制程序行为
1. 概述
ASP.NET MVC具有很好的扩展性,每一个核心功能都可以被扩展、重写 和 定制。
本章内容包括:实现MVC过滤器和controller工厂、使用 action results,view engines,model binders 和route handlers 来控制程序行为。
2. 主要内容
2.1 实现MVC过滤器和controller工厂
2.1.1 MVC过滤器应用于controller或action,有四种主要的类型:
① Authorization(授权型),继承自 System.Web.Mvc.IAuthorizationFilter,用于验证调用者身份。
② Action,继承自 System.Web.Mvc.IActionFilter ,用于给action方法提供额外的信息,检查action传出的信息或者取消action的执行。
③ Result,继承自System.Web.Mvc.IResultFilter,允许开发者对action方法返回的结果做额外的处理。
④ Exception,继承自 System.Web.Mvc.IExceptionFilter,当有未处理的异常从action方法中抛出时会被执行到,覆盖了action的整个生命周期。
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false,
Inherited = true)]
*把过滤器添加到App_Start/FilterConfig.cs文件中的RegisterGlobalFilters方法,就可以在全局内有效。
filters.Add(new MyCustomAttribute());
2.1.2 还可以创建自定义的controllers,或者是可以生成它的工厂。原因有二:
① 为了支持 Dependency Injection (DI) 和 Inversion of Control (IoC)。
② 为了传递 服务引用 或者 数据仓库信息。
* 创建自定义的 ControllerFactory 类需要实现 System.Web.Mvc.IControllerFactory接口,包含三个方法:
① CreateController:处理实际的controller创建工作。
② ReleaseController:执行清理工作。
③ GetControllerSessionBehavior:处理与session的交互工作。
创建完毕,可以添加到Global.asax的 Application_Start方法来注册。
ControllerBuilder.Current.SetControllerFactory(
typeof(MyCustomControllerFactory());
2.2 使用 action results 控制程序行为
Action results,处理action方法请求的结果的执行。一般用来处理返回信息的格式化。
最常用的是ViewResultBase,它是 View 和 PartialView 的基类。用于创建、生成、转化可发送到HTTP输出流的HTML信息。
现有的Action results类型基本可以满足大部分需求,个别个性的类型,比如 PDF或eDocs, EPS格式,需要创建自定义的Action results类型。
创建自定义的Action results类型需要实现System.Web.Mvc.ActionResult接口,重写 ExecuteResult方法。
public class CustomResult<T> : ActionResult
{
public T Data { private get; set; }
public override void ExecuteResult(ControllerContext context)
{
// do work here
string resultFromWork = "work that was done";
context.HttpContext.Response.Write(resultFromWork);
}
}
2.3 使用view engines控制程序行为
视图引擎(View engines)是处理Views到HTML的装置。
ASP.NET MVC 4 中有两种主要的视图引擎: Razor 和 Web Forms (ASPX),两者的主要区别就在转化和解析代码的格式。
如果想要向已经创建的视图中添加内容,就可以实现自定义的视图引擎。
这两个主要的视图引擎都继承自抽象基类 System.Web.Mvc.VirtualPathProviderViewEngine。
可以通过实现System.Web.Mvc.IViewEngine接口来创建自定义的视图引擎。这个接口包含FindView, FindPartialView 和 ReleaseView 三个方法。
可以通过重写现有的视图引擎来添加扩展功能。
创建自定义视图引擎的另一个原因是为了支持更加灵活的路径映射功能。
public class CustomViewEngine : VirtualPathProviderViewEngine
{
public MyViewEngine()
{
this.ViewLocationFormats = new string[]
{ "~/Views/{1}/{2}.mytheme ", "~/Views/Shared/{2}.mytheme" };
this.PartialViewLocationFormats = new string[]
{ "~/Views/{1}/{2}.mytheme ", "~/Views/Shared/{2}. mytheme " };
}
protected override IView CreatePartialView
(ControllerContext controllerContext, string partialPath)
{
var physicalpath =
controllerContext.HttpContext.Server.MapPath(partialPath);
return new myCustomView (physicalpath);
}
protected override IView CreateView
(ControllerContext controllerContext, string viewPath, string masterPath)
{
var physicalpath = controllerContext.HttpContext.Server.MapPath(viewPath);
return new myCustomView(physicalpath);
}
}
ViewEngines.Engines.Add(new CustomViewEngine());
创建一个视图需要实现System.Web.Mvc.IView 接口,该接口只有一个Render方法,接收一个System.IO.TextWriter类型的参数。
public class MyCustomView : IView
{
private string _viewPhysicalPath;
public MyCustomView(string ViewPhysicalPath)
{
viewPhysicalPath = ViewPhysicalPath;
}
public void Render(ViewContext viewContext, System.IO.TextWriter writer)
{
string rawcontents = File.ReadAllText(_viewPhysicalPath);
string parsedcontents = Parse(rawcontents, viewContext.ViewData);
writer.Write(parsedcontents);
}
public string Parse(string contents, ViewDataDictionary viewdata)
{
return Regex.Replace(contents, "\\{(.+)\\}", m => GetMatch(m,viewdata));
}
public virtual string GetMatch(Match m, ViewDataDictionary viewdata)
{
if (m.Success)
{
string key = m.Result("$1");
if (viewdata.ContainsKey(key))
{
return viewdata[key].ToString();
}
}
return string.Empty;
}
}
2.4 使用model binders控制程序行为
模型绑定(model binding)的一个最大的好处是潜在的重用性。
public class DropDownDateTimeBinder : DefaultModelBinder
{
private List<string> DateTimeTypes = new List<string>{ "BirthDate",
"StartDate", "EndDate" };
protected override void BindProperty(ControllerContext contContext,
ModelBindingContext bindContext, PropertyDescriptor propDesc)
{
if (DateTimeTypes.Contains(propDesc.Name))
{
if (!string.IsNullOrEmpty(
contContext.HttpContext.Request.Form[propDesc.Name + "Year"])
{
DateTime dt = new DateTime(int.Parse(
contContext.HttpContext.Request.Form[propDesc.Name
+ "Year"]),
int.Parse(contContext.HttpContext.Request.Form[propDesc.Name +
"Month"]),
int.Parse(contContext.HttpContext.Request.Form[propDesc.Name +
"Day"]));
propDesc.SetValue(bindContext.Model, dt);
return;
}
}
base.BindProperty(contContext, bindContext, propDesc);
}
}
ModelBinders.Binders.DefaultBinder = new DropDownDateTimeBinder();
要创建自定义的模型绑定,需要实现System.Web.Mvc.IModelBinder接口,重写其 BindModel方法,然后注册。
ModelBinders.Binders.Add(typeof(MyNewModelBinder), new MyNewModelBinder ());
2.5 使用route handlers控制程序行为
route是ASP.NET MVC的一个很重要的概念,是区别于Web Forms的一个主要的可见方式。MVC是基于action的,web forms 是基于页面的。
System.Web.Mvc.MvcRouteHandler是默认的route handler,重写MvcRouteHandler时,需要确保重写了GetHttpHandler方法,可以在这个方法中检查数据或者修改数据。
public class MyCustomRouteHandler : MvcRouteHandler
{
protected override IHttpHandler GetHttpHandler(RequestContext reqContext)
{
string importantValue = reqContext.HttpContext.Request.Headers.Get(
"User-Agent");
if (!string.IsNullOrWhiteSpace(importantValue))
{
reqContext.RouteData.Values["action"] = importantValue +
reqContext.RouteData.Values["action"];
}
return base.GetHttpHandler(reqContext);
}
}
routes.MapRoute(
"Home",
"{controller}/{action}",
new { controller = "Home", action = "Index"
).RouteHandler = new MyCustomRouteHandler ();
如果你有更深入的定制需求,可以通过实现 System.Web.Routing.IRouteHandler 接口来实现。一般用于自定义HttpHandler的情况下。
Route watermarkRoute = new Route("images/{name}",
new WaterMarkRouteHandler("CodeClimber - 2011"));
routes.Add("image", watermarkRoute);
3. 总结
① ASP.NET MVC 4 为开发者提供了大量的扩展功能,几乎可以在请求的每一个阶段插入自定义的逻辑。
② 扩展功能中最强大的,可能也是最常用的,就是action filter。你可以重写现有的action filter,或者通过实现 IActionFilter 接口来开发自定义的filter。
action filter 使得开发者可以在action执行前后插入定制化逻辑。
③ 与action filter类似的还有 result filter,可以在result逻辑被执行前后插入自定义逻辑。
④ 当需要传递特定信息,比如 依赖信息 或者 运行时引用时,还可以创建自定义的controller工厂。
⑤ 通过重载视图引擎可以在生成Html时插入自定义逻辑。还可以创建自定义的视图引擎。
⑥ 模型绑定是在应用程序中的视图/表单和模型间建立单向或双向交互的方式。有时候没有直接的交互方式,这时候就需要自定义模型绑定。
⑦ 默认的route handler 在定义映射方面给了开发者很多灵活性。默认的handler不能满足的情况,还可以实现自定义的handler。