Javascript Organization for MVC
Introduction
One of my pet peeves is code organization when it comes to putting together an application architecture. I have a current application that is built in ASP.NET MVC 2, using IOC, dependency injection, mutliple layers of abstraction from the business layer back.
But, I have found that after it was all said and done...there were .js files a plenty for performing all of the UI validation, presentation, AJAX calls so and so forth. Most of the scripts were in seperate files, but there was also a whole bunch within the views. Debugging and handing this off the the next developer became a challenge.
The goal was to refactor the organiztion and allow to slowly migrate to a better understanding if what executed where and what part of the script file was responsible for what view. The solution - ScriptViewPage.cs
The ScriptViewPage
The ScriptViewPage class inherits System.Web.Mvc.ViewPage and when instantiated, adds an event handler for the PreLoad event. So when the view renders, or pre-renders, the event is fired and the ScriptViewPage now handles and determines what View is executing and tries to locate the script file that is associated with it.
namespace System.Web.Mvc {
public class ScriptViewPage : ViewPage
{
public ScriptViewPage()
: base()
{
this.PreLoad += new EventHandler(_PreLoad);
}
Within the handler, we dig into the ViewContext.RouteData to determine the Controller and View and format the .js script file name.
string viewScriptPath = string.Format("~/ViewScripts/{0}/{1}.js", ViewContext.RouteData.Values["Controller"],
ViewContext.RouteData.Values["Action"]);
Test to see if the file exists and using the TagBuilder; create the script tag and set the text of the Literal control on the Master template/View if the control exists.
if (System.IO.File.Exists(MapPath(viewScriptPath)))
{
var scr = new TagBuilder("script");
scr.Attributes.Add("type", "text/javascript");
scr.Attributes.Add("src", ResolveClientUrl(viewScriptPath));
Control ctrl = Page.Master.FindControl("viewScript");
if (ctrl != null)
((Literal)ctrl).Text = scr.ToString(TagRenderMode.Normal);
}
Here is the full class code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace System.Web.Mvc
{
public class ScriptViewPage : ViewPage
{
public ScriptViewPage()
: base()
{
this.PreLoad += new EventHandler(_PreLoad);
}
void _PreLoad(object sender, EventArgs e)
{
string viewScriptPath = string.Format("~/ViewScripts/{0}/{1}.js", ViewContext.RouteData.Values["Controller"], ViewContext.RouteData.Values["Action"]);
if (System.IO.File.Exists(MapPath(viewScriptPath)))
{
var scr = new TagBuilder("script");
scr.Attributes.Add("type", "text/javascript");
scr.Attributes.Add("src", ResolveClientUrl(viewScriptPath));
Control ctrl = Page.Master.FindControl("viewScript");
if (ctrl != null)
((Literal)ctrl).Text = scr.ToString(TagRenderMode.Normal);
}
}
}
}
All you need to make sure of is that the .js files are named the same as your views.
Implementation
I needed a way to slowly refactor the existing code, but also allow for the new script organiztion to be used for all new view/scripts added to the application. Here is the direction I took.
Points of Interest
There may be other ways to accomplish the same task, but this was the easiest method to take in order to not have to restructure the current application and be able to organize go forward and have a structure to refactor to for existing scripts. Enjoy.