改善 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/
相關文章
- 讀改善c#程式碼157個建議:建議1~3C#
- 讀改善c#程式碼157個建議:建議4~6C#
- 讀改善c#程式碼157個建議:建議7~9C#
- 讀改善c#程式碼157個建議:建議10~12C#
- 讀改善c#程式碼157個建議:建議13~15C#
- Flutter 6 個建議改善你的程式碼結構Flutter
- Vue中拆分檢視層程式碼的5點建議Vue
- 改善 Python 程式的 91 個建議Python
- 編寫高質量程式碼:改善Java程式的151個建議(第5章:陣列和集合___建議60~64)Java陣列
- 編寫高質量程式碼:改善Java程式的151個建議(第5章:陣列和集合___建議65~69)Java陣列
- 編寫高質量程式碼:改善Java程式的151個建議(第5章:陣列和集合___建議70~74)Java陣列
- 編寫高質量程式碼:改善Java程式的151個建議(第5章:陣列和集合___建議75~78)Java陣列
- 編寫高質量程式碼:改善Java程式的151個建議(第5章:陣列和集合___建議79~82)Java陣列
- 改善軟體發行管理的七點建議
- 改善 Python 程式的 91 個建議(一)Python
- 改善 Python 程式的 91 個建議(二)Python
- 改善 Python 程式的 91 個建議(三)Python
- 改善 Python 程式的 91 個建議(四)Python
- 改善 Python 程式的 91 個建議(六)Python
- 編寫高質量程式碼 改善Python程式的91個建議Python
- ASP.NET程式的優化建議資料庫操作ASP.NET優化資料庫
- 每天寫出好程式碼的 5 個建議
- 編寫高質量程式碼:改善Java程式的151個建議(第4章:字串___建議52~55)Java字串
- 編寫高質量程式碼:改善Java程式的151個建議(第4章:字串___建議56~59)Java字串
- [譯] 程式碼評審的 8 點建議
- ASP.NET MVC5 知識點整理ASP.NETMVC
- 編寫高質量程式碼:改善Java程式的151個建議(第1章:JAVA開發中通用的方法和準則___建議1~5)Java
- 《編寫高質量程式碼:改善Java程式的151個建議》筆記Java筆記
- 編寫高質量程式碼:改善Java程式的151個建議(第2章:基本型別___建議21~25)Java型別
- 編寫高質量程式碼:改善Java程式的151個建議(第2章:基本型別___建議26~30)Java型別
- 關於程式碼審查的幾點建議
- 編寫高質量程式碼:改善Java程式的151個建議(第7章:泛型和反射___建議93~97)Java泛型反射
- 編寫高質量程式碼:改善Java程式的151個建議(第3章:類、物件及方法___建議41~46)Java物件
- 編寫高質量程式碼:改善Java程式的151個建議(第3章:類、物件及方法___建議47~51)Java物件
- 編寫高質量程式碼:改善Java程式的151個建議(第3章:類、物件及方法___建議31~35)Java物件
- 編寫高質量程式碼:改善Java程式的151個建議(第3章:類、物件及方法___建議36~40)Java物件
- 《改善python程式的91個建議》讀書筆記Python筆記
- 改善Java文件的理由、建議和技巧Java