分享

玩转Asp.net MVC 的八个扩展点(上)

 weijianian 2016-08-07


来源:richiezhang

链接: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格式的数据列表:


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类型:


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 = 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:


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 products, string style)
{
        @foreach (var product in products)
        {
            @product.Name
        }
}


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


Product list using helper
@ShowProduct(Model.SportProducts, 'list-group-item-info')
@ShowProduct(Model.BookProducts, 'list-group-item-warning')
@ShowProduct(Model.FoodProducts, 'list-group-item-danger')


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


在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(List data, Func getName)
    {
        return Show(data,getName, 'list-group-item-info');
    }
 
    public MvcHtmlString Warning(List data, Func getName)
    {
        return Show(data,getName, 'list-group-item-warning');
    }

    public MvcHtmlString Danger(List data, Func getName)
    {
        return Show(data,getName, 'list-group-item-danger');
    }
 
    public MvcHtmlString Show(List data, Func 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());
    }
}


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


Product list using htmlHelper
@Html.ListGroup().Info(Model.SportProducts,x=>x.Name)
@Html.ListGroup().Warning(Model.BookProducts,x => x.Name)
@Html.ListGroup().Danger(Model.FoodProducts,x => x.Name)


效果:



四、RazorViewEngine


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



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


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


最后在web.config制定一种Theme:,ocean文件夹下的View将会被优先采用:



    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多