Posted on April 28th, 2011
If your workload includes crafting multiple web sites you know that there are several components that are commonly used in each site including trusted finnish online casinos. Some of those common elements are Google's Analytics tracking JavaScript and meta tags used in the site submission process to help verify site ownership to both Google and Bing. It would be super ideal if we could try and find a way to have that code already written and be able to quickly add it to a new web site project built upon ASP.NET MVC3. Lets make it happen.
Building a common MVC web site library
We will begin by creating a new Windows -> Class Library project named MvcWebsiteFramework
.
We can use this class library to add a whole bunch of commonly used code over time that will speed up our web site development. To start, we will look at how we can build a solution for working with the Google Analytics javascript and meta data verification tags.
MVC has a class, System.Web.Mvc.HtmlHelper
, that has been around since the beginning to help render common HTML content. The core MVC framework is littered with extension methods that piggy back on this class to render things like form fields with Html.TextBox("MyField")
. The Html
in Html.TextBox
is actually a property (that is of type System.Web.Mvc.HtmlHelper
) of the System.Web.Mvc.ViewPage
object available to each view. We can easily follow this pattern and create our own extension methods for the HtmlHelper
object.
Before we write our extension methods, lets go over the markup that we will need to render.
The current Google Analytics tracking JavaScript looks like so:
The only piece of this JavaScript that changes from site to site is the UA number (UA-11111111-1 in our example code above). We will need a way to make that dynamic.
The meta tag for site verification with Google looks like:
The meta tag for site verification with Bing looks like:
Both of these will need to have a way to replace SOME_KEY with a unique value for each site.
Time to write some code. We will add a directory called HtmlHelpers
to our project to keep our code organized and create two new class files in it, Bing.cs
and Google.cs
. We will put our extension methods that help us render our JavaScript and html into these classes.
The Bing.cs
class:
using System.Web.Mvc;
namespace MvcWebsiteFramework.HtmlHelpers
{
public static class Bing
{
public static MvcHtmlString BingSiteVerificationMetaTag(this HtmlHelper helper, string key)
{
var html = string.Format("", key);
return MvcHtmlString.Create(html);
}
}
}
To create an extension method we need to make a static class and add a static method that takes in a reference to the object that the method will extend off of. Since we will be extending the System.Web.Mvc.HtmlHelper
class we need to add that as our first method argument and use the keyword this
. The second argument will be a string
that will contain the key we get from Bing to verify our new site. Our method will return a System.Web.Mvc.MvcHtmlString
object. The MVC engine expects this object to already be HTML-encoded and as a result, it won't apply any additional encoding. So when we use the Razor syntax to call the extension method it will not result in our html from our extension method getting encoded (and pieces like our greater than and less than tags will not get converted to > and <).
@Html.BingSiteVerificationMetaTag("THE_BING_KEY")
The name of our class isn't that important here as it is just acting as a container for our extension method. As you can see above, we will typically call this method off of an existing System.Web.Mvc.HtmlHelper
object and not directly with Bing.BingSiteVerificationMetaTag
. However, I have found it handy to use the name of this class to help keep my code organized and easy to read through when I have to come back to it 6 months from now because Bing or Google decides to do their verification or tracking code in a different way. One thing to note though, since we will be calling the extension methods off of the HtmlHelper
object we cannot rely on our class or namespace structure to keep these methods unique. So we couldn't name the extension method SiteVerificationMetaTag
in both the Bing
and Google
class because the compiler will not know which one to pick (the call would be ambiguous). Thus, we need to make each name unique and can do that by appending either Bing or Google to the method name.
The Google.cs
class is going to have a method for the meta tag as well as a method for the tracking JavaScript:
using System;
using System.Text;
using System.Web.Mvc;
namespace MvcWebsiteFramework.HtmlHelpers
{
public static class Google
{
public static MvcHtmlString GoogleSiteVerificationMetaTag(this HtmlHelper helper, string key)
{
var html = string.Format("", key);
return MvcHtmlString.Create(html);
}
public static MvcHtmlString GoogleAnalyticsTrackingScript(this HtmlHelper helper, string accountId)
{
var html = new StringBuilder("");
return MvcHtmlString.Create(html.ToString());
}
}
}
The GoogleSiteVerificationMetaTag
looks just like the BingSiteVerificationMetaTag
with the only difference being the meta tag name
attribute value specific for Google. The GoogleAnalyticsTrackingScript
gets a little bit nasty with the StringBuilder
class, but there is really no clean approach to crafting this JavaScript. If we used the string.Format
approach then we would have had to escape all of the curly brackets with a double curly. If we went with a multi-line string definition and closed/opened the string to inject the accountId
then we would have had to start the re-open with another @. Multiple ways to skin a cat, but really no way to make that naked feline look any prettier.
With this framework in place we are ready to start utilizing it in new web site projects, which brings us to the next segment of our article:
Using NuGet to manage installation of our framework into new projects
If we build a NuGet package out of our framework library we can then use the to quickly install our framework into new projects. There is a lot that goes into creating and delivering a NuGet package (you can learn all of the details on the page of the NuGet CodePlex site), so lets see if we can simplify that a bit to get up and running fast. Here is an outline of what we need to accomplish:
- Make sure you have the NuGet Package Manager installed in Visual Studio 2010.
-
the
NuGet.exe
application that will handle the build of our NuGet package. - Add a
.nuspec
manifest file that theNuGet.exe
application will use. - Integrate the build of our package into our class library project.
- Build our class library and verify that our NuGet package was created.
- Set up the NuGet Package Manager to know where our package is at.
- Verify that we can install the package with the NuGet Package Manager into a new web site project.
Assuming that you have the NuGet Package Manager installed and have downloaded the NuGet.exe
, lets look at how we can create a .nuspec
manifest file and integrate the execution of the NuGet.exe
application into our build process. Begin by adding a new XML file named MvcWebsiteFramework.nuspec
to the root of the MvcWebsiteFramework
project. The content of the file:
MvcWebsiteFramework
1.0.0
Justin Schwartzenberger
MvcWebsiteFramework is a class library of common MVC3 web site code that can be used as a
foundation for a new web site project.
There is a great post by Vadim Kreynin called Baby Steps to Create a NuGet Package that describes how the fields in the manifest file are used in the NuGet Package Manager application.
With the manifest file in place we can turn our attention to the build process for our project. We want to have our build process automatically run the NuGet.exe
application to build our package. For this to happen, we will need to include the NuGet.exe
in our solution. Add a New Solution Folder named Tools
to the solution, navigate to the solution folder in Windows Explorer and create a new directory named Tools
, put your downloaded NuGet.exe
file into that directory, and then back in Visual Studio right click on the Solution -> Tools directory and Add -> Existing Item to add the Tools/NuGet.exe
file. Our solution tree should now look like so:
If we double click on the MvcWebsiteFramework
project Properties and click the Build Events tab we can edit the Post-build event command line value to add a command to call the NuGet.exe
application:
"$(SolutionDir)Tools\NuGet.exe" pack "$(ProjectDir)MvcWebsiteFramework.nuspec"
This will execute NuGet.exe
from within our solution Tools directory, call the pack
option, and reference the manifest file located in our project directory. A new file called MvcWebsiteFramework.1.0.0.nupkg
will get created in our build output directory.
NOTE
I downloaded theNuGet.exe
from the and put it into the Tools dir, and upon building my project with the above build event for the first time the call toNuGet.exe
resulted in the application self-updating its version, and thus the NuGet package wasn't created. A second build resulted in the package build since myNuGet.exe
was up to date at that point. So you can either do what I did and be aware of that gotcha, or you could open a command line to the Tools dir and runNuGet.exe
with no arguments to have it self-update prior to running your project build for the first time with the build event in place.
Next we need to update the settings for the NuGet Package Manager in Visual Studio to make it aware of our package location. In Visual Studio, go to Tools -> Options to bring up the Options dialog. In the tree on the left, expand the Package Manager node and click on Package Sources. In the Name field we can put "MvcWebsiteFramework". Click on the ellipses next to the Source field to browse for the folder that contains our MvcWebsiteFramework.1.0.0.nupkg
file (located in the /bin/Debug
dir in our MvcWebsiteFramework project dir). When finished, click the "Add" button to add the package source.
We should now see our package location in the list:
NOTE
I added the path to thebin/Debug
simply to illustrate how to add a package source without having to show and explain a bunch of other steps. When you are ready to actually use this process it will be ideal to either have a deploy process upon build to put your NuGet package in some sort of shared file dir or even set up a stand alone NuGet package feed site and have your package deployed as a part of that.
Finally, we are ready to create a new MVC3 Web site and use the NuGet Package Manager to add our framework library. Create a new MVC 3 project, select the Internet Application template, and target the Razor view engine. This will give us a project with some controllers and views already set up so we can test after we install our package. From here we can right click on the project name and select Add Library Package Reference... to bring up the NuGet Package Manager dialog. In the nav tree on the left we can see our MvcWebsiteFramework package source listed below the official NuGet source. If we select that we can see the package we created along with the details from our manifest file.
Click the Install button and NuGet will work its magic, adding our dll and adding a reference to it in our project. We are all set to start using our code. Open up the Views/Shared/_Layout.cshtml
file and add a @using
statement to reference our namespace for our classes that contain our extension methods and then add the calls to our HtmlHelper
extension methods:
@using MvcWebsiteFramework.HtmlHelpers
@ViewBag.Title
@Html.GoogleSiteVerificationMetaTag("THE_GOOGLE_KEY")
@Html.BingSiteVerificationMetaTag("THE_BING_KEY")
@Html.GoogleAnalyticsTrackingScript("UA-11111111-1")
My MVC Application
@Html.Partial("_LogOnPartial")
@RenderBody()
Do an F5 and view the source on the page and we will see our meta tags and JavaScript rendered out.
And that's a wrap. We have created the foundation for a MVC3 web site library and set up a pipeline for easy deployment to new web site projects.
Where do we go from here?
- We could update our NuGet package to add a web.config transform that will add our namespace to the
node in the web.config so we don't have to put@using
statements in our views. You can learn more about that process . - We can add a build step to our framework project that deploys the NuGet package to a shared directory or create our own NuGet Package Feed site by reading Phil Haack's post and then update our NuGet Package Manager options in Visual Studio to point to one of those instead of our framework project
bin/Debug
directory (this is highly recommended!). - We could expand upon our
MvcWebsiteFramework
library to add other common functionality or even include Controller class code like the Search Engine Crawler Access code from one of my previous posts. - We could add a Unit Test project to our framework solution and learn how to do unit testing on
HtmlHelper
extension methods by using mockHttpContext
andViewDataContainer
objects. Check out Craig Stuntz’s great post on .
Updates to prevent Cross Site Scripting injection
To use the TagBuilder class you first need to add a reference to System.Web.WebPages
to the MvcWebsiteFramework
class library project as the TagBuilder
class now resides in that namespace in MVC3. Once that is added we can begin with the updates to our code.
Updates to the Bing HtmlHelper
:
using System.Web.Mvc;
namespace MvcWebsiteFramework.HtmlHelpers
{
public static class Bing
{
public static MvcHtmlString BingSiteVerificationMetaTag(this HtmlHelper helper, string key)
{
var html = new TagBuilder("meta");
html.MergeAttribute("name", "msvalidate.01");
html.MergeAttribute("content", key);
return MvcHtmlString.Create(html.ToString(TagRenderMode.SelfClosing));
}
}
}
The update for the Google HtmlHelper
is the same for the meta tag, but the analytics tracking code is a bit different. The analytics code is a script
tag that can be created with the TagBuilder
, but we need to put the contents of the tab in using the TagBuilder.InnerHtml
property. Since the TagBuilder.InnerHtml
will not do anything to protect us from XSS we need to handle the accountId
prior to building that content. What we can do is make a call to the HttpUtility.HtmlEncode
class to update our accountId
variable first. If some malicious value was sent in via the GoogleAnalyticsTrackingScript
method call this would handle that. Then we can start our TagBuilder
instance for a tag, build our the content of it with our
StringBuilder
and set the TagBuilder.InnerHtml
property with that string, then return the tag.
using System;
using System.Text;
using System.Web;
using System.Web.Mvc;
namespace MvcWebsiteFramework.HtmlHelpers
{
public static class Google
{
public static MvcHtmlString GoogleSiteVerificationMetaTag(this HtmlHelper helper, string key)
{
var html = new TagBuilder("meta");
html.MergeAttribute("name", "google-site-verification");
html.MergeAttribute("content", key);
return MvcHtmlString.Create(html.ToString(TagRenderMode.SelfClosing));
}
public static MvcHtmlString GoogleAnalyticsTrackingScript(this HtmlHelper helper, string accountId)
{
accountId = HttpUtility.HtmlEncode(accountId);
var html = new TagBuilder("script");
html.MergeAttribute("type", "text/javascript");
var javascriptCode = new StringBuilder("");
javascriptCode.Append(Environment.NewLine);
javascriptCode.Append("var _gaq = _gaq || [];");
javascriptCode.Append(Environment.NewLine);
javascriptCode.Append(string.Format("_gaq.push(['_setAccount', '{0}']);", accountId));
javascriptCode.Append(Environment.NewLine);
javascriptCode.Append("_gaq.push(['_trackPageview']);");
javascriptCode.Append(Environment.NewLine);
javascriptCode.Append("(function() {");
javascriptCode.Append(Environment.NewLine);
javascriptCode.Append("var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;");
javascriptCode.Append(Environment.NewLine);
javascriptCode.Append("ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') ");
javascriptCode.Append(Environment.NewLine);
javascriptCode.Append("+ '.google-analytics.com/ga.js';");
javascriptCode.Append(Environment.NewLine);
javascriptCode.Append("var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);");
javascriptCode.Append(Environment.NewLine);
javascriptCode.Append("})();");
javascriptCode.Append(Environment.NewLine);
html.InnerHtml = javascriptCode.ToString();
return MvcHtmlString.Create(html.ToString(TagRenderMode.Normal));
}
}
}
The final step to take is to update our MvcWebsiteFramework.nuspec
file to bump up the version of our class library package. This will let NuGet know that there is an update and we will be able to open up projects that have installed the class library and use the Add Library Package Reference... dialog to do an Update of our package. The change to the MvcWebsiteFramework.nuspec
file looks like so:
MvcWebsiteFramework
1.0.1
Justin Schwartzenberger
MvcWebsiteFramework is a class library of common MVC3 web site code that can be used as a foundation
for a new web site project.
Discussion
Justin Schwartzenberger
pointed out via Twitter that there is already a helper for Google Analytics, which is cool. That means we could refactor that out of our sample framework library and make room for other stuff! I must say though, it was a challenge tracking down information on said helper. But never the less, info has been found! There is a package available on the main NuGet feed called ASP.NET Web Helpers Library that contains a class called
Microsoft.Web.Helpers.Analytics
with a method calledGetGoogleHtml
. The good news is that it works like a champ in a view using the following:The bad news is that installing the package from NuGet added an
App_Code
and aFacebook
dir to the project:And a quick F5 of the application resulted in the following exception:
So the existing helper that is out there is awesome, but it looks like it may come with some baggage. Maybe there is an easier (or lighter weight) way to include the
Microsoft.Web.Helpers.Analytics
helpers?Damien Guard
Please never take a string from a user into a HtmlHelper and let it get through unencoded out as an MvcHtmlString as you're opening up XSS holes.
The best way to prevent this is to never use String.Format (and even better avoid StringBuilder entirely) when building HTML and instead use the TagBuilder class which knows how to properly encode attributes using MergeAttributes and encode text using InnerText.
[)amien
Preston
Damien, how does this open you up to XSS? I get the idea of not simply rendering input from a user, however this is helper is only used server side. The input is coming from the template when it's compiled, not from any runtime input like the query string or a form post.
Damien Guard
Sure, in this exact example it's unlikely should never fill in that field from user-supplied data.
But people copy code and examples and sooner or later some version or alternate helper will take user data and a hole will open up and the signatures to HtmlHelper method doesn't clarify which parameters are safe and which are not.
It's just a lot simpler to make sure it is encoded and everyone can trust HtmlHelper extensions to always do the right thing like the ones Microsoft ships.
[)amien
Andrei Ignat
Damien What about a blog post with the correct code? Thanks, Andrei
Justin Schwartzenberger
Thanks everyone for the discussion! Your contributions help to make this content better. Lets take a look at how we can refactor the code to implement the TagBuilder logic to help prevent potential XSS as per Damien's suggestion.
I added a section to the bottom of the post called Updates to prevent Cross Site Scripting injection.
Adam
Great article. I like the security spin. To contribute, HTML encoding is inappropriate when injecting an arguably user-defined string into JavaScript. HTML encoding is only intended to protect HTML injection, and while the HttpUtility encodes single and double quotes, it does not protect JavaScript from a line break (\r\n). Regardless of malicious capacity, JSON encoding will ensure that properly formed JavaScript is written in a restricted form without changing the value.
Cheers.