改善 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 官方博客。
原文地址: http://kamranicus.com/blog/2014/01/29/5-tips-to-improve-your-mvc-site/