Site Meter
 
 

Monthly Archives: December 2007

Client-side MVC with jMVC and ASP.NET MVC

As you might expect, it’s pretty neat and simple to use jMVC with the new ASP.NET MVC framework. Since MVC coders already understand the core concept, and since I’m about to give you a handy helper assembly, it hardly takes any effort at all.

Download helper assembly | Download demo project | Browse source repository

A reminder

In case you’ve missed my other posts about jMVC, it’s a teeny-tiny Javascript library which brings the MVC coding experience into the browser. It doesn’t solve world hunger, but it does make it far easier to build UIs that change dynamically according to data entered or options chosen, without needing any calls to the server (that means you, AJAX). There’s a demo site and a tutorial on CodeProject.com, both based around the ASP.NET flavour.

If you want to use it with ASP.NET, or with Castle MonoRail, you can have the convenience of strongly-typed .NET objects on the server databound (in and out) to a dynamic UI on the client. This post will show how to get the same convenience when using ASP.NET MVC.

Installation

1. Start a new MVC Web Application, or open an existing one.

image

2. Download the jMVC for ASP.NET MVC package, or build it yourself from the sources. Copy the two DLLs somewhere accessible to your project, and add a reference to them.

image

3. In your web.config file, add a reference to the jMVCHelper namespace:

 image

Well done, you’re ready to go!

Usage

I’ll copy the same Task List example as used on the CodeProject.com article. We want to add to our MVC application a simple, fully client-side task list editor looking like this:

image

First, go to the controller that’s going to provide the data. If you just made a new MVC application using the default template, you can use HomeController.cs, and edit it to look like this:

public class HomeController : Controller
{
    private class Task
    {
        public string Name;
        public bool IsCompleted = false;
        public bool HasNotes = false;
        public string Notes = "";
    }
 
    [ControllerAction]
    public void Index()
    {
        // Supply some initial data to jMVC
        // You'd normally get it from a database,
	// rather than hard-coding it
        ViewData["tasks"] = new List<task>() {
            new Task { Name = "Feed fish", IsCompleted = true },
            new Task { Name = "Buy new digital SLR" }
        };
 
        RenderView("Index");
    }
 
    [ControllerAction]
    public void About()
    {
        // Not really using this, but can leave it in place
        RenderView("About");
    }
}

So, we’ve defined a data structure for our Task objects, and assigned a List of them into the ViewData for the page.

Now, in the corresponding view (Index.aspx, in this case), you can insert the jMVC panel like so:

image

If you run the application now, you’ll get a weird 404 error because it’s trying to load /Views/jMVC/tasks.jmvc, but you haven’t created this yet. Create a new blank text file at that location. This isn’t a tutorial on jMVC syntax (for that, see this guide, or examine the samples), so just paste in the following:

<% if(model.tasks.length == 0) { %>
    <p>No tasks have been added.</p>
<% } else { %>
    <table border="1" cellpadding="6">
        <thead>
            <tr>
                <th align="left">Task</th>
                <th align="left">Status</th>
                <th align="left"></th>
            </tr>
        </thead>
        <% for(var i = 0; i < model.tasks.length; i++) { %>
            <tr>
                <td>
                    <span style='<%= model.tasks[i].IsCompleted ? "text-decoration:line-through" : "font-weight:bold" %>'>
                        <%= model.tasks[i].Name %>
                    </span>
                </td>
                <td>
                    <label>
                        <input type="checkbox" onclick="<%* function(i) { model.tasks[i].IsCompleted = this.checked } %>"
                            <%= model.tasks[i].IsCompleted ? "checked" : "" %> />
                        Completed
                    </label>
                    <label>
                        <input type="checkbox" onclick="<%* function(i) { model.tasks[i].HasNotes = this.checked } %>"
                            <%= model.tasks[i].HasNotes ? "checked" : "" %> />
                        Has notes
                    </label>
                    <% if(model.tasks[i].HasNotes) { %>
                        <div><textarea onchange="<%* function(i) { model.tasks[i].Notes = this.value; } %>">
                            <%= model.tasks[i].Notes %></textarea></div>
                    <% } %>
                </td>
                <td>
                    <a href="#" onclick="<%* function(i) { model.tasks.splice(i, 1); } %>"">Delete</a>
                </td>
            </li>
            </tr>
        <% } %>
    </table>
<% } %>
 
Add new task:
<input type="text" id="NewTaskName" onkeypress="return event.keyCode != 13; /* don't submit form if ENTER is pressed */"/>
<input type="button" value="Add" onclick="<%* function() { addNewTask(model.tasks); } %>" />
 
<%
    function addNewTask(taskList) {
        var taskName = document.getElementById("NewTaskName").value;
        if(taskName != "")
            taskList[taskList.length] = { Name : taskName, Notes : "" };
    }
%>

I know that looks intimidating, but it’s more interesting than just printing "Hello, world!". If you think about it for a minute, it’s similar syntax as you’d use in any ASP.NET MVC ViewPage, except in this case it’s going to be executed on the client, and Javascript event-handling is baked in. There are simpler examples on the demo site.

Soon I’m going to make the syntax cleaner by adding MVC-style view helper methods, so you’ll be able to write <%= Html.TextBox(…) %> etc., but for now you have to write all the HTML markup yourself.

Getting data back on the server

If you run the app now, you’ll be able to edit the task list. Whee! Isn’t that fun – no server communication at all!

Except there’s no way to post your work back to the server, so change your view to look like this:

 image

Note: you need to add a reference to the MVC Toolkit to use this syntax.

Now we’ve added a proper HTML form with submit button. This means that jMVC is going to post a JSON string representing the edited data to the server with the name "myJmvcPanel". To process that on the server, add a new action method to the controller:

[ControllerAction]
public void ReceiveData()
{
    // Retrieve the edited data
    List<task> result = this.ReadJsonFromRequest<list<task>>("myJmvcPanel", "tasks");
 
    // Do something useful with it, like save it to the database
    // In this case just prove we've got the updated data
    ViewData["count"] = result.Count;
    RenderView("About");
}

By the way, you’ll need to import the jMVCHelper namespace to use the ReadJsonFromRequest() helper method. Put this at the top of your controller file:

using jMVCHelper;

Hooray, we’re done. The user can add, edit and remove tasks in the browser with no client-server communication, then when they post the form, we get strongly-typed .NET objects to represent the submission.

If you couldn’t be bothered to type those things yourself, you can download the demo project I prepared earlier.

ASP.NET MVC: Prevent XSS with automatic HTML encoding

There’s an interesting (and sometimes heated) debate on the ASP.NET MVC forums about HTML encoding.

It started with a proposal for a helper method to HTML-encode strings as soon as they are received from the visitor, so they’d be stored HTML-encoded in the database. That way, you don’t have to HTML-encode them for display to prevent cross-site scripting. If that was the default behaviour for the UpdateFrom() method, the idea of encoding for storage would no doubt be widely adopted.

Almost everyone else on the forum, though, has a strong preference for not encoding anything until the moment of display. There are some obvious benefits to this approach – you don’t have to remember which strings were pre-encoded (according to their origin), and you don’t have un-encode them when outputting to any non-HTML format. But it does mean you have to remember to encode things wherever you output them.

Sadly the two methods are incompatible, and you will have to choose one side or the other. I am very definitely in the encode-when-displaying camp.

Another solution

What I’d really like is to change the default behaviour of ASPX’s <%= … %> syntax so that it HTML-encodes the result by default. That’s what you want 95% of the time, so why should you keep writing <%= HttpUtility.HtmlEncode(…) %> all the time?

  Current reality In my ideal world
Output unencoded string <%= value %> <%= (RawHtml) value %>
Output encoded string <%= HttpUtility.HtmlEncode(value) %> <%= … %>

This would give us the best of both worlds. You wouldn’t need to remember to HTML-encode your strings (since that happens by default), so there’d be no need to store things pre-encoded in the database and then worry about double-escaping, sharing data with external systems, unencoding for output to non-HTML format and all that other nonsense.

Spike implementation

It’s a great credit to the ASP.NET architecture that we can actually implement that change of behaviour ourselves, and with not much code either. The idea is to intercept the code generation phase that happens when an ASPX file is compiled.

You can specify your own compiler implementation by editing this section of the web.config:

<system.codedom>
   <compilers>
      <compiler language="c#;cs;csharp" type="Microsoft.CSharp.CSharpCodeProvider .. etc" extension=".cs" warninglevel="4" />
   </compilers>
</system.codedom>

… and, helpfully, you can subclass CSharpCodeProvider, override the GenerateCodeFromStatement() method, and redirect all the <%= … %> evaluations through a suitable helper function.

Demonstration

You can download a demonstration project to see this in action, or to install the behaviour into your own project, follow these steps:

1. Download the SafeEncodingHelper assembly (or build it yourself – the demo project includes sources), and add a reference to it in your project.

2. In your web.config, edit the system.codedom.compilers element, to look like this:

<compiler language="c#;cs;csharp" type="SafeEncodingHelper.SafeEncodingCSharpCodeProvider, SafeEncodingHelper" extension=".cs" warninglevel="4">
	<provideroption value="v3.5" name="CompilerVersion" />
	<provideroption value="false" name="WarnAsError" />
</compiler>

3. Also in web.config, under pages/namespaces, add a reference to the SafeEncodingHelper namespace:

<namespaces>
	<add namespace="System.Web.Mvc" />
	<add namespace="System.Linq" />
	<add namespace="SafeEncodingHelper" />
</namespaces>

 

That’s all! You will now find that <%=…%> encodes its output, or you can get unencoded output by casting your value to the RawHtml type, i.e. <%= (RawHtml)myValue %>.

What about MVCToolkit?

You might be thinking that this is going to break the MVC toolkit, since you use it to build HTML controls with a syntax like this:

<%= Html.TextBox("myinput", "It's nice") %>

You might, reasonably, expect this now to render a bunch of useless HTML-encoded nonsense. There’s a neat solution, though – the MVC toolkit could return values of the RawHtml type (which is merely a wrapper around System.String which adds no functionality). This is specially recognised by the SafeEncodingHelper compiler, and bypasses the HTML encoding. So, you can keep your clean syntax for any methods that you specifically want to render unencoded HTML.

Also, if someone isn’t using SafeEncodingHelper, no problem! The RawHtml type has a .ToString() method that simply returns the underlying value, so the MVC toolkit methods would still work just as well.

The demonstration project contains an alternative MVC toolkit that behaves this way. Actually, it only has a single facility (TextBox), but it’s enough to give you the idea.

Should I really use this then?

Firstly, this code comes with no warranties at all. Use it if you want, but beware – I just cooked it up on impulse and there may be any number of special cases I haven’t accounted for. It’s a proof of concept, that’s all.

Unless Microsoft chooses to support the RawHtml type in their MVC toolkit and related methods, you would have to remember to cast all MVC toolkit output to RawHtml, or write your own wrapper methods or something. Not much fun, sorry.

kick it on DotNetKicks.com

ASP.NET MVC Architecture 1: Routing

I’m going to do a series of short posts summarising the core architectural structure of ASP.NET MVC. This is really for my own benefit – to make sure I understand it in detail – so if someone else has already covered these topics, it doesn’t matter!

Routing: Introduction

This first component isn’t strictly part of the MVC framework. It was created by the MVC team, and is built to support it, but it actually operates completely independently and could be used as a standalone URL rewriting mechanism for any ASP.NET application. The objective is to catch nice URLs like /Products/Beans/Page7 and invoke an appropriate IHttpHandler with parameters extracted from the URL.

Registering with IIS

Before Routing can do anything, it needs ASP.NET to be registered with IIS to handle the incoming requests. There are three methods:

Method Platforms supported
Use .mvc extension on all URLs, and register that extension to be handled by ASP.NET IIS 5.1+
Wildcard URL mapping IIS 6.0+
Set runAllManagedModulesForAllRequests="true" on system.webServer/modules IIS 7.0

Using either of the last two methods means that IIS will invoke ASP.NET to handle all your requests. Unfortunately, for IIS5.1 (XP), you need the .mvc in the URL as wildcard mapping isn’t supported (see end of post for workaround).

Intercepting the ASP.NET pipeline

When you create a new MVC web application, you’ll get this line added to your web.config:

<httpmodules>
   <add type="System.Web.Mvc.UrlRoutingModule, etc etc... "
        name="UrlRoutingModule" />
</httpmodules>
UrlRewritingModule is the core component in Routing. When it’s invoked during the processing of a request, it does the following before ASP.NET runs any IHttpHandler:

1. Chooses the appropriate Route

… by calling System.Web.Mvc.RouteTable.Routes.GetRouteData().

RouteTable.Routes is a global singleton in which you’re expected to have already registered your routes (e.g. in Global.asax’s Application_Start()). The GetRouteData() method finds the first registered Route that matches the incoming request data, and returns a populated RouteData object.

2. Instantiates the chosen route’s IRouteHandler type

It uses the route’s RouteHandler property, which must be a type implementing IRouteHandler. For most MVC requests, this will be an MvcRouteHandler, but you can use anything.

3. Obtains the IHttpHandler

… by calling the IRouteHandler’s GetHttpHandler() method

4. Sets the IHttpHandler as the handler for the request

… then finishes, leaving the normal ASP.NET pipeline to actually invoke this nominated IHttpHandler

 

Cast and crew

RouteData- holds a Route, plus a list of the values matched to its placeholders

IRouteHandler – is an IHttpHandler factory

MvcRouteHandler – is an IRouteHandler that always supplies an MvcHandler instance

MvcHandler – is an IHttpHandler that knows how to invoke the rest of the MVC pipeline (e.g. calling action methods)


That’s it! Notice that there’s no mention of controllers or actions or whatnot, because as I said, routing is completely separate from MVC. All the MVC-specific responsibilities are held by MvcHandler, which will be the starting point for the next post in this series.

Suggested extensions and tweaks

  • Use extensionless URLs with IIS5.1. Don’t like that dirty .mvc in your URL? Use ISAPI Rewrite (freeware version) to forward all requests to /blah.ashx, then put in your own IHttpModule (before UrlRewritingModule) to rewrite back to the original URL (hint: it’s stored in the header X-Rewrite-Url). Handy if you’re developing on XP.
  • Try writing your own URL matching logic. If you don’t like MVC’s Route syntax, want to write your own that uses Regexes, want the configuration to come from a database or whatever, well, bad luck! You can’t change any behaviour of Route, because we have neither an interface or any virtual methods on Route. Your only option is to replace the entirety of UrlRoutingModule, but even then you’re stuck because the HtmlHelper methods are hard-coded to call System.Web.Mvc.RouteTable. Perhaps this might be opened up a bit in a future release (Phil Haack hinted as much in a blog comment).
  • Handle part of the URL space outside MVC – perhaps you want to serve static content on some URLs really quickly. Make an IHttpHandler to serve the content, and an IRouteHandler to instantiate the handler. Use this type as the RouteHandler in one of your Route registrations.