The Principle of Least Surprise

 

I’m having a discussion on the ASP.NET MVC forums with one of the guys from the ASP.NET team in regard to the Data Annotations in MVC 2 and I’m not sure I agree with him. Here’s an issue Jak and I have run into:

In MVC 2 there’s a new Html Helper named EditorForModel(); that renders out a form based on the properties of your model, along with the validation messages, labels, etc. So something like this:

 

   <% using (Html.BeginForm()) {%>
 
        <%=Html.EditorForModel() %>     
            <p>
                <input type="submit" value="Save" />
            </p>
    <% } %>

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

 

you’d get something like this:

image

If you want to do individual fields and thus have finer control, you can use the EditorFor helper, passing in a property name. In other words, the previous could be also rendered as:

    <% using (Html.BeginForm()) {%>
 
        
   <%=Html.EditorFor( model => model.FirstName) %> 
   <%=Html.EditorFor( model => model.LastName) %>
   <%=Html.EditorFor( model => model.Email) %>
        
            <p>
                <input type="submit" value="Save" />
            </p>
    <% } %>

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

but this time you don’t get the labels:

image

The problem however is that you lose something else: the Validation messages. If you have client-side validation enabled, the previous ASPX file generates a pretty much useless call to the EnableClientValidation JS function:

EnableClientValidation({"Fields":[],"FormId":"form0"}, null);

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

 

as opposed to:

EnableClientValidation({"Fields":[{"FieldName":"FirstName","ValidationRules":[{"ErrorMessage":"First name is required","ValidationParameters":....
 
// rest omitted for brevity

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

 

The result of course is that client-validation doesn’t work (and from what I’ve heard causes JS errors in some browsers).

The solution to this is to explicitly add a Validation Message, like so:

<%=Html.ValidationMessage("FirstName")%>

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Now I understand that EditorFor is for fine-tuning and there is a corresponding LabelFor (although a ValidationFor doesn’t exist yet), but my main concern here is that it’s breaking the principle of least surprise from an API perspective.

For me, the only difference between EditorFor and EditorForModel should be that in the first I specify a property name explicitly whereas in the latter it just assumes the whole model. Nothing is telling me by the name of the method that the second does a whole bunch more of magic.

One solution is for EditorFor to be renamed to something else if it’s ONLY going to provide the input box (be it a text area, checkbox, radio group, etc..).

Thoughts?

6 thoughts on “The Principle of Least Surprise

  1. Haacked

    Let’s say we did render the label and validation message in that case. Wouldn’t you be surprised by that? Because what happens now if you decide you want to render these things in a table. And thus you want to control where the validation message is located in relation to the input field. How would you do that if Html.EditorFor(model => model.FirstName) by default rendered all three of these things?

    BTW, one thing you could do is write a custom String.ascx editor template which included a validation message and label and get the behavior you want, but that would apply to *ALL* string properties. Not sure you’d want to do that.

    Reply
  2. Hadi Hariri

    @Phil,

    Completely agree with needing to have finer control and not suggesting that the solution is necessarily to have EditorFor render all of it. However, right now, EditorFor and EditorForModel only have one difference in terms of naming: One has the Model suffix. For me that means that they both do the same, one for individual properties whereas the other for the entire model.

    In fact, doing EditorFor( model => model) also renders validation and labels. So now the SAME method with different input params behaves radically different.

    To avoid confusion, maybe the current EditorFor could be renamed to InputFor, which is versatile enough to not imply a certain HTML input (it could still be text textarea, checkbox, etc.).

    Reply
  3. Steve Strong

    I’m with Hadi here. To me, I’d expect EditorForModel() and EditorFor() to do similar things, which they don’t. For sure, we need the fine grained control that EditorFor() offers, but it’s got the wrong name. InputFor() (or something like that) would be much better simple because it’s different to EditorForModel, which is correct since its behaviour is different.

    Reply
  4. Brad Wilson

    The difference isn’t EditorFor vs. EditorForModel, the difference is editing a complex object vs. editing a simple value.

    Reply
  5. Richard Kimber

    I’ve just been with playing with the EditorForModel. It’s extremely powerful, but in Preview 2, at least it doesn’t seem to support DataAnnotations DisplayAttribute. Is this your experience?

    This alone would open up a lot more possibilities for customisation.

    Rich

    Reply
  6. Hadi Hariri

    @Rich,

    Quite a number of thigns don’t work in Preview 2 in terms of DataAnnotations, both in combination with DisplayModel and without it. That specific one I haven’t run into (Yet) but it wouldn’t surprise me.

    Reply

Leave a reply to Brad Wilson Cancel reply