Berichten met label MVC

ASP.NET MVC: Editor Templates

Last week I sat with a colleague who is building an ASP.NET MVC application. Since I’m currently on a not-so-interesting application, I was really jealous and decided to out-smart him, showing off with some hard-core MVC code.

Here we go.

What’s your problem, dude?

Well, we are all very Web 2.0 and so, using Ajax and jQuery and maybe even single-page-applications, but on almost any site I fill in a form, a textbox is just a plain textbox. You can type text, like digits and characters, even if they need only my age. Come-on guys, an age is expressed as a number, so why allow me to type in anything else but digits?
Sure, you validate at the server and maybe even at the client, but still I can make errors that I have to fix later. Please, help me by preventing the error up-front.

The solution, part 1: jQuery-plugin

The solution starts with a nice jQuery-plugin by Paulo P. Marinas called jQuery AlphaNumeric. There is also a nice MaskedInput-plugin, but I found that too limiting.
It is simple to hook it up to my textbox:

$("#mytextboxid").numeric();

And voila, nothing but digits allowed!

Cool, I want that on all the fields that I know are numbers, but I surely don’t want to repeat the above Javascript.

The solution, part 2: EditorFor

Starting with MVC 2 there is an Html-helper called Html.EditorFor(). It uses a lambda as a parameter and derives a lot of interesting information from that. If you type

        <%= Html.EditorFor(model => model.Length) %>

You get code generated like this:

<div class="editor-field">
<input id="Length" name="Length" type="text" value="7" /></div>

De ViewEngine determines that you have a Integer field, generates a textbox with the name/id of the field and sets the value. If you have client-side data-validation on, you get the extra span-tag for the validation message. More on that in a minute. Maybe this doesn’t look like much, but you get different output for different field types; like radio-buttons for a Boolean field and appropriate formatting for a Decimal field.
The EditorForModel() method even generates code for all the fields on your class.

The solution, part 3: Turning validation on

I was hoping that the EditorFor() method would be all I needed. It supports validation, both client-side and server-side, and (as argued before) what better client-side validation then preventing false input to begin with!
Alas, it doesn’t do that. But in combination with DataAnnotations (Scott Guthrie has a nice blog about that) it adds range-checks to your Integer-field. So if I can limit the characters that can be typed into my textbox with a line of jQueury and can check for the upper- and lower-limit of the value with client-side validation, then I’m happy!

But that means that I have to change the behavior of the EditorFor() method. Can that be done? Well……, sort of.

The solution, part 4: Convention over configuration

The nice thing about the MVC framework is that it takes convention over configuration, meaning you do not have to configure anything to get a working application, as long as you adhere to the rules/conventions. Nothing new for Ruby on Rails programmers maybe, but for me as a long-time Microsoft-ee it is close to revolutionary.

In this case, the interesting convention is that there can be a EditorTemplates sub-directory in my View directory. If it is in a normal View directory it works for only those Views, but if you put it in the Shared directory it works for all your views:
SharedViews

If you have a user-control in there named Decimal.ascx it will be used everywhere the EditorFor() encounters a Decimal property. There is a bunch of default user-controls that are described by Brad Wilson.

What I would have liked to be “The solution, part 5: Overloading the decimal template”

Since I like the server-side formatting of the default template, and only want to add some jQuery, I would have liked to do the following:

<%@ Control Language="C#" AutoEventWireup="true" Inherits="System.Web.Mvc.ViewUserControl" %>
<%= Html.EditorFor(Model) %>
<script type="text/javascript">
    $("#mytextboxid").numeric();
</script>

I simply delegate to the original implementation, passing in the data I have available. But that doesn’t work since the EditorFor() expects a lambda as a first parameter. And I don’t know any way to convert the data I have at that point in time to a lambda. If you know, please tell me and you’ll be my hero (hmmmm, I hope it’s not going to be that colleague coming up with the solution, that would really ruin my post…).

The actual “The solution, part 5: Overloading the decimal template”

The best I could do was copying the default template for the Decimal from the blogpost of Brad Wilson. Yes that’s a shame, copying code, having to maintain code that isn’t even mine, not profiting of the improvements Microsoft makes in the default template. Nevertheless, here it is:

<%@ Control Language="C#" AutoEventWireup="true" Inherits="System.Web.Mvc.ViewUserControl" %>
<script runat="server">
    private object ModelValue {
        get {
            if (ViewData.TemplateInfo.FormattedModelValue == ViewData.ModelMetadata.Model) {
                return String.Format(System.Globalization.CultureInfo.CurrentCulture,
                                     "{0:0.00}", ViewData.ModelMetadata.Model);
            }
 
            return ViewData.TemplateInfo.FormattedModelValue;
        }
    }
</script>
<%= Html.TextBox("", ModelValue, new { @class = "text-box single-line" }) %>
<script type="text/javascript">
    $('#<%= ViewData.ModelMetadata.PropertyName %>').numeric({ allow: '<%= System.Globalization.CultureInfo.CurrentCulture.NumberFormat.CurrencyDecimalSeparator %>'});
</script>

So, there is some server-side code that I copied, and then some client-side code I added myself.
There are two interesting things to mention about my own single line of code:

  • The jQuery selector references the name of the property, thus guaranteeing that the javascript is coupled to the right text-box. Other solutions make you use a fixed class or prefix or postfix, but that only opens the possibility of name clashes. The name-property is available in the model-metadata. See the Bradd Wilson series mentioned above for more info
  • The allowed character (the decimal separator) for entering money is retrieved from the CurrentCulture and not hard-coded. Of course.
  • As ScottGu would say: hope this helps.

, ,

4 reacties