改善 ASP.NET MVC 代碼庫的 5 點(diǎn)建議

MVC,建議

剛剛檢查完支持工單中的一些代碼,筆者想針對 ASP.NET MVC 應(yīng)用的改進(jìn)寫一些建議怪瓶。這些內(nèi)容仍在筆者腦海中,愿與各位一同分享践美。若你已使用 MVC 一段時間洗贰,那么以下內(nèi)容可能并不新鮮。本文更適用于不常使用 MVC 或尚未充分了解 MVC 的讀者拨脉。

假設(shè)以下場景:你想弄清楚一個網(wǎng)絡(luò)應(yīng)用在生產(chǎn)環(huán)境下為何消耗了 Web 服務(wù)器2GB 內(nèi)存哆姻,于是,你將生產(chǎn)環(huán)境中運(yùn)行的應(yīng)用版本部署到本地運(yùn)行玫膀,用于分析和調(diào)試矛缨。

仔細(xì)查看代碼后,你認(rèn)真地分析帖旨,可能還時不時搖搖頭箕昭,最終弄清了問題的本質(zhì),那么此時解阅,你應(yīng)該給出反饋了落竹。

這就是筆者今天的經(jīng)歷,從中總結(jié)出5點(diǎn)建議货抄,希望能使讀者在使用 ASP.NET MVC 代碼時更加得心應(yīng)手述召。

1、了解問題范疇內(nèi)的查詢

筆者收到的支持工單蟹地,其根本原因在于积暖,從數(shù)據(jù)庫中提取了大量數(shù)據(jù),導(dǎo)致占用了過量內(nèi)存怪与。

這一問題十分常見夺刑。假如你建立了一個普通的博客,其中包含了文章以及多種媒體(圖片分别、視頻遍愿、附件)。你將一個 Media 數(shù)組放到 Post 域?qū)ο笾性耪叮笳邔⑺袌D片數(shù)據(jù)儲存在一個字節(jié)數(shù)組中沼填。由于你使用了 ORM,因此需要采用某種方法將域模型設(shè)計(jì)完善括授;我們都經(jīng)歷過這一步倾哺。

public class BlogPost {
  
  public ICollection<BlogMedia> Media { get; set; }

}

public class BlogMedia {
  
  public byte[] Data { get; set; }

  public string Name { get; set; }

}

這種設(shè)計(jì)并沒有大的不妥轧邪,你很準(zhǔn)確地建立了域模型。但問題在于羞海,當(dāng)你通過最常用的 ORM 發(fā)起查詢時,所有與博客文章相關(guān)的數(shù)據(jù)都會被加載出來曲管。

public IList<BlogPost> GetNewestPosts(int take) {
  return _db.BlogPosts.OrderByDescending(p => p.PostDate).Take(take).ToList();
}

這一行看起來毫無問題(除非你曾受其困擾却邓,所以了解它并非無害),但如果不取消延遲加載或沒讓 ORM 忽略日志媒體上的大「Data」屬性院水,那么就可能導(dǎo)致非常嚴(yán)重的后果腊徙。

你應(yīng)當(dāng)了解 ORM 是如何進(jìn)行查詢和映射對象的,并確保所查詢內(nèi)容就是需要的內(nèi)容(比如使用 projection)檬某,這一點(diǎn)十分重要撬腾。

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();
}

這能確保只抓取任務(wù)真正需要的數(shù)據(jù)量。如果你要做的僅僅是使用標(biāo)題和 ID 在主頁上建立一個鏈接恢恼,那么得到這倆屬性就夠了民傻。

你可以在知識庫中準(zhǔn)備5種以上的方法,為使用戶界面更加完善场斑,再仔細(xì)也不為過漓踢。

2、不要從視圖中調(diào)用知識庫

這一條比較難注意到漏隐。設(shè)想 MVC 視圖中的以下代碼:

@foreach(var post in Model.RelatedPosts) {
  ...
}

看起來沒什么問題喧半,但如果仔細(xì)看看這一模型屬性中隱含的內(nèi)容呢?

public class MyViewModel {
  
  public IList<BlogPost> RelatedPosts {
      get { return new BlogRepository().GetRelatedPosts(this.Tags); }
  }

}

呀青责!「視圖模型」中含有業(yè)務(wù)邏輯挺据,此外還直接調(diào)用了一個數(shù)據(jù)存取方法。如此一來脖隶,數(shù)據(jù)存取代碼被引入了陌生的區(qū)域扁耐,并隱藏在屬性中。將此代碼移動到控制器中浩村,便于對其進(jìn)行討論并有意識地為視圖模型添加內(nèi)容做葵。

此處正好說明一下,適當(dāng)?shù)膯卧獪y試可幫助發(fā)現(xiàn)此類問題心墅;由于肯定不能攔截對這此類方法的調(diào)用酿矢,你可能會恍然大悟,不該將知識庫注入視圖模型中怎燥。

3瘫筐、充分利用局部模塊和子動作

如需在視圖中執(zhí)行業(yè)務(wù)邏輯,那就應(yīng)重新考慮視圖模型和邏輯铐姚。不建議在 MVC Razor 視圖中執(zhí)行此類操作策肝。

@{
  var blogController = new BlogController();
}

<ul>
@foreach(var tag in blogController.GetTagsForPost(p.Id)) {
  <li>@tag.Name</li>
}
</ul>

切勿在視圖中使用業(yè)務(wù)邏輯肛捍,但除此之外,你可以創(chuàng)建一個控制器之众!將業(yè)務(wù)邏輯移動到動作方法中拙毫,并將視圖模型用于原本的用途。還可以將業(yè)務(wù)邏輯移動到單獨(dú)的動作方法中棺禾,這一動作方法僅在視圖內(nèi)被調(diào)用缀蹄,這樣就可在必要時單獨(dú)對其進(jìn)行緩存。

//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中提到:

任何一個標(biāo)有 「ChildActionOnlyAttribute」的方法都只能與 「Action」或「RenderAction」HTML 擴(kuò)展方法一同被調(diào)用缺前。

這就意味著,沒有人能通過操作 URL 來訪問你的子動作(如果你采用了默認(rèn)路徑)悬襟。

在 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);
}

什么效果也沒有!由于是通過視圖中的控制器變量和視圖模型中的屬性進(jìn)入數(shù)據(jù)層棺牧,因此并不能提升性能……緩存視圖模型并沒有什么用處巫糙。

試試緩存 MVC 動作的輸出吧:

[OutputCache(Duration=2000)]
public ActionResult Index() {
  var homepageViewModel = new HomepageViewModel();

  homepageViewModel.RecentPosts = _blogRepository.GetNewestPosts(5);

  return View(homepageViewModel);
}

請注意非常好用的「OutputCache」屬性。MVC 支持 ASP.NET 輸出緩存颊乘,因此請?jiān)谶m當(dāng)情況下参淹,充分利用這一特點(diǎn)。如需緩存模型乏悄,那么模型基本上應(yīng)為帶自動(且只讀)屬性的 POCO浙值,不能調(diào)用其他知識庫方法。

另外還想介紹筆者尚未嘗試的一個好方法檩小,即采用不同的輸出緩存供應(yīng)商开呐,從而在AppFabric、NoSQL 或其他任何需要的地方進(jìn)行緩存规求。MVC 的可擴(kuò)展性非常強(qiáng)筐付。

5、大膽使用 ORM

如果不好好利用 ORM 的特征集阻肿,那真是極大的損失瓦戚。筆者所檢查的代碼庫中用到了 NHibernate,但是并未真正利用好丛塌。本可以用來解決一部分內(nèi)存問題的 NHibernate 高級射影功能完全被忽略了较解。這一問題有時是因?yàn)槭褂谩皫炷J健彼斐傻慕┗季S畜疾,有時則是由于缺乏必要的知識。

與僅僅使用基本的類方法相比印衔,通過利用 EF 或 NHibernate 特征啡捶,知識庫的功能可以大大增加。它們可以在控制器中形成和返回你真正想要的數(shù)據(jù)奸焙,大大增強(qiáng)控制器的邏輯性届慈。趕緊閱讀 ORM 文件,了解一下它可以提供的功能吧忿偷,這將使你受益良多。

筆者認(rèn)為臊泌,采用知識庫模式鲤桥,就好比驅(qū)除掉霧霾,使明媚的陽光從 ORM 窗口照進(jìn)來渠概。剛接觸 RavenDB 時茶凳,筆者丟棄了知識庫層(實(shí)際上是整個數(shù)據(jù)項(xiàng)目),在應(yīng)用服務(wù)層中完全使用 Raven 查詢播揪,用了一點(diǎn)點(diǎn)擴(kuò)展方法來重復(fù)使用查詢邏輯贮喧。筆者發(fā)現(xiàn),許多邏輯都明顯依賴于特定的上下文猪狈,且利用 Raven 的擴(kuò)展特性進(jìn)行投射箱沦、形成并分批處理查詢,大有益處雇庙。

那只是你一家之言……

如果你認(rèn)為可以將 ORM 抽象化谓形,筆者強(qiáng)烈建議你換個角度思考。ORM 確實(shí)是抽象概念疆前,如果你認(rèn)為寒跳,由于 ORM 是「抽象」的,所以輕而易舉就能用別的 ORM 置換現(xiàn)有的 ORM竹椒,那么事實(shí)會讓你大吃一驚童太。因?yàn)槲抑耙彩沁@么想的,直到我了解到胸完,轉(zhuǎn)換至 Raven 簡直改變了我整個代碼庫书释,這是我完全沒有預(yù)料到的。ORM 不僅僅影響到數(shù)據(jù)存取舶吗,還會影響域以及業(yè)務(wù)邏輯征冷,甚至?xí)绊懹脩艚缑妗Mㄟ^移除知識庫抽象誓琼,可以切實(shí)降低數(shù)據(jù)存取代碼的整體復(fù)雜度检激。

「常識并非人人皆知」

家父常常拿這句話提醒我肴捉。有時候,通過仔細(xì)查閱代碼叔收,也會發(fā)現(xiàn)你認(rèn)為人人皆知的道理齿穗,事實(shí)并非如此;你可能從實(shí)踐經(jīng)驗(yàn)中了解到這一點(diǎn)饺律,或在 google 上讀到這一點(diǎn)窃页,就錯誤地假設(shè)這是人人都知道的事實(shí)了。

希望這篇文章能幫到需要的人复濒!

OneAPM 助您輕松鎖定 .NET 應(yīng)用性能瓶頸脖卖,通過強(qiáng)大的 Trace 記錄逐層分析,直至鎖定行級問題代碼巧颈。以用戶角度展示系統(tǒng)響應(yīng)速度畦木,以地域和瀏覽器維度統(tǒng)計(jì)用戶使用情況。想閱讀更多技術(shù)文章砸泛,請?jiān)L問 OneAPM 官方博客十籍。

本文轉(zhuǎn)自 OneAPM 官方博客

原文地址:http://kamranicus.com/blog/2014/01/29/5-tips-to-improve-your-mvc-site/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市唇礁,隨后出現(xiàn)的幾起案子勾栗,更是在濱河造成了極大的恐慌,老刑警劉巖盏筐,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件围俘,死亡現(xiàn)場離奇詭異,居然都是意外死亡机断,警方通過查閱死者的電腦和手機(jī)楷拳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吏奸,“玉大人欢揖,你說我怎么就攤上這事》芪担” “怎么了她混?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長泊碑。 經(jīng)常有香客問我坤按,道長,這世上最難降的妖魔是什么馒过? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任臭脓,我火速辦了婚禮,結(jié)果婚禮上腹忽,老公的妹妹穿的比我還像新娘来累。我一直安慰自己砚作,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布嘹锁。 她就那樣靜靜地躺著葫录,像睡著了一般。 火紅的嫁衣襯著肌膚如雪领猾。 梳的紋絲不亂的頭發(fā)上米同,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機(jī)與錄音摔竿,去河邊找鬼面粮。 笑死,一個胖子當(dāng)著我的面吹牛继低,可吹牛的內(nèi)容都是我干的但金。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼郁季,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了钱磅?” 一聲冷哼從身側(cè)響起梦裂,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎盖淡,沒想到半個月后年柠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡褪迟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年冗恨,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片味赃。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡掀抹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出心俗,到底是詐尸還是另有隱情傲武,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布城榛,位于F島的核電站揪利,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏狠持。R本人自食惡果不足惜疟位,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望喘垂。 院中可真熱鬧甜刻,春花似錦绍撞、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至尿招,卻和暖如春矾柜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背就谜。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工怪蔑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人丧荐。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓缆瓣,卻偏偏與公主長得像,于是被迫代替她去往敵國和親虹统。 傳聞我的和親對象是個殘疾皇子弓坞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,506評論 25 707
  • 不管你有多不開心 我們都有責(zé)任先吃好一頓飯睡好一個覺打扮好自己 很多煩惱其實(shí)都沒什么大不了 只是你在那個情境下在那...
    哀慕熙榮閱讀 185評論 0 1
  • 1.在積累財(cái)富的過程中,最困難的事情莫過于堅(jiān)持自己等待選擇而不盲目從眾车荔。因?yàn)樵诟偁幖ち业氖袌錾隙啥常后w往往會反應(yīng)遲鈍...
    yokohu閱讀 198評論 0 0