Tag Archives: c#

Inside RazorScriptManager

22 Jun

When you add the RazorScriptManager NuGet package to your project, three things happen: Script.cshtml is added to /App_Code, RazorScriptManager.cs is added to /Handlers, and a web.config transform is performed. The first file provides the Razor helper methods used in your views. The second provides the HttpHandler that handles the combining/compression and responds to requests for scripts.axd. The web.config transform adds a couple settings and registers the HttpHandler.

Script.cshtml

There are four Razor helpers added as part of the NuGet package. Two for adding script references to the response, and two for writing out script tags for the response. One of each type is provided for CSS and JavaScript.

Inside the two Add methods (AddCss() and AddJavaScript()), a new ScriptInfo object is created for the referenced script. That ScriptInfo object contains the script type, local path, CDN path, and whether the script is used site-wide. The ScriptInfo object is then added to a List<ScriptInfo> that’s kept in Session.

@helper AddJavaScript(string localPath, string cdnPath = null, bool siteWide = false) {
	var scriptType = ScriptType.JavaScript;
	//create a session key specifically for javascript ScriptInfo objects
	var key = "__rsm__" + scriptType.ToString();
	//If the List doesn't exist, create it
	if (Session[key] == null) {
		Session[key] = new List();
	}
	//pull out the current (or new) list - it may already have other ScriptInfo objects
	var scripts = Session[key] as List;
	//add the current ScriptInfo
	scripts.Add(new ScriptInfo(Server.MapPath(localPath), cdnPath, scriptType, siteWide));
	//put the list back in Session
	Session[key] = scripts;
}

In the Output methods (OutputCss() and OutputJavaScript()), the List<ScriptInfo> is extracted from Session. Based on web.config settings, a list of CDN-hosted scripts may be extracted. The helper then writes out <script> or <link> tags for CDN-hosted scripts (if any) and for the HttpHandler path. An MD5 hash is generated from the filenames of the referenced local scripts and appended to the HttpHandler path. This MD5 is used to cache the combined/compressed output in the HttpHandler, and will be explained more in that section.

@helper OutputJavaScript() {
	var scriptType = ScriptType.JavaScript;
	//create a session key specifically for javascript ScriptInfo objects
	var key = "__rsm__" + scriptType.ToString();
	//if no scripts have been added, don't do anything
	if (Session[key] == null) return;
	//pull out the current list from Session
	var scripts = Session[key] as List;
	var cdnScripts = new List();
	//if the web.config says to use CDN-hosted scripts, extract then from the list into cdnScripts
	if (bool.Parse(System.Configuration.ConfigurationManager.AppSettings["UseCDNScripts"])) {
		//get all scripts without a CDN path
		var localScripts = scripts.Where(s => string.IsNullOrWhiteSpace(s.CDNPath)).ToList().ToList();
		//get all scripts that aren't local-only scripts
		cdnScripts = scripts.Except(localScripts).ToList();
		//put the local scripts back into session (CDN scripts are handled here, not in the HttpHandler)
		Session[key] = localScripts;
	}

	//write out the CDN scripts to the response
	foreach (var cdnScript in cdnScripts) {
<script type="text/javascript" src="@cdnScript.CDNPath"></script>}

	//generate a unique hash based on the filenames
	var hash = HttpUtility.UrlEncode(RazorScriptManager.GetHash(scripts));
	//write out a script tag for the HttpHandler using the script type and hash<script type="text/javascript" src="/scripts.axd?type=@scriptType.ToString()&hash=@hash"></script>
}

RazorScriptManager.cs

The class file for the HttpHandler also contains the definitions for ScriptInfo, ScriptType and ScriptInfoComparer. These classes represent a script reference, the type of script, and a way of comparing two scripts. The comparer is used later to eliminate duplicate script references (e.g. if you have a reference to the same jQuery file on your Layout and a partial view, it will only use one). The RazorScriptManager class itself (which is the actual HttpHandler) provides an instance method for responding to requests (ProcessRequest()) and a static method for generating a hash (GetHash()). GetHash() works by appending the distinct list of script paths into a single string, then generating a standard MD5 hash of that string.

public static string GetHash(IEnumerable scripts) {
	var input = string.Join(string.Empty, scripts.Select(s => s.LocalPath).Distinct());
	var hash = System.Security.Cryptography.MD5.Create().ComputeHash(Encoding.ASCII.GetBytes(input));
	var sb = new StringBuilder();
	for (int i = 0; i < hash.Length; i++)
		sb.Append(hash[i].ToString("X2"));
	return sb.ToString();
}

ProcessRequest() is a little more involved. First, it determines the type of script being requested from the querystring value. Based on the type, it sets the response’s content type appropriately.

var scriptType = (ScriptType)Enum.Parse(typeof(ScriptType), context.Request.Params["type"]);

switch (scriptType) {
	case ScriptType.JavaScript:
		context.Response.ContentType = @"application/javascript";
		break;
	case ScriptType.Stylesheet:
		context.Response.ContentType = @"text/css";
		break;
}

After setting content type, the method checks the application cache to see if a combined/compress script has already been generated for that particular set of files. To do this, it uses the hash as a cache key. If the script output already exists, the method immediately returns the cached output and no further processing is required.

var hashString = context.Request.Params["hash"];
if (!String.IsNullOrWhiteSpace(hashString)) {
	var result = cache[HttpUtility.UrlDecode(hashString)] as string;
	if (!string.IsNullOrWhiteSpace(result)) {
		context.Response.Write(result);
		return;
	}
}

If the output wasn’t already cached, the method pulls the List<ScriptInfo> for the current type out of Session. It then obtains the distinct scripts from the list using the ScriptInfoComparer and reorders them based on whether or not they were marked as site-wide. Site-wide scripts (like jQuery) need to be loaded first, so that other script can take advantage of their methods. At this point, the contents of each file are added to a single string. This string will become the combined and compressed single script that’s returned by the handler.

var scripts = context.Session["__rsm__" + scriptType.ToString()] as IEnumerable;
context.Session["__rsm__" + scriptType.ToString()] = null;
if (scripts == null) return;
var scriptbody = new StringBuilder();

scripts = scripts.Distinct(new ScriptInfoComparer());

//add sitewide scripts FIRST, so they're accessible to local scripts
var siteScripts = scripts.Where(s => s.SiteWide);
var localScripts = scripts.Where(s => !s.SiteWide).Except(siteScripts, new ScriptInfoComparer());
var scriptPaths = siteScripts.Concat(localScripts).Select(s => s.LocalPath);
var minify = bool.Parse(ConfigurationManager.AppSettings["CompressScripts"]);

foreach (var script in scriptPaths) {
	if (!String.IsNullOrWhiteSpace(script)) {
		using (var file = new System.IO.StreamReader(script)) {
			var fileContent = file.ReadToEnd();
			if (scriptType == ScriptType.Stylesheet) {
				var fromUri = new Uri(context.Server.MapPath("~/"));
				var toUri = new Uri(new FileInfo(script).DirectoryName);
				fileContent = fileContent.Replace("url(", "url(/" + fromUri.MakeRelativeUri(toUri).ToString() + "/");
			}
			if (!minify) scriptbody.AppendLine(String.Format("/* {0} */", script));
			scriptbody.AppendLine(fileContent);
		}
	}
}
string scriptOutput = scriptbody.ToString();

If CompressScripts is set to true in the web.config, run the appropriate minifier for the current script type. Side note: there’s some interesting asymmetry within the YUI Compressor: for JavaScript the compress method is an instance method, while for CSS the compress method is a static method.

string scriptOutput = scriptbody.ToString();
if (minify) {
	switch (scriptType) {
		case ScriptType.JavaScript:
			var jscompressor = new Yahoo.Yui.Compressor.JavaScriptCompressor(scriptOutput);
			scriptOutput = jscompressor.Compress();
			break;
		case ScriptType.Stylesheet:
			scriptOutput = Yahoo.Yui.Compressor.CssCompressor.Compress(scriptOutput);
			break;
	}
}

Finally, save the output to the cache (for next time!) and send the output as the reponse.

var hash = GetHash(scripts);
cache[hash] = scriptOutput;
context.Response.Write(scriptOutput);

web.config.transform

Two appSettings are added to the web.config: UseCDNScripts and CompressScripts. The first determines whether or not the Output Razor helpers write out tags for the CDN paths. The second determines whether or not the <code>HttpHandler</code> compresses the combined output before returning the response.

<appSettings>
  <add key="UseCDNScripts" value="false" />
  <add key="CompressScripts" value="false" />
</appSettings>

The HttpHandler is also registered in the web.config. One version for IIS6, another for IIS7.

<system.web>
  <httpHandlers>
    <add verb="*" path="scripts.axd" type="RazorScriptManager.RazorScriptManager"/>
  </httpHandlers>
</system.web>
<system.webServer>
  <handlers>
    <add name="ScriptManager" verb="*" path="scripts.axd" type="RazorScriptManager.RazorScriptManager"/>
  </handlers>
</system.webServer>
Advertisements

Using the Range Attribute With Decimal or DateTime

15 Jun

Learned something cool about System.ComponentModel.DataAnnotations.RangeAttribute today – it can actually be used with any type. It only includes numeric constructors for Int32 and Double, which are probably the two most common uses, but it also includes a constructor that takes a type and two string as parameters:

public RangeAttribute(
  Type type,
  string minimum,
  string maximum
)

The one caveat is that the type must implement the IComparable interface. Typically you wouldn’t be using the Range attribute to decorate a custom type, but as long as your type implemented IComparable, you’d be just fine. However, in today’s scenario we were just trying to validate a Decimal value in a model in an MVC2 application. Our solution looked something like this:

[Range(typeof(Decimal),"0", "9999999")]
public decimal Rate { get; set; }

The downside is that the minimum and maximum values have to be passed as strings, but since they have to be constants anyway (to work with the attribute). that’s not much of a setback. This can also easily be used with dates as well – just be sure to write the date in a usable format.

Named Sections in Razor

10 Jun

This past week I had to build out a mostly-HTML site in MVC3. Since there wasn’t anything challenging on the backend, I decided to go all out and see how DRY I could make my view code in Razor, and to see if I ran into anything I couldn’t do that I was able to do with the WebForms view engine. The first thing that I ran into (that I didn’t know how to do) was to reproduce the same functionality as ContentPlaceHolder. Fortunately, Named Sections fit the bill perfectly.

Named Sections allow you to specify extra areas in your layout file by calling RenderSection(). These areas have a name (obviously) and can be marked as required or optional. In your view, you simply wrap the view code for a section inside Razor tags, like this:

//_Layout.cshtml
@RenderBody()
<footer>@RenderSection("footer")</footer>

//Details.cshtml
<p>Page Content</p>

@section footer {
  <div>Footer content</div>
}

In a typical _Layout.cshtml file, you’ll have the basic HTML structure of your site and the Razor tag @RenderBody(). And in a View that uses this layout, you’ll have your page content in the root of the document. What I didn’t understand was that MVC is essentially treating @RenderBody() as @RenderSection(“body”) and wrapping the primary (un-nested) content of your View file in @section body { }. The idea was to make the 90% use-case scenario as easy as possible, and they definitely accomplished that goal.

But what about default content in a section? I don’t want to have to specify the same footer code on every single view in my site, just so I can override it on one page. The easy way is to call IsSectionDefined() in the layout to see if the view contains the section, but that requires wrapping a (potentially large) section of view code in an if statement.

However, it is possible to extend RenderSection to take a default content parameter. Fortunately Phil Haack (who knows a little bit about how MVC3 works) covered this in a blog about layout sections, so I don’t have to. Put simply, it’s possible to write an extension method that takes a Razor block of HTML. It’s a little convoluted, but in the right scenario it can work great.

For me, Named Sections have turned out to be incredibly useful, and will definitely save me plenty of time in the future.

AutoMapperPagedList

9 Jun

If you’re just looking for an example of how to use AutoMapperPagedList, drop down to the bottom and look at the last code sample.

A couple months ago (while at MIX 11, actually) I was playing with some of the new MVC3 Tooling, including EF4.1. To play with it, I was trying to build a simple blog, and for no real reason at all decided to use both AutoMapper and Scott Guthrie’s PagedList. For those unfamiliar with either, AutoMapper is a great way to transform your data access objects into view models, and PagedList makes it really simple to hook up an IQueryable to server-side paging.

First, an explanation of AutoMapper. In creating AutoMapper, Jimmy Bogard provided a super-useful way to automatically map one object to another type. The mapping is primarily based on conventions, so if you name the properties on the classes properly, you only have to do minimal configuration. For more details, see the examples on AutoMapper’s CodePlex site. For my simple blog, I wanted to use AutoMapper to flatten my Post model into a PostViewModel. Because PostViewModel was set up to easily work with AutoMapper, the only configuration I had to do was make sure that this line was called in my Global.asax:

AutoMapper.Mapper.CreateMap<Post, PostViewModel>();

Then when I want to convert a Post to a PostViewModel, I can just call this:

AutoMapper.Mapper.Map<Post, PostViewModel>(post);

Next, an explanation of PagedList. The Gu created a helpful class and extension methods for working with server-side paging. The extension method extends an IQueryable<T> and converts it to a PagedList<T>. PagedList<T> inherits from List<T>, so it has all the normal features of a List<T>, but it adds the interface IPagedList:

public interface IPagedList {
	int TotalCount { get; set; }
	int PageIndex { get; set; }
	int PageSize { get; set; }
	bool IsPreviousPage { get; }
	bool IsNextPage { get; }
}

The properties added by IPagedList can be used in your view to easily control Previous/Next buttons. Additionally, the constructor of PagedList calls Skip() and Take() on the IQueryable before calling ToList() and adding the resultant items to itself, meaning that the execution of the IQueryable happens inside of PagedList. This is important because AutoMapper won’t work as part of deferred execution, which is what is going on with the IQueryable. This means that the trying to use AutoMapper to map an IQueryable<Post> to an IQueryable<PostViewModel> just can’t happen.

So since I can’t use AutoMapper to send objects to PagedList, I had to use AutoMapper inside PagedList, after the IQueryable was executed and returned as a List. However, I didn’t want to muck around too much with the regular PagedList class, so I created a new class that inherits from it instead. This new class is called (quite creatively) MappedPagedList. It works the exact same way as PagedList, but takes one additional generic type and one additional parameter. It requires two generic types because AutoMapper needs a source type and destination type. The extra parameter, however, is the key. As parameter types go, it’s a doozy:

public static MappedPagedList<TSource, TOutput> ToPagedList<TSource, TOutput>(this IQueryable<TSource> source, int index, Func<IEnumerable<TSource>, IEnumerable<TOutput>> mapper, int pageSize = 10) {
	return new MappedPagedList<TSource, TOutput>(source, index, pageSize, mapper);
}

Let me break it down a little.
Func<IEnumerable<TSource>, IEnumerable<TOutput>>
looks intimidating, but it’s really just a delegate for a call to AutoMapper.Map<TSource, TOutput>(). In simpler terms, the parameter takes a method that has one parameter of type IEnumerable<TSource> and returns IEnumerable<TOutput>. The best way to understand it is probably to see an example of the method being called:

IQueryable<Post> posts = context.Posts.OrderByDescending(p => p.Timestamp).AsQueryable();
<PostViewModel> pagedList = posts.ToPagedList<Post, PostViewModel>(2, Mapper.Map<IEnumerable<Post>, IEnumerable<PostViewModel>>, 10);
return View(pagedList);

Because MappedPagedList<T,O> inherits from PagedList<T> (and PagedList<T> is the only part that we actually need in the view), the View only needs to be passed an instance of Paged<T>. The magic happens inside the constructor of MappedPagedList, where it uses the passed-in Func to map the List<T> to a List<O>.

If you’ve read this far, you’re probably getting desperate for the part where I say “…and here’s how you use it in your site”, so here you go.

//Controller
public ViewResult Index(int page = 0) {
	int pageSize = 10;
	IQueryable<Post> posts = context.Posts.OrderByDescending(p => p.Timestamp).AsQueryable();
	PagedList<PostViewModel> pagedList = posts.ToPagedList<Post, PostViewModel>(page, Mapper.Map<IEnumerable<Post>, IEnumerable<PostViewModel>>, pageSize);
	return View(pagedList);
}

//View
@if (Model.IsPreviousPage) {
	@Html.ActionLink("Previous", "Index", new { page = Model.PageIndex - 1 })
}
@(Model.PageIndex + 1) of @(Model.TotalCount / Model.PageSize)
@if (Model.IsNextPage) {
	@Html.ActionLink("Next", "Index", new { page = Model.PageIndex + 1 })
}

This will generate a previous button, a page indicator, a next button. The previous and next button are hidden if there isn’t a previous or next page. The controller takes page as an optional parameter, and passes that to the MappedPagedList to use in Skip().

So why would you want to use it? If you need to use AutoMapper to send view models to your view, but want super-easy paging support, this single file will save you a bunch of time and effort. I’ve put it on NuGet at http://nuget.org/List/Packages/AutoMapperPagedList, so feel free to pull it down and give it a try. It’s a single file, and the source is hosted on GitHub at https://github.com/davecowart/AutoMapperPagedList, so feel free to clone it, fork it, or do whatever. I also have an example MVC project on GitHub at https://github.com/davecowart/AutoMapperPagedList.Demo if you need a full-site example.

Named Routes in ASP.NET MVC 3

8 Jun

I’m a recent convert to named routes. Using a named route to create a link or URL gives a you level of explicitness that’s comforting in most situations and a lifesaver in others. In their simplest form, though, named routes lead to DRYer routing and can save you some serious maintenance headaches.

All routes are effectively named routes. Using a named route means to specify the route by name instead of allowing the routing engine to determine which one to use. This means that a route that you intend to reference by name is created the same as any other route:

routes.MapRoute(
	name: "Post",
	url: "Post/{id}",
	defaults: new { controller = "Posts", action = "Details", id = UrlParameter.Optional }
);

If you were to use this as a typical action link, you’d specify the link text, controller, action and any route values (in this case id) as parameters in Html.ActionLink(), like this:

@Html.ActionLink("Show", "Posts", "Details", new { id = 4 })

The downside of this technique is that if you were to change a controller name, change an action name or move an action, your link would break. And not just this link, but any ActionLink that points to that combination of controller and action. The other danger is that you have no guarantees as to which route will be pulled from the routing table. It’s easy to envision a scenario where you have to figure out which of 50 routes in the routing table is being used. With named routes, you avoid both of these problems, and as a side benefit your link code is shorter too. To use a named route link, you only need to specify the link text, the name of the route and any route values as parameters, like this:

@Html.RouteLink("Show", "Post", new { id = 4 })

This link is now protected against future change, takes up a little less space, and leaves no doubts as to which route will be utilized. The one negative is that there is now an extra step to find the action and controller used by the route, but in a sensibly-structured site it shouldn’t take a wild guess to figure it out. In my mind these benefits vastly outweigh the slight obfuscation. With named routes, you’ll always know what you get.

Extending Html.RouteLink()

7 Jun

The other day I was tasked with converting a raw HTML site into MVC3/Razor. Each individual HTML page had its own copy of the navigation menu, with the link to the current page manually set to include class=”current” in order to automatically expand the menu and style the link differently. However, I wanted to place the navigation menu into a partial so that I could reuse it between multiple layouts. That meant  needed to have a way to determine the current page, compare that to the URL in the link, and add the “current” class whenever the two matched.

I wanted to use named routes for these links. Named routes are great for maintainability, because the references to action and controller names are contained in a single place–Global.asax.  However, neither route links nor action links provide the functionality I was looking for, which meant I would need to extend the existing HtmlHelpers to add a new kind of link helper. I wanted to cause as little disruption as possible with the new helper, so I made sure to match the syntax of Html.RouteLink() as closely as possible.

First, I needed to create my helper method.

using System.Collections.Generic;
using System.Web.Routing;

namespace System.Web.Mvc.Html {
	public static class Html {
		public static MvcHtmlString NavigationRouteLink(this HtmlHelper<dynamic> htmlHelper, string linkText, string routeName, RouteValueDictionary routeValues = null, IDictionary<string, object> htmlAttributes = null) {
			return new MvcHtmlString(HtmlHelper.GenerateRouteLink(htmlHelper.ViewContext.RequestContext, RouteTable.Routes, linkText, routeName, routeValues, htmlAttributes));
		}
	}
}

Next, I needed a way to determine whether or not the current page’s URL matched the URL generated by the named route. To do that, I pulled the current request’s absolute path out of the HtmlHelper object, and I pulled the named route’s absolute path out of an instance of UrlHelper.

using System.Collections.Generic;
using System.Web.Routing;

namespace System.Web.Mvc.Html {
	public static class Html {
		public static MvcHtmlString NavigationRouteLink(this HtmlHelper<dynamic> htmlHelper, string linkText, string routeName, RouteValueDictionary routeValues = null, IDictionary<string, object> htmlAttributes = null) {
			if (htmlHelper.ViewContext.RequestContext.HttpContext.Request.Url.AbsolutePath == new UrlHelper(htmlHelper.ViewContext.RequestContext).RouteUrl(routeName))
				htmlAttributes.Add("class", "current");
			return new MvcHtmlString(HtmlHelper.GenerateRouteLink(htmlHelper.ViewContext.RequestContext, RouteTable.Routes, linkText, routeName, routeValues, htmlAttributes));
		}
	}
}

With that out of the way, I needed to make sure that I wouldn’t run into any problems if htmlAttributes were null (which is extremely likely).

using System.Collections.Generic;
using System.Web.Routing;

namespace System.Web.Mvc.Html {
	public static class Html {
		public static MvcHtmlString NavigationRouteLink(this HtmlHelper<dynamic> htmlHelper, string linkText, string routeName, RouteValueDictionary routeValues = null, IDictionary<string, object> htmlAttributes = null) {
			IDictionary<string, object> dict;
			if (htmlAttributes == null)
				dict = new Dictionary<string, object>();
			else
				dict = htmlAttributes;
			if (htmlHelper.ViewContext.RequestContext.HttpContext.Request.Url.AbsolutePath == new UrlHelper(htmlHelper.ViewContext.RequestContext).RouteUrl(routeName))
				dict.Add("class", "current");
			return new MvcHtmlString(HtmlHelper.GenerateRouteLink(htmlHelper.ViewContext.RequestContext, RouteTable.Routes, linkText, routeName, routeValues, dict));
		}
	}
}

As a possible improvement, I could allow the name of the assigned class to be passed in. Alternatively, I could provide another (required) htmlAttributes parameter, which would only be applied if the named route matched the current URL. However, given the small scope of the project and the extra effort involved, I decided I wasn’t going to need it. The final product was an easy-to-use HtmlHelper that allowed me to add navigation links to my heart’s content without having to stop and think about URL-comparison logic.

<div id="nav">
    <ul class="level1">
        <li>
            <a>Who We Are</a>
            <ul class="level2">
                <li>@Html.NavigationRouteLink("About Us", "About")</li>
                <li>@Html.NavigationRouteLink("The Blog", "Blog")</li>
            </ul>
        </li>
    </ul>
</div>

produces:

<div id="nav">
	<ul class="level1">
		<li>
			<a>Who We Are</a>
			<ul class="level2">
				<li><a class="current" href="/AboutUs">About Us</a></li>
				<li><a href="/Blog">The Blog</a></li>
			</ul>
		</li>
	</ul>
</div>