in

Platinum Bay

Peace, Love, and...

This Blog

Syndication


.NETicated

January 2009 - Posts

  • DevEvents – LINQ to SQL Model Binding in .NET MVC

    In a previous post, I outlined the generics-based Repository model I built for DevEvents. There are two additional pieces of extensibility to the model that I left out for the sake of clarity. The first one, and the one I’ll cover in this post, is LINQ to SQL model binding.

    During the initial development of the DevEvents framework, I was using MVC model binding for forms and entities:

    public ActionResult Create(FormCollection form)
    {
    var org = new Organization();
    try
    {
    UpdateModel(org, form);

    Unfortunately, from time to time I would receive a stack overflow exception when calling UpdateModel. This is because LINQ to SQL entities contain references as properties to related entities, and the model binding would iterate recursively through them, forever.

    Fortunately, UpdateModel contains a generic overload that helps to solve this problem:

    protected internal void UpdateModel<TModel>(TModel model) where TModel : class;

    For TModel, I can specify an interface which contains properties for the fields to bind. I have 60 tables though, and I didn’t want to manually create interfaces for each of them, so I set about with a T4 template to auto-create the interfaces based on my LINQ to SQL model. The generated code lives in the same namespace as my model, and contains a base, empty interface:

    public interface IFormBindable { }

    Then, I iterate through the DBML XML and create an interface that implements IFormBindable for each table. Finally, I iterate through the columns, and create properties for each column that is a primitive type.

    public interface IPresentationFormBindable : IFormBindable
    {
        System.String Title { get; set; }
        System.String Abstract { get; set; }
        ...
    }

    In order to make my model entities bindable, I apply these custom interfaces to their respective types in model partial classes, such as:

    public partial class Presentation : IPresentationFormBindable

    Finally, I can call UpdateModel for a presentation as follows, and only the properties specified on the IFormBindable interface will be bound.

    public ActionResult AddPresentation(string id, FormCollection form)
    {
    ... Presentation presentation = _presentationRepository.GetPresentationByPresentationID(id); UpdateModel<IPresentationFormBindable>(presentation, form);
    ...

    The second item I left out of the generic repository post for clarity was validation, which I plan to discuss in an upcoming post.

    Hope this helps!

  • DevEvents: More .NET MVC HtmlHelpers

    In my last post, I showed how I used an HtmlHelper to create an enumerable-based dropdown. In this post, I’ll share a few more HtmlHelpers I’ve created to make life easier, or the user experience better.

    There are places in DevEvents, like the directory listings where it would not be optimal to display the whole text of a speaker or event description. In such cases, I use an HtmlHelper to truncate the text, and optionally append a ‘more info’ link. To make it even better, you might consider finding the next space (‘ ‘), and truncating there.

    public static string TruncateText(this HtmlHelper helper, string text, int maxLength, string postFix)
    {
    if (text != null && text.Length > maxLength)
    return text.Substring(0, maxLength) + postFix;

    return text;
    }

    ResolveUrl is a very handy method, but it isn’t available everywhere I needed it to be, like inside other HtmlHelpers, so I created my own ResolveUrl HtmlHelper.

    public static string ResolveUrl(this HtmlHelper helper, string url)
    {
    if (url.StartsWith("~/"))
    {
    string siteRoot = helper.ViewContext.HttpContext.Request.ApplicationPath;

    if (siteRoot == string.Empty)
    siteRoot =
    "/";

    return url.Replace("~/", siteRoot);
    }
    else
    {
    return url;
    }
    }

    The HTML construct of ‘<label for’ is very helpful for individuals with accessibility issues and non-standard browsers. Instead of having to write out the whole HTML every time, I created an HtmlHelper:

    public static string LabelFor(this HtmlHelper helper, string target, string text)
    {
    return String.Format("<label for='{0}'>{1}</label>", target, text);
    }

    One common website complaint is that text is not pluralized correctly. I created a very simple HtmlHelper to do just this:

    public static string PluralizeText(this HtmlHelper helper, string singularText, string pluralText, int number)
    {
    if (number == 1)
    return singularText;
    else
    return
    pluralText;
    }
    You may wish to pass in HTML attributes to one or more of your HtmlHelpers, but how do you append them to the output? Here is a simple example showing how to append HTML attributes using a dictionary. Arguably this functionality could be an HtmlHelper itself. Notice it also utilizes the ResolveUrl HtmlHelper. (Updated: 1/14/2009: Removed some redundancy thank to Marc’s comments below
    public static string Image(this HtmlHelper helper, string url, object htmlAttributes)
    {
    StringBuilder sb = new StringBuilder();
    sb.Append(
    "<img src=\"" + helper.ResolveUrl(url) + "\"");

    IDictionary<string, object> attributes = new RouteValueDictionary(htmlAttributes);

    if (attributes != null)
    {
    foreach (var item in attributes) { sb.Append(" " + item.Key + "=\"" + item.Value.ToString() + "\""); }
    }

    sb.Append(
    " />");

    return sb.ToString();
    }

    The introduction of anonymous types, generics, and other .NET 3.0 features also introduces some complexity when working with Nullable types. To facilitate these scenarios, I created the following HtmlHelper: (Updated 1/14/2009: Made it a generic method thanks to Liam’s comment below)

    public static string FormatNullable<T>(this HtmlHelper helper, Nullable<T> nullable, string format, string emptyText) 
        where T : struct
    {
        if (nullable.HasValue)
            return string.Format(format, nullable.Value);
        else
            return emptyText;
    }

    Hopefully you will find one or more of these HtmlHelpers helpful in your .NET MVC development. What custom HtmlHelpers do you use? Would you do any of these differently?

    Posted Jan 12 2009, 11:50 AM by Steve with 8 comment(s)
    Filed under: , ,
  • Enumeration-Based Dropdowns in .NET MVC

    I have a few enumerations in DevEvents, and I found myself frequently writing code similar to the following:

    Dictionary<string, int> etlist = new Dictionary<string, int>();
    etlist.Add(
    "Code Camp", (int)EventType.CodeCamp);
    etlist.Add(
    "Conference", (int)EventType.Conference);
    etlist.Add(
    "Day of .NET", (int)EventType.DayOfDotNet);
    etlist.Add(
    "Hands-On Labs", (int)EventType.HandsOnLabs);
    etlist.Add(
    "User Group Meeting", (int)EventType.UserGroup);
    etlist.Add(
    "Workshop", (int)EventType.Workshop);
    etlist.Add(
    "Pub Night", (int)EventType.PubNight);
    etlist.Add(
    "Online", (int)DevEvents.Common.EventType.Online);
    etlist.Add(
    "BarCamp", (int)DevEvents.Common.EventType.BarCamp);
    etlist.Add(
    "Geek Dinner", (int)DevEvents.Common.EventType.GeekDinner);

    if (evnt != null)
    ViewData[
    "EventTypes"] = new SelectList(etlist, "Value", "Key", evnt.EventTypeID);
    else
    ViewData["EventTypes"] = new SelectList(etlist, "Value", "Key");

    Update - 1/12/2009: For an easier way to do the above, check out this post by Rune Jacobson.

    The problem with the approach however, is that every time I need to add a new enumeration value, I would have to modify each of these code sections, and that got very old very fast. I needed a way to generate dropdowns from the enumerations themselves. I ended up with a custom HtmlHelper to generate the dropdowns and eliminate the repetitive code.

    First, I created the base HtmlHelper:

    public static string EnumDropDown(this HtmlHelper helper, Type enumType, string id)

    The ID is used to set the HTML ID attribute, since some dropdowns were being used by jQuery. The parameter enumType is used to pass in the type of enumeration that the dropdown should be generated from. Since I need it to be an enumeration, I check as such, returning an empty string if the check fails.

    if (enumType == null || !enumType.IsEnum)
    return "";

    Next, I attempt to retrieve a value to set as default; first from ViewData, and then from the model if it has been set. I catch an empty exception at the end, which is typically a bad practice, because it is not important if the value cannot be found – the View may be an empty form.

    object defaultValue = null;

    if (helper.ViewData != null)
    defaultValue = helper.ViewData.Eval(id);

    try
    {
    if (defaultValue == null)
    defaultValue = helper.ViewData.Model.GetType().GetProperty(id).GetValue(helper.ViewData.Model,
    null);
    }
    catch (Exception) { }

    Finally, I set about to return HTML back to the View for the desired dropdown. I create a StringBuilder, append the select start tag, and iterate through all the fields on the enumeration:

    StringBuilder sb = new StringBuilder();

    sb.AppendFormat(
    "<select id=\"{0}\">", id);
    foreach (var item in enumType.GetFields(BindingFlags.Public | BindingFlags.Static))
    {

    Next, for each enumeration field, I check whether the value equals the expected default value determined above:

    string def = "";
    if (defaultValue != null && defaultValue.ToString() == item.GetRawConstantValue().ToString())
    def =
    "selected=\"selected\"";

    Finally, I append each option to the StringBuilder:

        sb.AppendFormat("<option value=\"{0}\" {1}>{2}</option>",
    item.GetRawConstantValue().ToString(), def, item.Name);
    }
    sb.Append(
    "</select>");

    return sb.ToString();

    I was then able to call my HtmlHelper from the View as follows:

    <%= Html.EnumDropDown(typeof(DevEvents.Common.EventType), "EventTypeID") %>

    I ran the code, and had a dropdown rendered with each item in the enumeration. There was one small problem however; each option display value was the enumeration field name, exactly. Instead of seeing a pretty name like “Hands-On Labs”, I saw “HandsOnLabs”. Of course this is to be expected based on the generation method used, but how do I enable a more elegant UX?

    The solution I used was to create a custom Attribute for my custom enumerations, storing a single property called DisplayName:

    [AttributeUsage(AttributeTargets.Field)]
    public class EnumDisplayNameAttribute : Attribute
    {
    private string _displayName;

    public EnumDisplayNameAttribute(string displayName)
    {
    _displayName = displayName;
    }

    public string DisplayName
    {
    get
    {
    return _displayName;
    }
    }
    }

    I then applied that attribute to my custom enumerations as follows:

    public enum EventType
    {
    [
    EnumDisplayName("User Group")]
    UserGroup = 0,
    [
    EnumDisplayName("Code Camp")]
    CodeCamp = 1,
    [
    EnumDisplayName("Day of .NET")]
    DayOfDotNet = 2,
    [
    EnumDisplayName("Conference")]
    Conference = 3,
    [
    EnumDisplayName("Hands-On Lab")]
    HandsOnLabs = 4,
    [
    EnumDisplayName("Workshop")]
    Workshop = 5,
    [
    EnumDisplayName("Pub Night")]
    PubNight = 6,
    [
    EnumDisplayName("Online")]
    Online = 7,
    [
    EnumDisplayName("BarCamp")]
    BarCamp = 8,
    [
    EnumDisplayName("Geek Dinner")]
    GeekDinner = 9
    }

    Finally, in my HtmlHelper, I checked for the presence of this attribute on each enumeration field, and set the display name accordingly:

    EnumDisplayNameAttribute[] attributes =
    (
    EnumDisplayNameAttribute[])item.GetCustomAttributes(typeof(EnumDisplayNameAttribute), false);

    if (attributes.Length > 0)
    sb.AppendFormat(
    "<option value=\"{0}\" {1}>{2}</option>",
    item.GetRawConstantValue().ToString(), def, attributes[0].DisplayName);
    else
    sb.AppendFormat("<option value=\"{0}\" {1}>{2}</option>",
    item.GetRawConstantValue().ToString(), def, item.Name);

    Here is the final completed code for the HtmlHelper:

    public static string EnumDropDown(this HtmlHelper helper, Type enumType, string id)
    {
    if (enumType == null || !enumType.IsEnum)
    return "";

    object defaultValue = null;

    if (helper.ViewData != null)
    defaultValue = helper.ViewData.Eval(id);

    try
    {
    if (defaultValue == null)
    defaultValue = helper.ViewData.Model.GetType().GetProperty(id).GetValue(helper.ViewData.Model,
    null);
    }
    catch (Exception) { }

    StringBuilder sb = new StringBuilder();

    sb.AppendFormat(
    "<select id=\"{0}\">", id);
    foreach (var item in enumType.GetFields(BindingFlags.Public | BindingFlags.Static))
    {
    string def = "";
    if (defaultValue != null && defaultValue.ToString() == item.GetRawConstantValue().ToString())
    def =
    "selected=\"selected\"";

    EnumDisplayNameAttribute[] attributes =
    (
    EnumDisplayNameAttribute[])item.GetCustomAttributes(typeof(EnumDisplayNameAttribute), false);

    if (attributes.Length > 0)
    sb.AppendFormat(
    "<option value=\"{0}\" {1}>{2}</option>",
    item.GetRawConstantValue().ToString(), def, attributes[0].DisplayName);
    else
    sb.AppendFormat("<option value=\"{0}\" {1}>{2}</option>",
    item.GetRawConstantValue().ToString(), def, item.Name);
    }
    sb.Append(
    "</select>");

    return sb.ToString();
    }

    What do you think? Would you have done it differently?

    Updated:

    In thinking about this, the next logical step would be to store the enumeration values in the database so they could be updated at runtime. While I’m not going to set about to do this right away, I would imagine pulling them when the application starts and caching them using a SQL cache dependency. Not too many things should be cached in application memory, but this list should stay relatively small. Thoughts?

    Posted Jan 10 2009, 02:47 AM by Steve with 9 comment(s)
    Filed under:
  • 52’er

    I was notified last week that I have been selected as an INETA NORAM speaker! I am quite honored, and excited to be a part of such a great team.

    Being New Years and all, I thought I would set a goal this year. Last year, I gave 48 presentations at 32 engagements. While most seem to think this is a lot, I would like to expand on that number this year. My goal? 52 engagements, 20 more than last year. So far I have scheduled 13 which brings me to 25% of my goal.

    If you need a speaker, if no one else can help, and if you can find me, maybe you can bring in: ME!

    Previous and upcoming engagement list: http://www.platinumbay.com/about.aspx

Powered by Community Server (Commercial Edition), by Telligent Systems
© Platinum Bay | Some Rights Reserved Creative Commons License

Disclaimer: The information in this weblog is provided "AS IS" with no warranties, and confers no rights. This weblog does not represent the thoughts, intentions, plans or strategies of my employer. It is solely my opinion. Feel free to challenge me, disagree with me, or tell me I'm completely nuts in the comments section of each blog entry, but I reserve the right to delete any comment for any reason whatsoever (abusive, profane, rude, or annonymous comments) - so keep it polite, please.