Dennis Burton's Develop Using .NET

Change is optional. Survival is not required.
Tags: diabetes

On October 4th and October 10th, my family will be participating in fund raising walks to raise money for research into better management, and someday a cure, for diabetes. Similar to many of the technical conferences I attend, these events provide a great place to get tips and tricks, and to share stories of shared experiences. As parents, we walk away from these events refreshed with new ideas and comforted that our struggles are shared. It is also a good place for my son Drew to be around other kids who have diabetes--a place where it is not unusual to do a blood sugar test before lunch.

Since last year, Drew has now moved on to first grade. He has become a voracious reader, loves launching model rockets, riding his bike, and of course, pestering his little brother. He lives a very full life, just like any other 6 year old boy, with a bit more ceremony around eating, exercise, and bedtime. One of his favorite activities is driving, whether it is Power Wheels, the lawn tractor, or popping dad’s car out of gear and releasing the emergency brake for a fun-filled ride into the ditch; he relishes every minute behind the wheel. As any 6 year old should, he now knows the tune to The Victors and most of the words. In short, this little guy does not let anything keep him from enjoying being a kid.

Through all of the cake and ice cream ridden birthday parties, hormone changes, and marathon play sessions, control has been pretty good so far this year. The last three A1c readings have been at 7.1%. The goal that we have been given by the endocrinologist is 8%. We try very hard, as parents, to learn and teach how to deal with life with diabetes so that Drew will have all the knowledge he needs to significantly reduce the risk of common complications. Through events like this walk, my hope is that in my lifetime, management of this disease will not be a 24/7 activity. If you would like to help support us in this endeavor, you can sponsor our team here:

JDRF Team Donation Page

Tags: aspnetmvc | jquery

The model binders in ASP.NET MVC represent a fantastic example of Coding by Convention. If you choose to abide by the naming rules, large amounts of work can be performed for you, but if you stray off of the path, you will have a lot of code to write. Let’s start with the simplest possible case, a model with a single property and a view with a single textbox for populating that property.

public class SimpleModel
{
  public string SimpleProperty { get; set; }
}

In order for a Create view to bind to this property, all that is required is the name attribute of any input type (like textbox) match the name of the property in the model you intend to populate, in this case SimpleProperty.

<input name="SimpleProperty" />

When the form is submitted, the Create action on the SimpleModelController is fired with a SimpleModel object as a parameter. This object has the SimpleProperty set with the value from the <input> with the same name. Pretty cool, but where is the massive amount of work being done for me?

Nested Classes

Now, lets make the model class a bit more interesting. This time, the model will be an outer class containing a nested class.

public class OuterClass
{
  public string SomeProperty { get; set; }
  public NestedClass Nested { get; set; }
}

public class NestedClass
{
  public string SomeOtherProperty { get; set; }
}

Now, if you use the Add View wizard, the resulting view will not contain any reference to the NestedClass, but really who keeps those around anyway? (Until v2 when you can customize the template.) Just as in the previous sample, we need an <input> for each property that should be populated with values from the POST. SomeProperty is the same case as the previous sample; Nested and SomeOtherProperty are slightly different. To understand the convention applied to this binding, think of how you would access SomeOtherProperty from an instance of an OuterClass. You would access the outerClass.Nested.SomeOtherProperty, but remember outerClass is our model and does not need to be named, so we are left with Nested.SomeOtherProperty.

<input name="SomeProperty"/> 
<input name="Nested.SomeOtherProperty" />

Again, when the form is submitted, the Create action on the OuterClassController will have a fully populated OuterClass as a parameter including an instance of NestedClass with SomeOtherProperty set. Now, I am starting to get impressed. The techniques used to map controls to objects within WebForms, are starting to look pretty clunky. The default model binder is a powerful tool as long as you follow the naming rules.

Nested Lists

This next scenario is where it all clicked for me. I was trying to bind a dynamic list of objects to a property of my model. This is a simplified version of the scenario I was looking at:

public class LogEntry 
{ 
  public int BloodSugar { get; set; } 
  public List<FoodEntry> FoodEntries { get; set; } 
} 

public class FoodEntry 
{ 
  public string Name { get; set; } 
  public string Carbs { get; set; } 
}

Just as with simple nested property, the default strongly-typed view creator will not add any UI elements for the nested class. That is fine; fewer lines of code for us to delete. To set up the view correctly, think of the description given in the last sample for the naming convention and the Rule of Least Surprise. If you think of how to access each individual element of FoodEntries you would have FoodEntries[N].Name and FoodEntries[N].Carbs. These are the names required of our input elements on the view.

<div class="logEntry"> 
  <div class="bloodSugarEntry"> 
    <label for="BloodSugar">BloodSugar:</label> 
    <input type="text" name="BloodSugar" />
  </div> 
  <div class="foodEntries"> 
    <div class="foodEntry"> 
      <label for="FoodEntries[0].Name">Name:</label>
      <input type="text" name="FoodEntries[0].Name" /> 
      <label for="FoodEntries[0].Carbs">Carbs:</label>
      <input type="text" name="FoodEntries[0].Carbs" /> 
    </div> 
    <input type="button" value="Add More" name="FoodEntries_AddMore" /> 
  </div> 
  <div> 
    <input type="submit" value="Log It!" /> 
  </div> 
</div>

Now that there is some structure in the HTML that we can leverage, we can start adding some dynamic client side behavior. In this case, I want to be able to add more food items to the form without having to postback or even perform any AJAX requests. JQuery will be leveraged to inject more food entries into the DOM. The following code is added to the click event from the FoodEntries_AddMore button.

$('#FoodEntries_More').click(function() { 
    var nextFoodEntryIndex = $('.foodEntry').size(); 
    var nextFoodEntryNamespace = 'FoodEntries[' + nextFoodEntryIndex + ']';

    var newFoodEntry = $('<div class="foodEntry">' + 
                         '<label for="' + nextFoodEntryNamespace + '.Name">Name:</label>' + 
                         '<input type="text" id="' + nextFoodEntryNamespace + '_Name"' +
                         'name="' + nextFoodEntryNamespace + '.Name" />' + 
                         '<label for="' + nextFoodEntryNamespace + '.Carbs">Carbs:</label>' + 
                         '<input type="text" id="' + nextFoodEntryNamespace + '_Carbs"' +
                         'name="' + nextFoodEntryNamespace + '.Carbs" />' + 
                         '</div>'); 

    $('.foodEntry:last').after(newFoodEntry); 
});

What this does is look at the set of food entries to determine how many are on the current page in order to determine the index used for the name on the next row of food entries. The first click will result in adding FoodEntries[1].Name and FoodEntries[1].Carbs. These names follow the naming convention that we established earlier. The default model binder recognizes this naming convention and populates the LogEntry object with as many FoodEntry items as have been created on the page.

Summary

All of this auto-magic binding assumes that you are willing and able to follow the naming conventions necessary to have the default model binder do the work for you. If you are facing an exceptional situation where the default model binder will not work for you, use a FormCollection in the Create method and do all of the parsing yourself or create a custom model binder. As you can imagine, these tasks can become complicated and unreadable. So if at all possible, try to follow the path that does the work for you. It is worth noting that this is not at all like the drag and drop designers that have a habit of creating poor, hard-to-maintain code. All that is happening here is a form element to object mapping with the code on either end being as elegant as you like.

Where is the code?

Code for this post can be found on GoogleCode at http://dennisburton.googlecode.com/svn/trunk/PostSamples.

Dennis Burton

View Dennis Burton's profile on LinkedIn
Follow me on twitter
Rate my presentations
Google Code repository

Community Events

Windows Azure Boot Camp Lansing GiveCamp