65.9K
CodeProject 正在变化。 阅读更多。
Home

通过在服务器生成完整页面之前下载 JS/CSS 来加快页面渲染

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (26投票s)

2014年4月7日

CPOL

4分钟阅读

viewsIcon

70145

让浏览器在服务器仍在执行耗时操作并生成页面输出时下载 Javascript 和 CSS

引言

当动态页面在服务器上执行时,浏览器除了等待来自服务器的 html 之外什么也没做。如果您的服务器端代码需要 5 秒钟来执行服务器上的数据库操作,那么在这 5 秒钟内,用户将盯着一个空白的白屏。为什么不利用这个机会同时在浏览器上下载 Javascript 和 CSS 呢?到服务器完成工作时,服务器只需发送动态内容,浏览器就能立即呈现它。这种优化技术可以提高任何在服务器上需要一段时间才能完成其工作的动态页面以及需要下载一些 js 和 css 的性能。

这是一个典型页面的例子,其中 JS/CSS 仅在页面传递到浏览器后才加载

它需要 7 秒钟来渲染内容。

这是优化后的版本,其中 JS/CSS 由浏览器加载,而服务器仍在生成页面内容

这个在 6 秒内渲染,节省了惊人的 1 秒!你拥有的 JS、CSS 和较慢的动态页面越多,你获得的改进就越多。

但是,这很容易做到!

这在使用 PHP、NodeJS、旧 ASP 等技术时很容易做到,您可以在其中直接从代码中编写输出,并且可以随时刷新响应。例如,一个 PHP 解决方案就像 Steve Souders 所示,如下所示

<?php
// Flush any currently open buffers.
while (ob_get_level() > 0) {
    ob_end_flush();
}
ob_start();
?>
[a bunch of HTML...]
<!-- 0001020304050607080[2K worth of padding]... -->
<?php
ob_flush();
flush();
require_once('trends.inc'); // contains the slow query
?> 

但是对于 ASP.NET WebForms 或 ASP.NET MVC,尤其是在使用 Razor 视图引擎时,这很复杂。您不能随便调用 Response.Flush。对于 ASP.NET WebForms,它会弄乱视图状态、回发事件处理程序等。对于使用 Razor 的 ASP.NET MVC,它根本不起作用。

在 ASP.NET MVC 中有一些巧妙的方法可以做到这一点。例如,使用局部视图,它保存您想立即发送的内容,例如包含 js、css 的 <head> 部分。一个例子是

public ActionResult FlushDemo()
{
       PartialView("Head").ExecuteResult(ControllerContext);
       Response.Flush();

       // do other work
       return PartialView("RemainderOfPage");
}  

但这意味着,您不能使用 Layouts 来保存您的 <head> 部分。您必须停止使用任何 Layout 解决方案。此外,您的控制器现在必须进行将视图拆分并将视图的一部分尽早发送的接线工作。它现在被一些“视图”类型的代码污染了。

最好从 View 中执行此操作,您可以在其中定义 View 的哪个部分应该先出现,哪个部分应该在服务器端工作完成后出现。此外,理想的解决方案应该与布局完美配合。

Razor 的解决方案

首先,我们希望让 Controller 只做“Controller”的事情,而不必担心视图、局部视图、响应刷新等。以下是操作方法

public ActionResult Index()
{
    Thread.Sleep(5000); // Do something expensive here
    var model = new HomeModel()
    {
        Text = "Hello"
    };
    
    return View(model);
}

public ActionResult FastIndex()
{
    var asyncModel = new AsyncModel<HomeModel>(new HomeModel(),
        model =>
        {
            Thread.Sleep(5000); // Do something expensive here
            model.Text = "Hello";
        });

    return View(asyncModel);
} 

第一个 Index() 向您展示了如何编写典型的控制器代码。这是慢速版本。 FastIndex() 是更快的版本。其思想是推迟执行生成模型的代码,直到视图呈现开始。

现在是慢速视图

@{
    ViewBag.Title = "Home";
}

<p>From slow index: @Model.Text</p> 

这是相同视图的更快版本

@{
    ViewBag.Title = "Home";
}

@section AsyncBody {    
    @{ViewData.Model = Model.Execute();}
    <p>From faster index: @Model.Text</p>
}

AsyncBody 部分包含在控制器中运行昂贵代码后呈现的 View 内容。在该部分中,首先它调用 Model.Execute(),它调用 AsyncModelExecute() 函数。然后它触发在 FastIndex 函数中运行在控制器中定义的代码的委托。

现在是布局部分。该布局负责立即呈现 <head> 部分,然后呈现视图中的 AsyncBody 部分。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>@ViewBag.Title</title>
    <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Scripts/jquery-1.7.1.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/jquery.validate.min.js")"></script>
    ...
    ...   
</head>

<body>
    <p>Before Body</p>
    @if (IsSectionDefined("AsyncBody")) {
        var sb = ((StringWriter)ViewContext.Writer).GetStringBuilder();
        Response.Write(sb);
        Response.Flush();
        sb.Length = 0; 
        @RenderSection("AsyncBody");
    }
    else {
        @RenderBody();    
    }    
    <p>After Body</p>
</body>
</html> 

在这里,它抓取 Razor 内部缓冲区,该缓冲区保存生成的内容,并在响应上刷新它。然后它调用 @RenderSection("AsyncBody"),然后调用 Model.Execute(),进而调用控制器的昂贵代码。

AsyncModel 类实际上什么也没做

public class AsyncModel<T>
{
    private Action<T> ControllerCode;
    public T RealModel;
    public AsyncModel(T realModel, Action<T> controllerCode)
    {
        this.ControllerCode = controllerCode;
        this.RealModel = realModel;
    }
    public T Execute()
    {
        this.ControllerCode(this.RealModel);
        return this.RealModel;
    }
} 

就是这样!

它看起来的样子

当您访问页面时,您会立即获得一些输出,并且 js、css 开始下载,而服务器正在执行其工作

当服务器完成工作后,其余内容将被渲染

您可以在这里看到在 js、css 下载时间方面取得的巨大节省。

 

 

结论

在这里,您有一个针对 ASP.NET MVC 的解决方案,特别是针对 Razor,可以立即呈现部分页面,而服务器正在进行耗时的工作。对于常规的 ASP.NET WebForm,它要复杂得多,我们将在稍后探讨。由于 ASP.NET MVC 没有 Control 对象模型、没有 Page 状态、没有 ViewState,因此在 MVC 中执行此操作比在 WebForms 中容易得多。通过实现此解决方案,您可以使几乎任何 ASP.NET MVC Razor 页面的加载和渲染速度显着加快。

© . All rights reserved.