改善 ASP.NET MVC 代码库的 5 点建议
MVC,建议
刚刚检查完支持工单中的一些代码,笔者想针对 ASP.NET MVC 应用的改进写一些建议。这些内容仍在笔者脑海中,愿与各位一同分享。若你已使用 MVC 一段时间,那么以下内容可能并不新鲜。本文更适用于不常使用 MVC 或尚未充分了解 MVC 的读者。
假设以下场景:你想弄清楚一个网络应用在生产环境下为何消耗了 Web 服务器2GB 内存,于是,你将生产环境中运行的应用版本部署到本地运行,用于分析和调试。
仔细查看代码后,你认真地分析,可能还时不时摇摇头,最终弄清了问题的本质,那么此时,你应该给出反馈了。
这就是笔者今天的经历,从中总结出5点建议,希望能使读者在使用 ASP.NET MVC 代码时更加得心应手。
1、了解问题范畴内的查询
笔者收到的支持工单,其根本原因在于,从数据库中提取了大量数据,导致占用了过量内存。
这一问题十分常见。假如你建立了一个普通的博客,其中包含了文章以及多种媒体(图片、视频、附件)。你将一个 Media 数组放到 Post 域对象中,后者将所有图片数据储存在一个字节数组中。由于你使用了 ORM,因此需要采用某种方法将域模型设计完善;我们都经历过这一步。
public class BlogPost {
public ICollection<BlogMedia> Media { get; set; }
}
public class BlogMedia {
public byte[] Data { get; set; }
public string Name { get; set; }
}
这种设计并没有大的不妥,你很准确地建立了域模型。但问题在于,当你通过最常用的 ORM 发起查询时,所有与博客文章相关的数据都会被加载出来。
public IList<BlogPost> GetNewestPosts(int take) {
return _db.BlogPosts.OrderByDescending(p => p.PostDate).Take(take).ToList();
}
这一行看起来毫无问题(除非你曾受其困扰,所以了解它并非无害),但如果不取消延迟加载或没让 ORM 忽略日志媒体上的大「Data」属性,那么就可能导致非常严重的后果。
你应当了解 ORM 是如何进行查询和映射对象的,并确保所查询内容就是需要的内容(比如使用 projection),这一点十分重要。
public IList<PostSummary> GetNewestPosts(int take) {
return _db.BlogPosts.OrderByDescending(p => p.PostDate).Take(take).Select(p => new PostSummary() {
Title = p.Title,
Id = p.Id
}).ToList();
}
这能确保只抓取任务真正需要的数据量。如果你要做的仅仅是使用标题和 ID 在主页上建立一个链接,那么得到这俩属性就够了。
你可以在知识库中准备5种以上的方法,为使用户界面更加完善,再仔细也不为过。
2、不要从视图中调用知识库
这一条比较难注意到。设想 MVC 视图中的以下代码:
@foreach(var post in Model.RelatedPosts) {
...
}
看起来没什么问题,但如果仔细看看这一模型属性中隐含的内容呢?
public class MyViewModel {
public IList<BlogPost> RelatedPosts {
get { return new BlogRepository().GetRelatedPosts(this.Tags); }
}
}
呀!「视图模型」中含有业务逻辑,此外还直接调用了一个数据存取方法。如此一来,数据存取代码被引入了陌生的区域,并隐藏在属性中。将此代码移动到控制器中,便于对其进行讨论并有意识地为视图模型添加内容。
此处正好说明一下,适当的单元测试可帮助发现此类问题;由于肯定不能拦截对这此类方法的调用,你可能会恍然大悟,不该将知识库注入视图模型中。
3、充分利用局部模块和子动作
如需在视图中执行业务逻辑,那就应重新考虑视图模型和逻辑。不建议在 MVC Razor 视图中执行此类操作。
@{
var blogController = new BlogController();
}
<ul>
@foreach(var tag in blogController.GetTagsForPost(p.Id)) {
<li>@tag.Name</li>
}
</ul>
切勿在视图中使用业务逻辑,但除此之外,你可以创建一个控制器!将业务逻辑移动到动作方法中,并将视图模型用于原本的用途。还可以将业务逻辑移动到单独的动作方法中,这一动作方法仅在视图内被调用,这样就可在必要时单独对其进行缓存。
//In the controller:
[ChildActionOnly]
[OutputCache(Duration=2000)]
public ActionResult TagsForPost(int postId) {
return View();
}
//In the view:
@{Html.RenderAction("TagsForPost", new { postId = p.Id });}
注意 「ChildActionOnly」 属性。MSDN中提到:
任何一个标有 「ChildActionOnlyAttribute」的方法都只能与 「Action」或「RenderAction」HTML 扩展方法一同被调用。
这就意味着,没有人能通过操作 URL 来访问你的子动作(如果你采用了默认路径)。
在 MVC 库中,局部模块和子动作都是很有用的工具,所以充分利用起来吧!
4、缓存重要的东西
有了以上的代码做铺垫,如果只缓存视图模型,又会有怎样的效果呢?
public ActionResult Index() {
var homepageViewModel = HttpContext.Current.Cache["homepageModel"] as HomepageViewModel;
if (homepageViewModel == null) {
homepageViewModel = new HomepageViewModel();
homepageViewModel.RecentPosts = _blogRepository.GetNewestPosts(5);
HttpContext.Current.Cache.Add("homepageModel", homepageViewModel, ...);
}
return View(homepageViewModel);
}
什么效果也没有!由于是通过视图中的控制器变量和视图模型中的属性进入数据层,因此并不能提升性能……缓存视图模型并没有什么用处。
试试缓存 MVC 动作的输出吧:
[OutputCache(Duration=2000)]
public ActionResult Index() {
var homepageViewModel = new HomepageViewModel();
homepageViewModel.RecentPosts = _blogRepository.GetNewestPosts(5);
return View(homepageViewModel);
}
请注意非常好用的「OutputCache」属性。MVC 支持 ASP.NET 输出缓存,因此请在适当情况下,充分利用这一特点。如需缓存模型,那么模型基本上应为带自动(且只读)属性的 POCO,不能调用其他知识库方法。
另外还想介绍笔者尚未尝试的一个好方法,即采用不同的输出缓存供应商,从而在AppFabric、NoSQL 或其他任何需要的地方进行缓存。MVC 的可扩展性非常强。
5、大胆使用 ORM
如果不好好利用 ORM 的特征集,那真是极大的损失。笔者所检查的代码库中用到了 NHibernate,但是并未真正利用好。本可以用来解决一部分内存问题的 NHibernate 高级射影功能完全被忽略了。这一问题有时是因为使用“库模式”所造成的僵化思维,有时则是由于缺乏必要的知识。
与仅仅使用基本的类方法相比,通过利用 EF 或 NHibernate 特征,知识库的功能可以大大增加。它们可以在控制器中形成和返回你真正想要的数据,大大增强控制器的逻辑性。赶紧阅读 ORM 文件,了解一下它可以提供的功能吧,这将使你受益良多。
笔者认为,采用知识库模式,就好比驱除掉雾霾,使明媚的阳光从 ORM 窗口照进来。刚接触 RavenDB 时,笔者丢弃了知识库层(实际上是整个数据项目),在应用服务层中完全使用 Raven 查询,用了一点点扩展方法来重复使用查询逻辑。笔者发现,许多逻辑都明显依赖于特定的上下文,且利用 Raven 的扩展特性进行投射、形成并分批处理查询,大有益处。
那只是你一家之言……
如果你认为可以将 ORM 抽象化,笔者强烈建议你换个角度思考。ORM _确实是_抽象概念,如果你认为,由于 ORM 是「抽象」的,所以轻而易举就能用别的 ORM 置换现有的 ORM,那么事实会让你大吃一惊。因为我之前也是这么想的,直到我了解到,转换至 Raven 简直改变了我整个代码库,这是我完全没有预料到的。ORM 不仅仅影响到数据存取,还会影响域以及业务逻辑,甚至会影响用户界面。通过移除知识库抽象,可以切实降低数据存取代码的整体复杂度。
「常识并非人人皆知」
家父常常拿这句话提醒我。有时候,通过仔细查阅代码,也会发现你认为人人皆知的道理,事实并非如此;你可能从实践经验中了解到这一点,或在 google 上读到这一点,就错误地假设这是人人都知道的事实了。
希望这篇文章能帮到需要的人!
OneAPM 助您轻松锁定 .NET 应用性能瓶颈,通过强大的 Trace 记录逐层分析,直至锁定行级问题代码。以用户角度展示系统响应速度,以地域和浏览器维度统计用户使用情况。想阅读更多技术文章,请访问 OneAPM 官方博客。
本文转自 OneAPM 官方博客
原文地址:http://kamranicus.com/blog/2014/01/29/5-tips-to-improve-your-mvc-site/
改善 ASP.NET MVC 代码库的 5 点建议的更多相关文章
- 通过扩展改善ASP.NET MVC的验证机制[实现篇]
原文:通过扩展改善ASP.NET MVC的验证机制[实现篇] 在<使用篇>中我们谈到扩展的验证编程方式,并且演示了本解决方案的三大特性:消息提供机制的分离.多语言的支持和多验证规则的支持, ...
- 通过扩展改善ASP.NET MVC的验证机制[使用篇]
原文:通过扩展改善ASP.NET MVC的验证机制[使用篇] ASP.NET MVC提供一种基于元数据的验证方式是我们可以将相应的验证特性应用到作为Model实体的类型或者属性/字段上,但是这依然具有 ...
- ASP.NET MVC扩展库
很多同学都读过这篇文章吧 ASP.NET MVC中你必须知道的13个扩展点,今天给大家介绍一个ASP.NET MVC的扩展库,主要就是针对这些扩展点进行.这个项目的核心是IOC容器,包括Ninject ...
- ASP.NET MVC模型绑定的6个建议(转载)
ASP.NET MVC模型绑定的6个建议 发表于2011-08-03 10:25| 来源博客园| 31 条评论| 作者冠军 validationasp.netmvc.netasp 摘要:ASP.NET ...
- 生成Nuget 源代码包来重用你的Asp.net MVC代码
ASP.NET 开发人员有时会陷入一种困境:想要重用以前写过的东西,如一些具有完整功能的Web页面+后台逻辑, 往往不那么直接了当,因此很不爽.经常采用的方式是:找到以前写过的项目,从中挑出来一些有用 ...
- ASP.NET MVC 5 02 - ASP.NET MVC 1-5 各版本特点
参考书籍:<ASP.NET MVC 4 高级编程>.<ASP.NET MVC 5 高级编程>.<C#高级编程(第8版)>.<使用ASP.NET MVC开发企业 ...
- ASP.Net请求处理机制初步探索之旅 - Part 5 ASP.Net MVC请求处理流程
好听的歌 我一直觉得看一篇文章再听一首好听的歌,真是种享受.于是,我在这里嵌入一首好听的歌,当然你觉得不想听的话可以点击停止,歌曲 from 王菲 <梦中人>: --> 开篇:上一篇 ...
- Response.End()在Webform和ASP.NET MVC下的表现差异
前几天在博问中看到一个问题--Response.End()后,是否停止执行?MVC与WebForm不一致.看到LZ的描述后,虽然奇怪于为何用Response.End()而不用return方式去控制流程 ...
- ASP.Net MVC请求处理流程
ASP.Net MVC请求处理流程 好听的歌 我一直觉得看一篇文章再听一首好听的歌,真是种享受.于是,我在这里嵌入一首好听的歌,当然你觉得不想听的话可以点击停止,歌曲 from 王菲 <梦中人& ...
随机推荐
- LPC2378-Jlink 能下载程序,但是调试出现各种奇怪问题
LPC2378-Jlink调试经验 1.Jlink(d版的没有关系)不能下载程序? 把下载速率设置到500Khz及以下的速率. 2.Jlink能在500Khz的速率下载程序,但是不能调试?添加 ...
- CHENEY-YANG'S BLOG(cheney-yang)
This is Cheney-Yang's blog ! Welcome to here ! This is Cheney-Yang's blog ! Welcome to here ! This i ...
- Java基础知识强化之网络编程笔记01:InetAddress类的概述和使用
1. InetAddress类 InetAddress是Java对IP地址的封装,在java.net中有许多类都使用到了InetAddress,包括ServerSocket,Socket,Datagr ...
- Android(java)学习笔记181:利用Service在后台播放背景音乐
1.在android应用程序里,有一种没有UI的类(android.app.Service)——Service.简单来说,Service是一个 background process(背景程序),通过背 ...
- python 验证码
python写的验证码小程序 ##################验证码,数字+字母 import random check_code="" for i in range(6): ...
- Linux文件查找命令find用法整理(locate/find)
Linux文件查找查找主要包括:locate和find 1.locate 用法简单,根据数据库查找,非实时,用法: locate FILENAME 手动更新数据库(时间可能较长) updatedb 2 ...
- 基于.net 职责链来实现 插件模式
插件式的例子 QQ电脑管家,有很多工具列表,点一下工具下载后就可以开始使用了 eclipse ,X Server 等等 插件式的好处 插件降低框架的复杂性,把扩展功能从框架中剥离出来 让第三方有机会来 ...
- C# DbHelperSQLP,操作不同的数据库帮助类 (转载)
本类主要是用来访问不同数据库而编写的主要功能如下 .数据访问基础类(基于不同数据库),主要是用来访问不同数据库的. .得到最大值:是否存在:是否存在: . 执行SQL和Orace语句,返回影响的记录数 ...
- 初学HTML5系列三:事件
Window 事件属性 针对 window 对象触发的事件(应用到 <body> 标签): 属性 值 描述 onafterprint script 文档打印之后运行的脚本. onbefor ...
- CSS3弹性盒模型flex box快速入门 2016.03.16
@import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/c ...