Dec 22 2009

Exposing the View Model to JavaScript in ASP.NET MVC

Category: MVC | JavaScriptMatt @ 17:24

The prevailing practice for moving data between the controller and the view in ASP.NET MVC applications is to utilize a view model.  While using a view model from within the view’s ASPX page is quite easy, utilizing it from JavaScript can be more complex.  While JavaScript blocks declared inline on the view page can easily consume values from the model, external script files cannot.  In order to take advantage of script batching and minimization, you should avoid the use of inline script blocks and instead use external JavaScript files (.js).  What happens when you need to reference a value from the view model in your JavaScript though?  Since the JavaScript files are not (by default) processed by the ASP.NET pipeline, it isn’t possible for them to leverage the Model; the Model exists server-side, while JavaScript is processed client-side. 

I’ve struggled with this limitation since the first preview release of ASP.NET MVC.  Here are a couple of the approaches that I tried (and hated):

Place scripts in partial views

Instead of following best-practices and placing JavaScript in an external script file, scripts can instead be placed in partial views.  This simplifies the main view by encapsulating the script, and it does allow the script to be re-used.  It also allows the script to easily reference values from the view model. 

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<AwesomeViewModel>" %>

<script type="text/javascript">
    
    alert('Hello from the view model: <%=Model.Hello%>');
    
</script>

The downside to this approach is that the script cannot be easily minimized or combined, and it can’t be cached by the browser since it is actually rendered inline in the final markup produced by the view. 

Pass view model properties through an initialization function

Another approach that I’ve used is to define an initialization function within my external JavaScript files.  Any values that are needed by the script can be extracted from the model within the main view, then passed to the external JavaScript through the initialization function.

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<AwesomeViewModel>" %>
...
<script type="text/javascript" src="ExternalScript.js"></script>
<script type="text/javascript">
    var name = "<%=Model.Name %>";
    ExternalScript_Init(name);
</script>
...

//Contents of ExternalScript.js
ExternalScript_Init(name) {
    alert("Hello from JavaScript: " + name);
}

This approach is also less than ideal.  Any values the script requires must be manually extracted from the view model, which makes maintenance more of a headache than it should be. 

The ideal solution

The previous two approaches “work”, but they each have drawbacks.  The ideal solution would allow the model to be easily consumed by external JavaScript files within a minimal amount of manual work.  Adding a new property to the view model should require zero JavaScript in order to expose the new property for use by scripts.  It turns out that this is actually quite easy to do….

The right way: serialize the model to JavaScript!

.NET 3.5 introduced the JavaScriptSerializer class for serializing objects to/from JSON. With it, most .NET types can be easily converted into a form that’s easily consumable by JavaScript.  Scott Gu introduced a simple ToJSON extension method on his blog which can be used to transform a view model into JSON.  When the output of this method is assigned to a JavaScript variable, the properties of the view model effectively become available to client-side script (note that methods defined on the view model, if any, are ignored by the JavaScriptSerializer). 

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<AwesomeViewModel>" %>
...
    <script type="text/javascript">
        var Model = <%=Model.ToJson() %>
        //If needed, new properties can be added to the model, such as URLs for AJAX requests:
        Model.MyAjaxMethod = '<%=Html.BuildUrlFromExpression<MyController>(c => c.DoAjaxyThing()) %>';        
    </script>

The above code block should appear before any scripts that wish to access properties from the view model, which can reference view model properties easily:

<script type="text/javascript">
    
    alert("Hello from the JSON view model:" + Model.Hello);
    
</script>

This is a big improvement over the other two approaches I’ve tried (at least in my opinion), but it still requires me to perform this mundane serialization task on each view.  An easy improvement is to move it to the master page:

<script type="text/javascript">
    var Model = <%=Model != null ? Model.ToJson() : "{}" %>;
</script>

Now every view that has a view model will automatically have a Model object available.

Thoughts?  Suggestions?  Anyone found a better solution that I’ve just overlooked somehow?

Tags:

blog comments powered by Disqus