Click here to Skip to main content
Click here to Skip to main content

JavaScript Organization for ASP.NET MVC3 with Razor Engine

, 24 Mar 2011 CPOL
Rate this:
Please Sign up or sign in to vote.
A technique to automatically register JavaScript View files.

Introduction

A nice feature of ASP.NET MVC is how Controllers and Views become automatically related through method names, file names, and directory structure. Some Views can have some related JavaScript code that could be separated in files (js) and I was wondering if there were some patterns to organize these JavaScript and make them automatically included in each correspondent View.

After some research, I found this article by Shayne P Boyer but my project uses ASP.NET MVC 3 with the Razor View Engine.

Besides, I was concerned about Partial Views that could have their own related JavaScript file, and was thinking in a way to make these scripts included once on the page even if the same Partial View is used multiple times on the same Page.

Here I show a ViewPageBase that automatically references JavaScript files for the current View and for the inner Partial Views (using the structure suggested by Shayne and avoiding that the same file is registered more than once).

ViewPageBase

ViewPageBase inherits System.Web.Mvc.WebViewPage<TModel> and overrides the InitializePagemethod that will search for the related JavaScript file.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.WebPages;
using System.Text;
using System.IO;

namespace JSOrganization
{
    public abstract class ViewPageBase<T> : WebViewPage<T>
    {
        protected override void InitializePage()
        {
            base.InitializePage();
           
            AddScriptForView(this.VirtualPath);
        }

The AddScriptForViewmethod will check if the JavaScript file exists for the View and, if so, will keep the reference.

void AddScriptForView(string path)
{
    if (string.IsNullOrEmpty(path)) { return; }

    var dctScripts = GetScriptsDictionary();
    if (!dctScripts.ContainsKey(path))
    {
        //Change path to the javascripts folder
        path = path.Replace("~/Views/", "~/ViewScripts/");
        //Change the extension to .js
        path = Path.ChangeExtension(path, "js");

        //Check if the file exists
        if (File.Exists(Server.MapPath(path)))
        {
            //Build the javascript reference and keep it in the dictionary
            var tagBuilder = new TagBuilder("script");
            tagBuilder.Attributes.Add("type", "text/javascript");
            tagBuilder.Attributes.Add("src", Url.Content(path));
            dctScripts[path] = string.Format("{0}{1}", 
              tagBuilder.ToString(TagRenderMode.Normal), System.Environment.NewLine);
        }
    }
}

GetScriptsDictionarywill search, in the current HttpContext, the dictionary that keeps the JavaScript file references. At this point, the program identifies the first View processed, which is usually the View returned by the current Action and will include later all the script references in the final HTML.

bool isPrincipalView = false;
const string _viewScriptsKey = "__ViewScripts";
private Dictionary<string, string> GetScriptsDictionary()
{
    var ctx = Context;
    var dctScripts = default(Dictionary<string, string>);
    if (!ctx.Items.Contains(_viewScriptsKey))
    {
        //If the dictionary is not created yet, then this view is the principal
        isPrincipalView = true;
        dctScripts = new Dictionary<string, string>();
        ctx.Items[_viewScriptsKey] = dctScripts;

    }
    else
    {
        dctScripts = ctx.Items[_viewScriptsKey] as Dictionary<string, string>;
    }
    return dctScripts;
}

Then, ExecutePageHierarchyis overridden. It's where the "Principal View" inserts all the JavaScript references at the top of the HTML.

    public override void ExecutePageHierarchy()
    {
        base.ExecutePageHierarchy();

        //This code will execute only for the principal view,
        //not for the layout view, start view, or partial views.
        if (isPrincipalView)
        {
            var dctScripts = GetScriptsDictionary();
            var sw = this.Output as StringWriter;
            var sb = sw.GetStringBuilder();

            //add here the javascript files for layout and viewstart
            AddScriptForView(this.Layout);
            AddScriptForView("~/Views/_ViewStart.cshtml");

            //Insert the scripts
            foreach (var pair in dctScripts)
            {
                sb.Insert(0, pair.Value);
            }
        }
    }
}

public abstract class ViewPageBase : ViewPageBase<dynamic> { }

Using the base view class

To make it work, it's necessary that all the Views inherits from ViewPageBase. There are two ways to accomplish this.

One way is inheriting per-View, inserting the following clause at the top of each View:

@inherits JSOrganization.ViewPageBase  

Another way is to specify in Views/Web.config the base class for all the Views:

<system.web.webPages.razor>
    <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, 
            Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <pages pageBaseType="JSOrganization.ViewPageBase"> 

Project organization

The project structure must be like the following example:

organization.PNG

In the example above, each View has your associated JavaScript file in the ViewScripts folder. Remember that even a partial View can have its own JavaScript that will be referenced just once, even if the View is rendered multiple times in a parent View.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Guilherme Meinlschmiedt Abdo
Software Developer FUNDAP - Fundação do Desenvolvimento Administrativ
Brazil Brazil
No Biography provided
Follow on   Twitter

Comments and Discussions

 
GeneralNot sucha good idea.. PinmemberSeishin#25-Mar-11 3:36 
with your approach for every view there's an extra request for it's js file.
With one (minimized) js file you have one request and later on it's cached..
life is study!!!

GeneralRe: Not sucha good idea.. PinmemberGuilherme Meinlschmiedt Abdo25-Mar-11 4:13 
GeneralRe: Not sucha good idea.. PinmemberSeishin#25-Mar-11 14:44 
GeneralRe: Not sucha good idea.. PinmemberGuilherme Meinlschmiedt Abdo30-Mar-11 11:14 
GeneralRe: Not sucha good idea.. [modified] PinmemberSeishin#31-Mar-11 3:34 
GeneralSolid 4 Pinmembermelchizidech25-Mar-11 0:30 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.141220.1 | Last Updated 24 Mar 2011
Article Copyright 2011 by Guilherme Meinlschmiedt Abdo
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid