Handling Scripts in ASP.Net MVC

29. June 2009 15:39

Okay, so you have a bunch of script references, some added via your master template, others in your views.  You'd also like to be able to handle script adds in your partial views.  The problem is, you don't want your views, and partial views to know about the implementation details.  What I am going to do, is outline my solution for adding needed scripts into a given view/output without having duplicate script tags, and allowing each master, view, and partial to call for all the scripts it will need.

public static void AddClientScript(this HtmlHelper helper, string scriptPath)
{
  var scripts = helper.ViewContext.HttpContext.Items["client-script-list"] as Dictionary<string , string> ?? new Dictionary<string , string>();
			
  string scriptFilePath = helper.ViewContext.HttpContext.Server.MapPath(scriptPath);
  if (!File.Exists(scriptFilePath)) return;
			
  var fi = new FileInfo(scriptFilePath);
			
  if (scripts.ContainsKey(fi.FullName)) return;
			
  scripts.Add(fi.FullName, scriptPath);
  helper.ViewContext.HttpContext.Items["client-script-list"] = scripts;
}

Each script needed will simply call the HtmlHelper extension method above.  As you can see, a generic collection is used, and stored in the HttpRequest.Current.Context.items collection.  This allows for any level of view, code behind, controller, or other instances to actually add scripts that would need to be used within the page fairly easily.

public static void ClientScripts(this HtmlHelper helper)
{
  var response = helper.ViewContext.HttpContext.Response;
  var url = new UrlHelper(helper.ViewContext.RequestContext, helper.RouteCollection);
  var scripts = helper.ViewContext.HttpContext.Items["client-script-list"] as Dictionary<string , string> ?? new Dictionary7lt;string , string>();
			
  response.Write("\r\n\t<!-- BEGIN - Html.ClientScripts() -->\r\n");
  foreach (var script in scripts)
  {
    response.Write(string.Format(
      "\t<script src=\"{0}\" type=\"text/javascript\">lt;/script>\r\n", 
      url.Content(script.Value)
    ));
  }
  response.Write("\t<!-- END - Html.ClientScripts() --7gt;\r\n");
}

The code above shows the HtmlHelper extension method that needs to be added within the Master/View.  My own preference is to make the call directly before the closing body tag (</body>), this is because I prefer to avoid inline scripts altogether, and include the necessary scripts at the end.  Also, I prefer to use event binding, instead of using the on* attributes within the html tags.   By injecting scripts this way, you get a fairly clean separation of concerns, can assure that scripts necessary for a given view or partial view are included, and can utilize project level paths without issue.

--- From each view / partial view ---
<%Html.AddClientScript("~/Scripts/jquery.js");%>
<%Html.AddClientScript("~/Scripts/jquery-ui.js");%>
<%Html.AddClientScript("~/Scripts/site.js");%>
<%Html.AddClientScript("~/Scripts/views/myview.js");%>

--- From the main Master/View ---
    <%Html.ClientScripts();%>    
lt;/body>

-- EDIT: 2009-08-20 --

Chris Pietschmann made a post about his implementation for a SimpleScriptManager for ASP.Net MVC with some of the features I had mentioned. In the comments from this post is a mention to a pretty cool project on Codeplex, ASP.NET MVC Client-side Resource Combine.

Either of these are decent uses for within ASP.Net MVC, the actual combine project on codeplex wouldn't even be limited to use with MVC, though it makes more sense with MVC, since the ScriptManager that comes with ASP.Net Ajax is a decent solution for that paradigm.

Comments

6/30/2009 10:21:31 AM #

justin

Nice article. Just one recommendation, use the helper.ViewContext.HttpContext (or maybe it's helper.ViewContext.ControllerContext.HttpContext can't remember off the top of my head) rather than HttpContext.Current.  This way you can mock it and test it.

justin United States |

6/30/2009 5:22:46 PM #

tracker1

@justin, thanks for the suggestion...  Wanting to make this into a script-manager type of use case, along with potentially some built-in minification.  Will likely make the change there.

tracker1 United States |

6/30/2009 5:24:04 PM #

tracker1

Actually, will probably make the helper extensions off of HttpContext, then the this object.Items can be referenced directly, and have other extensions for the more local contexts.

tracker1 United States |

8/20/2009 10:23:11 AM #

tracker1

Okay, made a few changes to this and added some additional references.

tracker1 United States |

Comments are closed

Tracker1

Michael J. Ryan aka Tracker1

My name is Michael J. Ryan and I've been developing web based applications since the mid 90's.

I am an advanced Web UX developer with a near expert knowledge of JavaScript.