問題
今天很多網站與資料庫互動。如果您的網站接收大量的流量,SQL查詢來檢索資料,是相當激烈的。更重要的是因為普通使用者點擊一個在15秒內到達您的網站的連結, 檢索和產生內容的工作可能是不必要的,尤其是當內容是“地區摺疊”(不滾動是不可見的)。為了協助解決這個問題,內容將被“按需”載入。足夠的內容將被載入,使頁面感覺起來是隨使用者向下滾動閱讀遞增的,更多的內容將在不影響使用者體驗的情境下填充。
解決方案
使用非同步controller與jQuery按需載入內容,當使用者開始通過網站的內容滾動時進一步載入內容。
討論
非同步controller可能在許多MVC應用中未充分利用。最有可能的是因為人們不知道他們,更重要的是,不知道何時使用它們。以下是摘錄見MSDN網站:
“在應用中,線程饑餓可能會發生,您可以配置action 非同步處理。非同步請求和同步請求過程需要相同的時間例如,如果一個請求,使得網路調用需要兩秒鐘來完成的,請求需要兩秒鐘,無論是執行同步或非同步。然而,在一個非同步呼叫,當伺服器響應等待它的第一次請求時完成時他響應其他的請求沒有被阻塞。因此,當有許多請求調用長時間啟動並執行操作時,非同步請求會防止請求排隊。“
在這個例子裡,非同步請求是完美的解決方案。因為他會讓IIS伺服器有能力去處理更多重要的請求,比如一個新的使用者第一次訪問網站。其中,載入使用者點播內容是不太重要,因為大多數人甚至不關注被載入額外的內容。在一個典型的社交網站,大多數活動可能包含使用者的意見。在以前的秘方中,建立了一個評論的功能。在這個例子中,將更新網站的網頁,列出最近的評論。足夠的評論會被顯示,所以會出現捲軸。一旦使用者開始滾動,一個Ajax請求非同步controller將檢索其他評論。
首先Home/Index view 需要更新去顯示最近的評論。為了提供一些評論的上下文內容,關於書的基礎詳情也將被顯示為導航到圖書的連結。所以這個view將簡單的調用view 中的render function會在下邊建立:
@model IEnumerable<MvcApplication.Models.BookComment>
@{
ViewBag.Title = "Home Page";
}
<h2>@ViewBag.Message</h2>
<p>
To learn more about ASP.NET MVC visit <a href="http://asp.net/mvc" title="ASP.NET MVC Website">
http://asp.net/mvc</a>.
</p>
<script type="text/javascript">
var lastY = 0;
var currentY = 0;
var page = 1;
var maxPages = @ViewBag.maxPages;
$(window).scroll(function () {
if (page < maxPages) {
currentY = $(window).scrollTop();
if (currentY - lastY > 200 * (page - 1)) {
lastY = currentY;
page++;
$.get('CommentFeed/Comments?page=' + page,
function(data) {
$('#comments').append(data);
});
}
}
});
</script>
<div id="comments">
<h2>
Recent Comments</h2>
@Html.Partial("../CommentFeed/Comments", Model)
</div>
在上面的例子,執行滾動視窗時也有一些比較複雜的JavaScript代碼會執行。一些全域JavaScript變數被定義去保持監控當前的“Y”滾動的位置,最後的“Y”滾動位置和當前被檢索的頁面。當視窗的scrollTop位置減去最後的滾動位置是大於一個具體的數字,通過Ajax檢索新書評論並附加到評論列表。
你將根據你自己的網站去根據矯正那個具體的數字,基於內容的高度,要確保新的內容總是要提前檢索。
下一步,HomeController需要更新檢索圖書評論列表。 評論在降序排序,以確保最新的建立日期評論首先顯示。為了防止激烈的資料庫負載,全部評論將減少到一個小數目。這應該根據你的網站去調節,以確保有足夠的內容,導致滾動。在下面的例子,建議被限制在3。分頁的最大數也取決於評論總數於除以3。一旦最大的評論數已經返回,最大分頁數是用來防止進一步的Ajax調用。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Globalization;
using System.Data.Entity;
using MvcApplication.Models;
namespace MvcApplication.Controllers
{
public class HomeController : Controller
{
private BookDBContext db = new BookDBContext();
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
// Get our recent comments
var bookcomments = db.BookComments.Include(b => b.Book).OrderByDescending(b => b.Created).Take(3);
var count = db.BookComments.Count();
ViewBag.maxPages = count / 3 + 1;
return View(bookcomments);
}
public ActionResult ChangeLanguage(string language)
{
Session["CurrentLanguage"] = new CultureInfo(language);
return Redirect("Index");
}
public ActionResult About()
{
return View();
}
public ActionResult MobileTest()
{
return View();
}
public ActionResult MobileTest2()
{
return View();
}
}
}
同樣的功能需要被複製到一個新的非同步controller。控制器檔案夾選中,按右鍵並選擇“添加→控制器。新
控制器將被命名為CommentFeedController。該控制器不需要腳手架模板功能,下拉,選擇空
控制器,然後按添加。
這個控制器會看起來與一個典型的控制器稍有不同。使用非同步控制器,一個view將分成兩個函數。第一個函數執行的非同步
請求(例如,檢索的view)。第二個函數接收結果,非同步呼叫和返回或顯示的結果。
提示:在下面的例子,呈現局部視圖。在某些應用中,它可能是有益的,以減少網路流量,返回一個JSON結果,讓JavaScript代碼處理與顯示。但是,要簡化這個例子,重點放在非同步控制器,前者將用於返回一個partial view。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcApplication.Models;
using System.Data.Entity;
namespace MvcApplication.Controllers
{
public class CommentFeedController : AsyncController
{
private BookDBContext db = new BookDBContext();
public void CommentsAsync(int page)
{
AsyncManager.OutstandingOperations.Increment();
AsyncManager.Sync(() =>
{
var bookcomments = db.BookComments.Include(
b => b.Book).OrderByDescending(b =>
b.Created).Skip(page * 3).Take(3);
AsyncManager.Parameters["bookcomments"] =
bookcomments;
AsyncManager.OutstandingOperations.Decrement();
});
}
public ActionResult CommentsCompleted(
IEnumerable<BookComment> bookcomments)
{
return PartialView(bookcomments);
}
}
}
第一個 函數,CommentsAsync,接收從javascript傳入的當前頁面,並且用這個值去檢索接下來的三個評論。然後通過非同步方法呼叫,檢索評論並且傳遞一個變數到第二個函數。最終的事是執行AsyncManager.OutstandingOperations.Decrement()方法。OutstandingOperations(未解決操作)的increment(遞增)和decrement(遞減)的匹配是很重要的。否則,當他們不匹配時,sync manager 將取消請求,這可以組織永不休止的請求。
第二個函數接收book comments 並且返回一個partial view。這和Home/Index view一樣。在這個過程的最後一個步驟是建立partial view。右擊檔案夾,添加新檔案夾。這個檔案夾應該命名為CommentFeed去匹配controller的名字。選擇這個檔案夾,右擊,點Add→View 命名為Comments-----在添加它之前確定去檢查Partial view。
@model IEnumerable<MvcApplication.Models.BookComment>
@foreach (var item in Model) {
<h3><a href="@Url.Action("Details", "Books", new {ID=item.Book.ID } )">
@Html.DisplayFor(modelItem => item.Book.Title)
</a></h3>
<h4>Comment Posted: @Html.DisplayFor(
modelItem => item.Created)</h4>
<p>@MvcHtmlString.Create(Html.Encode(item.Comment).Replace(
Environment.NewLine, "<br />"))</p>
}
上邊的view遍曆comments,並首先顯示書的標題和詳細資料頁面的連結,然後建立評論的日期,最後建立comment本身。由於View裡可能包含分行符號,用<br/>替代每個Evironment.NewLine去匹配評論輸入間距。
另請參閱
Asynchronous controllers