In my previous two performance related posts I’ve gone on and on about the benefits of flushing an HTTP response early and how to do it in ASP.NET MVC. If you haven’t read those yet, I recommend you take a quick moment to at least read Flushing in ASP.NET MVC, and if you have a little extra time go through More Flushing in ASP.NET MVC as well.
I think those posts did a decent job of explaining why you’d want to flush early. In this post I’m going to dig into the details of how to flush early using my library, PerfMatters.Flush.
Three Usage Patterns
The core of what you need to know to use PerfMatters.Flush is that I’ve tried to make it easy to use by providing a few different usage models. Pick the one that works best in your scenario, and feel free to mix and match across your application.
1. Attribute Based
The easiest way to use PerfMatters.Flush is via the [FlushHead]
action filter attribute, like this:
[FlushHead(Title = "Index")]
public ActionResult Index()
{
// Do expensive work here
return View();
}
The attribute can be used alone for HTML documents with a static <head>
section. Optionally, a Title
property can be set for specifying a dynamic <title>
element, which is very common.
2. Code Based
For more complex scenarios, extension methods are provided which allow you to set ViewData
or pass along a view model:
public ActionResult About()
{
ViewBag.Title = "Dynamic title generated at " + DateTime.Now.ToLocalTime();
ViewBag.Description = "A meta tag description";
ViewBag.DnsPrefetchDomain = ConfigurationManager.AppSettings["cdnDomain"];
this.FlushHead();
// Do expensive work here
ViewBag.Message = "Your application description page.";
return View();
}
As you can see, this mechanism allows for very dynamic <head>
sections. In this example you could imagine a <title>
element, <meta name="description" content="…">
attribute (for SEO purposes) and <link rel="dns-prefetch" href="…">
(for performance optimization) all being set.
3. Global Lambda
Finally, PerfMatters.Flush offers a model to flush early across all your application’s action methods – which simply leverages the same global action filters that have been in ASP.NET MVC for years now:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new FlushHeadAttribute(actionDescriptor =>
new ViewDataDictionary<CustomObject>(new CustomObject())
{
{"Title", "Global"},
{"Description", "This is the meta description."}
}));
}
In this case we pass a Func<ActionDescriptor, ViewDataDictionary>
to the FlushHeadAttribute
constructor. That function is executed for each action method. This example is pretty contrite since the result is deterministic, but you can see both a custom model (CustomObject
) and ViewData
in use at the same time.
In real world usage the actionDescriptor
parameter would be analyzed and leveraged to get data from some data store (hopefully in memory!) or from an IOC container.
Installation & Setup
Getting up and running with PerfMatters.Flush is as easy as installing the NuGet package.
From there, you’ll want to move everything you’d like to flush out of _Layout.cshtml to a new file called _Head.cshtml (which sits in the same directory). Here’s an example of _Head.cshtml:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@if (ViewBag.Description != null)
{
<meta name="description" content="@ViewBag.Description">
}
<title>@ViewBag.Title - My ASP.NET Application</title>
@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/modernizr")
</head>
Here’s its corresponding _Layout.cshtml file:
@Html.FlushHead()
<body>
<!-- Lots of lovely HTML -->
<div class="container body-content">
@RenderBody()
<hr />
<footer>
<p>© @DateTime.Now.Year - My Application</p>
</footer>
</div>
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/bootstrap")
@RenderSection("scripts", required: false)
</body>
</html>
Notice the @Html.FlushHead()
method on the first line? That’s the magic that stiches this all together. It allows you to use MVC the way your used to on action methods that you don’t want to flush early, and opt-in (via one of the usage model’s described above) when you do.
Wrapping Up
PerfMatters.Flush has been fun for me to work on. It’s currently being used in production on getGlimpse.com and has nicely boosted perceived performance on the pages that do any database queries.
To be honest, I wish that PerfMatters.Flush didn’t have to exist. I’ve asked the ASP.NET team to look into baking first class support for flushing early into the framework, but I don’t foresee that happening for quite awhile. Either way, there are tons of application built in MVC 3, 4 and 5 that can leverage this now.
The project is open source, and the code is hosted on GitHub. I’d love to hear your feedback on ways to make it better and easier to use.