http://www.cnblogs.com/richieyang/p/5180939.html
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
祝大家春节快乐,猴年大吉!
Asp.net MVC 的八个扩展点的更多相关文章
-
玩转Asp.net MVC 的八个扩展点
MVC模型以低耦合.可重用.可维护性高等众多优点已逐渐代替了WebForm模型.能够灵活使用MVC提供的扩展点可以达到事半功倍的效果,另一方面Asp.net MVC优秀的设计和高质量的代码也值得我们去 ...
-
net MVC 的八个扩展点
net MVC 的八个扩展点 MVC模型以低耦合.可重用.可维护性高等众多优点已逐渐代替了WebForm模型.能够灵活使用MVC提供的扩展点可以达到事半功倍的效果,另一方面Asp.net MVC优秀的 ...
-
MVC 的八个扩展点
Asp.net MVC中常用的八个扩展点并举例说明. 一.ActionResult ActionResult代表了每个Action的返回结果.asp.net mvc提供了众多内置的ActionResu ...
-
ASP.NET MVC 4 (八) URL链接和Ajax帮助函数
使用帮助函数创建链接 MVC提供一些帮助函数创建链接,这些函数根据路径映射表自动调整生成的URL: 说明 示例 输出结果 应用程序相对URL Url.Content("~/Content/S ...
-
ASP.NET MVC教程八:_ViewStart.cshtml
一.引言 _ViewStart.cshtml是在ASP.NET MVC 3.0及更高版本以后出现的,用Razor模板引擎新建项目后,Views目录下面会出现一个这样的文件: 打开_ViewStart. ...
-
MVC自定定义扩展点之ActionNameSelectorAttribute+ActionFilterAttribute 在浏览器中打开pdf文档
仅仅演示 了ASP.MVC 5 下为了在在浏览器中打开pdf文档的实现方式之一,借此理解下自定义ActionNameSelectorAttribute+ActionFilterAttribute 类的 ...
-
ASP.NET MVC 第八回 Helper之演化
题目:如何在View中写一个超级连接连接到主页? 这个问题看起来很好回答: <a href="/home/index">首页</a> 其实上面这种不能称之为 ...
-
详解ASP.NET MVC的请求生命周期
本文的目的旨在详细描述asp.net mvc请求从开始到结束的每一个过程. 我希望能理解在浏览器输入url并敲击回车来请求一个asp.net mvc网站的页面之后发生的任何事情. 为什么需要关心这些? ...
-
ASP.NET MVC的请求生命周期
我希望能理解在浏览器输入URL并敲击回车来请求一个ASP.NET MVC网站的页面之后发生的任何事情. 为什么需要关心这些?有两个原因.首先是因为ASP.NET MVC是一个扩展性非常强的框架.例如, ...
随机推荐
-
pcl曲面重建模块-poisson重建算法示例
poisson曲面重建算法 pcl-1.8测试通过 #include <iostream> #include <pcl/common/common.h> #include &l ...
-
Python学习笔记(一)——环境搭建
一.安装包下载: 国内镜像:32位:http://pan.baidu.com/s/1jI4q4lS 64位:http://pan.baidu.com/s/1eRPhpRW 版本更迭速度很 ...
-
tls/ssl证书生成和格式转换
生成密钥:openssl genrsa -out my.key 2048 生成csr申请文件:openssl req -sha256 -new -key my.key -out my.csr 生成自签 ...
-
URAL 1915 Titan Ruins: Reconstruction of Bygones(思路)
搞这个题差不多是从比赛开始到结束. 从自信慢慢的看题一直到wrong到死. 这个题目可以说成是思路题,以为我们只要明白一点,这道题就成了纯暴力的水题, 那就是当操作数不足栈中数字数目的时候,我们就没有 ...
-
Unity 区分不同平台
问题:公司开发的游戏实在android平台上运行,但是我们是在windows平台下进行开发,OK ,经常有些地方开发完之后就要换到android上面,能区分平台的不同就可以对代码做区分处理 回答:un ...
-
自动化脚本中click()或sendKeys()没有反应
前提: 排除xpath引用错误或元素的xpath每次都不同的情形. 问题描述 自动化脚本中click()方法和sendKeys()方法报错, 返回异常InvocationTargetException ...
-
华为mate10 UA
Dalvik/2.1.0 (Linux; U; Android 9; ALP-AL00 Build/HUAWEIALP-AL00) "user-agent": "Mozi ...
-
PLSql的使用
1.安装 plsqldeveloper和数据库驱动-ODAC 2.在数据库驱动ODAC中添加 Oracle客户端的网络服务名配置文件tnsnames.ora 路径为: 3.汉化 直接运行Languag ...
-
f11 全屏
function fullScreen() { var el = document.documentElement; var rfs = el.requestFullScreen || el.webk ...
-
vue 数组push元素 视图没更新
Vue 包含一组观察数组的变异方法,所以它们也将会触发视图更新.这些方法如下: push() pop() shift() unshift() splice() sort() reverse() 问题描 ...