Asp.net MVC2 使用經驗,性能優化建議
這個月一直在用 asp.net MVC2 做http://www.86e0.com/t 這個網站,用的時候是 aps.net MVC2 RC2,然后現在Asp.net MVC2正式版已經是發布了。 在MVC的使用上,有一些心得。下面作一下總結,希望對大家有用,也歡迎大家討論。
1.關于緩存
緩存上,數據層上的緩存是必須的,這點不必多說了。
另一個很重要的是:視圖片段緩存。
我參考了老趙的寫的三篇關于片段緩存的文章:
- 適合ASP.NET MVC的視圖片斷緩存方式(上):起步
- 適合ASP.NET MVC的視圖片斷緩存方式(中):更實用的API
- 適合ASP.NET MVC的視圖片斷緩存方式(下):頁面輸出原則
本想用老趙的了,但是我發現Asp.net MVC2 的有一個新功能:Html.Partial可以返回生成的HTML,返回的類型是:MvcHtmlString. 雖然要利用PartialView才能生成Html片段,但是我想這個已經夠我用的了,所以我做了一個這樣一個Helper,主要是將生成的HTML片段緩存到Memcached里。代碼如下:
{
public static MvcHtmlString MemcacheHtmlPartial(this HtmlHelper htmlHelper,int duration, string partialViewName, object model, ViewDataDictionary viewData)
{
object obaear = htmlHelper.ViewContext.RouteData.DataTokens["area"];
string area=string.Empty;
if (obaear != null) area = obaear.ToString();
string key = string.Format("MemcacheHtmlPartial_{0}{1}", area, partialViewName);
object ob = DistCache.Get(key);
if (ob == null)
{
MvcHtmlString mstr = htmlHelper.Partial(partialViewName, model, viewData);
DistCache.Add(key, mstr.ToString(), TimeSpan.FromSeconds(duration));
return mstr;
}
else
{
return MvcHtmlString.Create((string)ob);
}
}
}
然后,我覺得,這樣,在每次請求時,還是要在Controller 里把數據取出來,然后再傳到 Partial View里。 既然已經緩存了,就應該不用每次請求都要在Controller里把數據取出來才對!雖然數據層會有緩存。
所以我,能不能再省下去Controller取數據的消耗,于是又有了以下代碼,其功能是:緩存Action生成的HTML到Memcached里。
{
object obaear = htmlHelper.ViewContext.RouteData.DataTokens["area"];
string area = string.Empty;
if (obaear != null) area = obaear.ToString();
string key = string.Format("MemcacheHtmlRenderAction_{0}{1}{2}", area, controllerName,actionName);
object ob = DistCache.Get(key);
if (ob == null)
{
// htmlHelper.RenderAction(actionName, controllerName, routeValues);
StringWriter writer = new StringWriter(CultureInfo.CurrentCulture);
ActionHelper(htmlHelper, actionName, controllerName, routeValues, writer);
string wStr = writer.ToString();
DistCache.Add(key, wStr,TimeSpan.FromSeconds(duration));
MvcHtmlString mstr = MvcHtmlString.Create(wStr);
return mstr;
}
else { return MvcHtmlString.Create((string)ob); }
}
說明一下,Actionhelper的方法是在MVC原代碼里提取出來的。 因為MVC2里的 Html.RenderAction方法并沒有返回 MvcHtmlString的重載版。那位有更好的方法?
其實,MVC里的Action有輸出緩存,所以直接在View里用 Html.RenderAction都可以解決很多問題了。這個主要是可以用程序管理緩存。
2.關于靜態內容的放置
習慣上,靜態內容會放在 mvc程序所在的目錄下,比如說js,css,上傳的圖片等。但是這樣的話,所有的靜態請求都要經過 aspnet_isapi 處理,這樣是非常不合算的。所以靜態內容一般都會放在另外的子域上。http://www.86e0.com/t 是放在 cdn.86e0.com上。
3.關于強類型ViewModel
我基本上看了老趙的Asp.net MVC最佳實踐。其中有一點,就是強烈推薦使用強類型的ViewModel.我試了一些頁面,發現用強類型的ViewModel,現階段并不適用于我。因為我是用NbearLite,從數據庫抓出來的大多是DataTable.我是覺得DataTable+NbearLite蠻方便的,雖然沒有動態語言的數據訪問來得方便,但是比用Entity,ViewModel,DTO,等等來說,還是可以省下很多代碼。然后,最重要的是,由于我這種站經常會修改,所以數據庫改變,加字段,減字段是很經常性的事。但是,用NbearLite + DataSet,DataTable,卻非常方便。
所以我覺得,做Asp.net MVC,如果你不是用DDD,DDT的話,用DataTable還是可以的。因為DDD,DDT學習起來還是要點成本的。
4.關于URL生成
URL生成, 老趙寫了一系列文章:
- 各種URL生成方式的性能對比
- 各種URL生成方式的性能對比(結論及分析)
- 為URL生成設計流暢接口(Fluent Interface)
- URL生成方式性能優化結果
我直接選擇:Raw方式了, 速度最快的,才是適合我的。呵。 而不是強類型的才是適合我的。
最后,分享一個很實用的Asp.net MVC 分頁Helper.
這個Helper引自重典老大的blog:http://www.cnblogs.com/chsword/ . 我在之前做了少少修改,現已經在http://www.86e0.com/t 上使用了。
效果如下:
請大家注意生成的 URL, 是用 ?參數=頁碼 的方式。代碼如下:
/// 分頁Pager顯示
/// </summary>
/// <param name="html"></param>
/// <param name="currentPageStr">標識當前頁碼的QueryStringKey</param>
/// <param name="pageSize">每頁顯示</param>
/// <param name="totalCount">總數據量</param>
/// <returns></returns>
public static string Pager(this HtmlHelper html, string currentPageStr, int pageSize, int totalCount)
{
var queryString = html.ViewContext.HttpContext.Request.QueryString;
int currentPage = 1; //當前頁
if(!int.TryParse(queryString[currentPageStr], out currentPage)) currentPage = 1; //與相應的QueryString綁定
var totalPages = Math.Max((totalCount + pageSize - 1) / pageSize, 1); //總頁數
var dict = new RouteValueDictionary(html.ViewContext.RouteData.Values);
var output
= new StringBuilder();
foreach (string key in queryString.Keys)
if (queryString[key] != null && !string.IsNullOrEmpty(key))
dict[key] = queryString[key];
if (totalPages > 1)
{
if (currentPage != 1)
{//處理首頁連接
dict[currentPageStr] = 1;
output.AppendFormat("<span class=\"p_home\">{0}</span>", html.RouteLink("首頁", dict));
}
if (currentPage > 1)
{//處理上一頁的連接
dict[currentPageStr] = currentPage - 1;
output.AppendFormat("<span class=\"p_up\">{0}</span>", html.RouteLink("上一頁", dict));
}
else
{
output.AppendFormat("<span class=\"p_disable\">{0}</span>","上一頁");
}
int currint = 5;
for (int i = 0; i <= 10; i++)
{//一共最多顯示10個頁碼,前面5個,后面5個
if ((currentPage + i - currint) >= 1 && (currentPage + i - currint) <= totalPages)
if (currint == i)
{//當前頁處理
output.Append(string.Format("<span class=\"p_current\">{0}</span>", currentPage));
}
else
{//一般頁處理
dict[currentPageStr] = currentPage + i - currint;
output.AppendFormat("<span class=\"p_num\">{0}</span>",html.RouteLink((currentPage + i - currint).ToString(), dict));
}
}
if (currentPage < totalPages)
{//處理下一頁的鏈接
dict[currentPageStr] = currentPage + 1;
output.AppendFormat("<span class=\"p_down\">{0}</span>", html.RouteLink("下一頁", dict));
}
else
{
output.AppendFormat("<span class=\"p_disable\">{0}</span>", "下一頁");
}
if (currentPage != totalPages)
{
dict[currentPageStr] = totalPages;
output.AppendFormat("<span class=\"p_last\">{0}</span>",html.RouteLink("末頁", dict));
}
}
output.AppendFormat("<span class=\"p_count\">第{0}頁/共{1}頁</span>", currentPage, totalPages);//這個統計加不加都行
return output.ToString();
}
另: http://www.86e0.com/t 是做淘寶客類應用的。園子里還有誰在做淘寶客類網站么? 有的話多交流。^_^