IN THIS CHAPTER
Wrox.com Code Downloads for this Chapter
You can find the wrox.com code downloads for this chapter at www.wrox.com/go/azureaspmvcmigration on the Download Code tab. It is recommended that you download the code and review it so that you have a good understanding of what is about to be discussed. The steps in this chapter cover baseline performance setting techniques, and ASP.NET Web Forms and ASP.NET MVC 4 optimization features.
As discussed in the previous chapter, the performance of your system is critical to its success. If your website runs slow, visitors are very unlikely to return to it. This chapter has examples and tips on how to fine-tune and optimize the ASP.NET, ASP.NET MVC 4 website, and ASP.NET MVC 4 Web Role.
Before you begin implementing any change, it is always a good idea to set a performance baseline for what you are about to change. By doing this, you can see how the change impacts your system. You may be surprised that a change you expected to have a big impact actually had no or even a negative impact. However, without knowing the current status, there is actually no way of telling how the change affected the system . . . unless it is absolutely obvious.
To capture some performance statistics, you can use a free tool called Fiddler. Although Fiddler is commonly used to troubleshoot issues happening between the browser and the web server, you can also use it to see how fast a web page can render. For this section, the purpose of these statistics is to set a baseline for you to use when you compare the results of optimizations performed later in this chapter.
To use Fiddler to capture performance statistics for both the ASP.NET website and ASP.NET MVC 4 project, perform the following steps:
For the following exercises, the statistics captured for baseline analysis and the basis for improvement are shown in Table 4-1 (for the ASP.NET Website) and Table 4-2 (for the ASP.NET MVC 4 Web Role).
ATTRIBUTE | VALUE |
# of HTTP requests (objects) | 29–61 |
Total size (bytes) | 205 K–745.9 KB |
Total download time (seconds) | 3.03–6.89 |
HTML size (bytes) | 36.7–79.2 |
HTML download time (seconds) | .39 |
CSS size (bytes) | 11.4 |
CSS download (seconds) | .46 |
Image size (bytes) | 94.2–104.8 |
Image download (seconds) | 4.3 |
Script size (bytes) | 63.3–547.2 |
Script download (seconds) | 1.74 |
Total header sizes (bytes) | 23.6 KB |
ATTRIBUTE | VALUE |
# of HTTP requests (objects) | 14–33 |
Total size (bytes) | 161 KB–376 KB |
Total download time (seconds) | 3.2–3.7 |
HTML size (bytes) | 5.5–42.8 |
HTML download time (seconds) | 1.67 |
CSS size (bytes) | 1.9 KB |
CSS download (seconds) | .21 |
Image size (bytes) | 25.9KB–88 KB |
Image download (seconds) | 1.67 |
Script size (bytes) | 65.9–307.3 KB |
Script download (seconds) | 1.55 |
Total header sizes (bytes) | 13.2 |
The statistics captured for the attributes in Table 4-1 and Table 4-2 are from the following URLs:
Before tuning the website and Web Role, you can implement a tool called MiniProfiler. MiniProfiler is a single .NET assembly that, when configured, displays the timing of specific code segments in an ASP.NET page. For example, you can wrap it around a method or a specific line of code in a method. You can also wrap it around an entire ASP.NET page from start to finish.
The following exercises in this section provide the implementation instructions for both the sample ASP.NET website and sample ASP.NET MVC 4 project. The MiniProfiler is imbedded to track the performance of the following features contained in both the ASP.NET website and ASP.NET MVC 4 Web Role samples:
You can begin by implementing the MiniProfiler capabilities into the ASP.NET website project.
You will certainly notice that there is no code-behind when you add the file from the previous section. Historically, a Global.asax file is not needed in an ASP.NET Website project, and therefore they were/are not added by default. Also, Microsoft moved away from a code-behind file in this context in favor of the script-based code you see when the Global.asax file is opened. For this example, you simulate a code-behind for the Global.asax by adding a new class named Global to the App_Code directory.
To do this, follow these steps:
<%@ Application Inherits="Global" Language="C#" %>
LISTING 4-1: The Simulated Global.asax.cs Code-behind Class with MiniProfiler Configurations
using System;
using StackExchange.Profiling;
public class Global : System.Web.HttpApplication
{
protected void Application_BeginRequest()
{
MiniProfiler profiler = null;
profiler = MiniProfiler.Start(ProfileLevel.Verbose);
}
protected void Application_EndRequest()
{
MiniProfiler.Stop(true);
}
}
Now that you have the code-behind sorted out, you can begin to configure the MiniProfiler and then load it. This includes determining the time it takes to load the homepage, sample blog, rating, blog comments, and dynamic data.
You can do so by following these steps:
LISTING 4-2: MiniProfiler Configuration — web.config
<system.webServer>
<handlers>
<add name="MiniProfiler" path="mini-profiler-resources/*"
verb="*" type="System.Web.Routing.UrlRoutingModule"
resourceType="Unspecified" preCondition="integratedMode" />
</handlers>
</system.webServer>
<%= StackExchange.Profiling.MiniProfiler.RenderIncludes() %>
LISTING 4-3: RSS XML Load — MiniProfiler
using StackExchange.Profiling;
protected void Page_Load(object sender, EventArgs e)
{
var mp = MiniProfiler.Current;
using (mp.Step("RSS XML Load "))
{
LoadBlogXML();
}
}
LISTING 4-4: Blog Rating — MiniProfiler
<%@ Import Namespace="StackExchange.Profiling" %>
<asp:TableRow><asp:TableCell>
<% using (MiniProfiler.Current.Step("Blog Rating Load"))
{%>
<RATING:Rating runat="server" ID="RatingControl" />
<% } %>
</asp:TableCell></asp:TableRow>
LISTING 4-5: Blog Comment — MiniProfiler
<asp:TableRow><asp:TableCell>
<% using (MiniProfiler.Current.Step("Blog Comments Load"))
{%>
<FEEDBACK:fbFORM runat="server" ID="FEEDbackForm" />
<% } %>
</asp:TableCell></asp:TableRow>
LISTING 4-6: Total # of Blogs - MiniProfiler
using StackExchange.Profiling;
protected int DisplayTotalBlogs()
{
var mp = MiniProfiler.Current;
using (mp.Step("DisplayTotalBlogs"))
{
return PostsDAC.PostTotalCount();
}
}
Now that you have all the pieces together, you can run your site, see how it performs, and migrate everything to the server. To do so, follow these steps:
PROFILED FEATURE | TIME TAKEN (MS) |
Homepage | 2945.4 |
RSS XML load | 8.9 |
Sample Blog page load | 4372.8 |
Blog Rating load | 18.3 |
DisplayTotalBlogs | 181.2 |
DisplayTotalBlogComments | 883.8 |
DisplayTotalFundamentals | 2639.7 |
DisplayBlogArchive | 231.6 |
The following are the modified files that you need to publish so that they can measure the performance on a server and not a PC:
Now that the MiniProfiler is implemented in the ASP.NET website and the baseline timings are logged, you can implement the same into the ASP.NET MVC 4 Web Role.
You need to install MiniProfiler, as shown in the following steps:
Install-package MiniProfiler.MVC3
You should see a result similar to Figure 4-5, shown previously, while performing this command on the sample ASP.NET website.
When complete, begin to implement the code into the ASP.NET MVC 4 Web Role that configures and tracks timings of the chosen features, shown in the following list:
To do this, follow these steps.
@using StackExchange.Profiling;
LISTING 4-7: Adding the RenderIncludes() Method — MiniProfiler
@MiniProfiler.RenderIncludes()
</body>
</html>
LISTING 4-8: RSS XML Load — MiniProfiler
using StackExchange.Profiling;
public ActionResult Index()
{
ViewBag.Message = "This site is ... Windows Azure.";
var mp = MiniProfiler.Current;
using (mp.Step("RSS XML Load"))
{
var blogs = BlogListXML();
return View(blogs);
}
}
LISTING 4-9: DisplayTotalBlogComments — MiniProfiler
using StackExchange.Profiling;
public IList<Comments> GetCommentsList(int Id)
{
var mp = MiniProfiler.Current;
using (mp.Step("DisplayTotalBlogComments"))
{
using (ISession session = MvcApplication.SessionFactory.OpenSession())
using (ITransaction transaction = session.BeginTransaction())
{
Blog blog = session.Get<Blog>(Id);
IList<Comments> comments = blog.comments.ToList<Comments>();
return comments;
}
}
}
LISTING 4-10: DisplayTotalBlogs — MiniProfiler
using StackExchange.Profiling;
var mp = MiniProfiler.Current;
using (mp.Step("DisplayTotalBlogs "))
{
using (ISession session = MvcApplication.SessionFactory.OpenSession())
using (ITransaction transaction = session.BeginTransaction())
{
IQuery blogQuery = session.CreateQuery("Select count(*) from Blog");
elements.blogCount = Convert.ToInt32(blogQuery.UniqueResult());
}
}
After publishing the code changes to your Windows Azure Web Role, you can execute the request for the homepage and sample blog. Document the baseline timings so that you can compare them to the same timings after the optimizations. Table 4-4 represents the baseline timings logged by the profiler.
PROFILED FEATURE | TIME TAKEN (MS) |
Homepage | 1351.1 |
RSS XML load | 4.6 |
Sample Blog page load | 2452.4 |
DisplayTotalBlogs | 288.6 |
DisplayTotalBlogComments | 34.9 |
DisplayTotalFundamentals | 47.7 |
DisplayBlogArchive | 35.9 |
Figure 4-11 illustrates an example of the MiniProfiler report after clicking the tab.
Another very useful tool for measuring performance and for debugging your website is the F12 Developer tool suite. To access these tools, follow these steps:
If you perform these actions on one of your own websites or Web Roles, you might consider exporting the data by clicking the Save button. You can save the data as XML or CSV format, and use it later to compare it against a more fine-tuned or optimized ASP.NET project.
Google PageSpeed is a very powerful tool that performs analysis on a user-supplied web address. The result of the analyses is a score of somewhere between 0 and 100 — with 100 being the best. For example, entering the following URL into the tool results in a score of 78 points out of 100: http://aspnet.thebestcsharpprogrammerintheworld.com/blogs/Using-the-as-keyword-versus-boxing.aspx.
In addition, one benefit of this online tool is that it provides numerous suggestions that can help the analyzed website perform more optimal. Also, PageSpeed renders a very valuable set of actions items that prioritize the reasons your site did not achieve the full 100 points, listing those actions having the most positive impact first. The actionable items provided after the analysis of the previously entered web address are provided in Table 4-5:
RECOMMENDATION | PRIORITY |
Leverage Browser Caching | 1 |
Enable Compression | 2 |
Serve Scaled Images | 2 |
Optimize Images | 3 |
Bundle and Minify JavaScript, CSS, and HTML | 3 |
When you click any of the suggestions, you navigate to a page describing the action required to implement the suggestion. The following sections illustrate how to implement the first four suggestions in Table 4-5 to see how it improves the score.
When you have caching content in the browser, it means that if the same file is requested again in the future, instead of performing an HTTP GET to retrieve the content, the content is loaded from the local cache. Because the ASP.NET sample website is held with a hosting provider, you have no possibility of configuring IIS to set the caching properties. In addition, this ASP.NET website is hosted on IIS 6, and there is no simple way to set the Expires header date for static content. Therefore, the configuration is performed in the web.config file in preparation for migration to IIS 7+. For example, the following configuration, shown in Listing 4-11, sets the expiration date of the static content to a date one year in the future.
LISTING 4-11: Setting the Expires Value in the web.config File
<system.webServer>
<staticContent>
<clientCache cacheControlMode="UseExpires"
httpExpires="Mon, 03 Mar 2014 08:00:00 GMT" />
</staticContent>
</system.webServer>
Alternatively, you can add the following code snippet at the top of the ASP.NET page. The snippet caches the page for approximately 30 days.
<%@ OutputCache Duration="25920000" VaryByParam="*" %>
Adding both of the elements just mentioned increases the Google PageSpeed by one point. Migrating the ASP.NET website to an IIS 7+ instance increases the score even more.
To enable compression using your web.config file add the following configuration, as shown in Listing 4-12.
LISTING 4-12: Configure Compression in Your web.config File
<system.webServer>
<httpCompression
directory="%SystemDrive%inetpub empIIS Temporary Compressed Files">
<scheme name="gzip" dll="%Windir%system32inetsrvgzip.dll" />
<dynamicTypes>
<add mimeType="text/*" enabled="true" />
<add mimeType="message/*" enabled="true" />
<add mimeType="application/javascript" enabled="true" />
<add mimeType="*/*" enabled="false" />
</dynamicTypes>
<staticTypes>
<add mimeType="text/*" enabled="true" />
<add mimeType="message/*" enabled="true" />
<add mimeType="application/javascript" enabled="true" />
<add mimeType="*/*" enabled="false" />
</staticTypes>
</httpCompression>
<urlCompression doStaticCompression="true" doDynamicCompression="true" />
</system.webServer>
After you add this configuration, the content types identified by the mimeType value are compressed. Adding and deploying these compression examples increases the PageSpeed score by 2 points.
There are two files identified in the report as being scaled incorrectly:
It appears that these two images are larger than necessary, and when they are rendered, HTML or CSS resizes them to a smaller size. It makes sense to reduce the size of the files so that they match the size being rendered.
The IIS8.jpg is referenced in the IncludeBlogRightColumn.ascx file and it does indeed include Width="90" and Height="110" values, which reduce its actual values from width="421" by height="529". You must remove the Width and Height settings from the IncludeBlogRightColumn.ascx and reduce the size of the image using an image editor, such as Microsoft Paint (mspaint.exe).
To reduce the image size, follow these simply steps:
The images are now smaller in size (IIS8.png is now 8 KB and c-sharp.png is now 4 KB) and no longer need to be reduced using HTML width and height attributes.
PNG image files are considered static files; however, they usually do not benefit from compression because these file are likely already compressed. Nonetheless, you can take some actions to reduce the size without losing the quality of the image.
The results of the Google PageSpeed analysis identified 16 image files, which may result in an approximate 17 KB reduction in the size of the files. This equates to a reduction of more than 25 percent in their original size. Using tools, such as PNGOUT or GIMP, to reduce the size and convert the images from JPG to PNG would create these gains.
One of the images identified in the analysis report is the titlebar.jpg. By converting the image from a JPG to a PNG you may reduce the size by 45 percent. Opening the titlebar.jpg in Paint and performing a Save As ⇒ PNG, results in a 33 percent reduction in the size of the file. Now see if you can get the 45 percent reduction using GIMP:
When exported, check the size of the image. Instead of Paint’s reduction of 33 percent, GIMP reduced the image size by 66 percent; this is even more than the original estimation.
If you perform the same method with the other JPGs and convert them to PNGs (see Table 4-6), you’ll see that not every export does not result in an image size reduction.
FILENAME | ORIGINAL SIZE | EXPORTED SIZE |
Titlebar.jpg/png | 6K B | 2 KB |
5.jpg/png | 3.61 KB | 3.49 KB |
6.jpg/png | 3.93 KB | 3.87 KB |
7.jpg/png | 3.64 KB | 3.60 KB |
IIS8.jpg/png | 7 KB | 15 KB |
Lessons_t.jpg/png | 3 KB | 4 KB |
Reviews_t.jpg/png | 3 KB | 4 KB |
NHibernate_small.jpg/png | 3 KB | 7 KB |
News_t.jpg/png | 2 KB | 3 KB |
Home_t.jpg/png | 2 KB | 3 KB |
Help_t.jpg/png | 2 KB | 3 KB |
Blogs_t_dark.jpg/png | 2 KB | 4 KB |
Newsletter.jpg/png | 2 KB | 1 KB |
Titlebar_sub_nav.jpg/png | 960 B | 229 B |
Eyebrow.jpg/png | 803 B | 221 B |
C-sharp.jpg/png | 2 KB | 3 KB |
You must modify the references made to an image when you notice a reduction in file size. You can do so by changing the image reference from .jpg to .png in the SecondLevel.master and the Blogs/Using-the-as-keyword-versus-boxing.aspx file.
Bundling and minification are new to ASP.NET 4.5 and can improve performance by reducing the number of file downloads required to render a page and reducing the size of JavaScript, CSS, and HTML files. They’re defined as:
To view the impact of implementing these features, follow these steps:
To implement bundling into the ASP.NET website, perform the following steps:
Install-package Microsoft.AspNet.Web.Optimization
LISTING 4-13: Bundling ASP.NET JavaScript Files
void Application_Start(object sender, EventArgs e)
{
Bundle JSBundle = new Bundle("~/JSBundle");
JSBundle.Include("~/syntax/scripts/shCore.js");
JSBundle.Include("~/syntax/scripts/shBrushCSharp.js");
BundleTable.Bundles.Add(JSBundle);
}
<%@ Import Namespace="System.Web.Optimization" %>
//<script type="text/javascript"
//src="<%= BundleTable.Bundles.ResolveBundleUrl("~/JSBundle") %>"></script>
LISTING 4-14: Individual JavaScript File References
<script type="text/javascript" src="../syntax/scripts/shCore.js"></script>
<script type="text/javascript" src="../syntax/scripts/shBrushCSharp.js"></script>
Compare the size and time taken between the bundled and nonbundled results (refer to Figures 4-17 and 4-18. Notice that the size reduced from 19.16 KB to 18.68 KB, and the time improved about 4/10th of a second. But you can get additional improvement after implementing minification.
To implement minification:
LISTING 4-15: Add Minification to the Application_Start() Method
void Application_Start(object sender, EventArgs e)
{
Bundle JSBundle = new Bundle("~/JSBundle", new JsMinify());
JSBundle.Include("~/syntax/scripts/shCore.js");
JSBundle.Include("~/syntax/scripts/shBrushCSharp.js");
BundleTable.Bundles.Add(JSBundle);
}
LISTING 4-16: Bundling and Minification of CSS in ASP.NET MVC 4 Web Role
Bundle CSSBundle = new Bundle("~/CSSBundle", new CssMinify());
CSSBundle.Include("~/Content/Site.css");
CSSBundle.Include("~/Content/Syntax/Styles/shCoreFadeToGrey.css");
BundleTable.Bundles.Add(CSSBundle);
<link type="text/css" rel="stylesheet"
href="@BundleTable.Bundles.ResolveBundleUrl("~/CSSBundle")" />
As you’ll notice, there were some significant improvements for both size and speed in this example. Prior to implementing the bundling and minification the total time for download was 134 ms with a combined size of about 22 KB. After the implementation, the download time is 15 ms with a size of 15 KB. That’s almost a 90 percent increase in performance.
A benefit of utilizing a Windows Azure Web Role compared to a Windows Azure Web Site is making a Remote Desktop Connection to the server. This provides the administrator the ability to directly optimize configurations from the IIS Management console.
To create a Remote Desktop Connection and connect to the Windows Azure Web Role, review the exercises in Chapter 8 and then follow these steps to implement compression and caching:
In this section, you make some changes to the Output Caching capabilities so you can understand what the feature does. For example, output caching can be used to store dynamic content in memory so that the script required to generate each request does not need to be executed for each page. This can yield some significant performance improvements.
LISTING 4-17: Add a DateTime Stamp to the _Layout.cshtml File
<div>@DateTime.Now</div>
The fact that the time stamp does not change for 30 seconds means that the page is being cached and therefore is not downloaded, saving bandwidth and increasing performance. In most cases, you would set the file-cache time interval to a value greater than 30 seconds. The actual value is specific to the application hosted on the web site. It is possible for some files not to change for days or weeks. Therefore they do not need to be downloaded again for a longer period of time. An understanding of an application’s purpose is required for setting the proper value for this setting.
Recall from Table 4-1 that baseline performance metrics were captured. These metrics measured the speed and size of files required to render requests made to the following:
From the captured baseline metrics, the optimizations implemented included:
When you learn how to deploy your ASP.NET website and ASP.NET MVC 4 Web Role to the Windows Azure platform discussed in Chapter 5 and how to execute it in Chapter 6, you can redeploy the code enhancements made in this chapter to test and review the improvements. Even better, after testing, you can go straight into the production environment with the most optimal code.
After you deploy the optimized code, you can capture the statics in the same way you captured them when you created the baseline in this chapter. Table 4-6 and Table 4-7 provide the performance metrics after the recommendations presented in this chapter were implemented. Notice that, in almost every case, there was an improvement in speed and, in many cases, a decrease in size.
ATTRIBUTE | VALUE |
# of HTTP requests (objects) | 25–58 |
Total size (bytes) | 194 K–623.6 KB |
Total download time (seconds) | 2.15–4.39 |
HTML size (bytes) | 33.4-62.5 |
HTML download time (seconds) | 0.25 |
CSS size (bytes) | 8.12 |
CSS download (seconds) | 0.24 |
Image size (bytes) | 56.3–89.3 |
Image download (seconds) | 3.4 |
Script size (bytes) | 42.1–385.8 |
Script download (seconds) | 0.97 |
Total header sizes (bytes) | 23.6 KB |
ATTRIBUTE | VALUE |
# of HTTP requests (objects) | 11–28 |
Total size (bytes) | 134 KB–332 KB |
Total download time (seconds) | 2.7–3.1 |
HTML size (bytes) | 4.6–36.3 |
HTML download time (seconds) | 1.44 |
CSS size (bytes) | 1.62 KB |
CSS download (seconds) | 0.19 |
Image size (bytes) | 26.1 KB–87 KB |
Image download (seconds) | 1.41 |
Script size (bytes) | 66.4–242.9 KB |
Script download (seconds) | 1.37 |
Total header sizes (bytes) | 14.5 |
In this chapter, you captured a set of baseline performance metrics using tools such as Fiddler, MiniProfiler, and the F12 Developer Tool Suite in Internet Explorer. It’s important that you capture the baseline so that you have something to which you can compare your post-optimization performance.
You also used Google PageSpeed to help identify some inefficiencies in the sample ASP.NET website, some of which were referencing an image. You then modified the width and height attributes to reduce the size instead of just reducing the image size. You also ensured that browser caching was implemented effectively.
In addition, you learned that by minifying and bundling CSS and JavaScript files, you could realize gains in performance in both ASP.NET websites and ASP.NET MVC 4 Web Roles.
After you implemented these optimization examples and redeployed the enhanced source code to the respective Windows Azure environments, you saw positive results. In almost every case, there was an improvement in download speed and a reduction in overall download size.