改善 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/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,424评论 25 707
  • 不管你有多不开心 我们都有责任先吃好一顿饭睡好一个觉打扮好自己 很多烦恼其实都没什么大不了 只是你在那个情境下在那...
    哀慕熙荣阅读 185评论 0 1
  • 1.在积累财富的过程中,最困难的事情莫过于坚持自己等待选择而不盲目从众。因为在竞争激烈的市场上,群体往往会反应迟钝...
    yokohu阅读 198评论 0 0