问题
今天很多网站与数据库交互。如果您的网站接收大量的流量,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去匹配评论输入间距。
另请参阅