pop·cy·cli·cal

Monday, January 4, 2010

ASP.NET MVC ViewModel Value Formatting using AutoMapper

From ASP.NET MVC in Action section 4.4.1:

Views are difficult to unit test, so we want to keep them as thin as possible. … Notice in [the View Model] that all of the properties are strings. We’ll have the [properties] properly formatted before this view model object is placed in view data. This way, the view need not consider the object, and it can format the information properly.

To facilitate the formatting between the Domain Model and the View Model, a few of AutoMapper’s features may be utilized. Here’s a DomainModel containing a CurrencyProperty which will needed to formatted for human consumption:

public class DomainModel
{
    public decimal CurrencyProperty { get; set; }
}

Now, here is a ViewModel which will be used to transport the formatted value to the View:

public class ViewModel
{
    ///<summary>Currency Property - formatted as $#,###.##</summary>
    public string CurrencyProperty { get; set; }
}

Mapping from Domain Model to View Model

AutoMapper provides an easy way to create a mapping between two object types.  For particular tweaks for individual property mappings, the ForMember method can be used like:

///<summary>Setup mapping between domain and view model</summary>
static ViewModel()
{
    // map dm to vm
    Mapper.CreateMap<DomainModel, ViewModel>()
      .ForMember(vm => vm.CurrencyProperty, mc => mc.AddFormatter<CurrencyFormatter>());
}

This sets up a mapping between the DomainModel and ViewModel and additionally applies a custom formatter for CurrencyProperty.  The formatter must implement the IValueFormatter interface like so:

public class CurrencyFormatter : IValueFormatter
{
    ///<summary>Formats source value as currency</summary>
    public string FormatValue(ResolutionContext context)
    {
        return string.Format(CultureInfo.CurrentCulture, "{0:c}", context.SourceValue);
    }
}

…and a simple conversion constructor on the ViewModel:

/// <summary> Creates the view model from the domain model.</summary>
public ViewModel(DomainModel domainModel)
{
    Mapper.Map(domainModel, this);
}

Now, neither the Controller or View need concern about any formatting and can stay focused on orchestrating and layout:

public ViewResult Index()
{
    var model = new DomainModel{CurrencyProperty = 19.95m};

    var viewModel = new ViewModel(model);

    return View(viewModel);
}
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<ViewModel>" %>
<asp:Content ContentPlaceHolderID="MainContent" runat="server">
<% using(Html.BeginForm()) {%>
    <%= Html.TextBoxFor(m=>m.CurrencyProperty)%>
<%} %>
</asp:Content>

Aside: TextBoxFor is an upcoming ASP.NET MVC 2 feature that’s available today in MVC Futures or the RC.  Check out Matt’s post for some neat stuff.

Mapping from View Model back to Domain Model

So now the formatted value is being rendered – but how do we go about the reverse trip back to the server?  First, to define an action:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(ViewModel viewModel)
{
    var model = new DomainModel();
    
    viewModel.MapTo(model);

    // ... return a view or action result
}

Aside: for validating that what the user enters is in the correct format, see another post about jQuery Validate here.

… and a Map method on the ViewModel:

public void MapTo(DomainModel domainModel)
{
    Mapper.Map(this, domainModel);
}

But this mapping will fail without first creating a definition back from the ViewModel to the Model.  In the ViewModel’s static constructor:

  // from vm to dm
  Mapper.CreateMap<ViewModel, DomainModel>()
   .ForMember(dm => dm.CurrencyProperty, 
    mc => mc
     .ResolveUsing<CurrencyResolver>()
     .FromMember(vm => vm.CurrencyProperty));

This utilizes another AutoMapper method - ResolveUsing - which can be used to get the string property back to a decimal.  The ValueResolver<TSource,TDestination> is defined like so:

public class CurrencyResolver : ValueResolver<string, decimal>
{
    ///<summary>Parses source value as currency</summary>
    protected override decimal ResolveCore(string source)
    {
        return decimal.Parse(source, NumberStyles.Currency, CultureInfo.CurrentCulture);
    }
}

Conclusions

There may be more elegant ways to accomplish formatting for MVC Views, but this method is quite workable.  In particular, I can imagine utilizing DataAnnotations’s DisplayFormatAttribute to decorate the Model or ViewModel and the framework automagically applying the formatting while rendering the View.

Sunday, January 3, 2010

jQuery Validate and Jeditable, Part 2

When using Jeditable, there is no form element to bind jQuery Validate rules with.  Instead, when an editable element is clicked or activated, it dynamically creates a new form and input element and destroys them after the user is done editing.  For the ViewModel from Part 1, the View might be rendered like so for Jeditable:

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<ValidateViewModel>" %>
<asp:Content ContentPlaceHolderID="MainContent" runat="server">
<% using(Html.BeginForm()) {%>
    <%= Html.ValidationSummary() %>
    <label for="StringRequired">StringRequired:</label>
    <div class="editable" id="StringRequired" name="StringRequired">
        <%= Model.StringRequired %>
    </div>
    
    <label for="DoubleRange13_100">DoulbeRange13_100:</label>
    <div class="editable" id="DoubleRange13_100" name="DoubleRange13_100">
        <%= Model.DoubleRange13_100%>
    </div>
<%} %>
</asp:Content>

xVal’s ClientSideValidation<TViewModel>() used in Part 1 won’t work to validate this.  The reason?  It generates a script that binds validation directly to the form elements on page load.  The rendered script looks for the ViewModel looks like:

<script type="text/javascript">
xVal.AttachValidator(null, 
    {"Fields":[
      {
         "FieldName":"StringRequired",
         "FieldRules":[
            {
               "RuleName":"Required",
               "RuleParameters":{

               },
               "Message":"This string is required"
            },
            {
               "FieldName":"DoubleRange13_100",
               "FieldRules":[
                  {
                     "RuleName":"Range",
                     "RuleParameters":{
                        "Min":"13",
                        "Max":"100",
                        "Type":"decimal"
                     },
                     "Message":"Must be between 13 and 100"
                  }
               ]
            }
         ]
      }
    ]}, {})
</script>

The rules are in the xVal’s StandardJSON format and the AttachValidator function (in xVal.jquery.validate.js) scans the DOM and attaches jQuery Validate rules as attributes to the matched input elements.  Since Jeditable doesn’t create these elements until they’re actively being edited, the rules have nothing to attach to since they don’t exist yet.  Fortunately, jQuery Validate provides several strategies for defining the rules.  In addition to being able to attach attributes to the input elements, the rules can be placed in a separate data structure.  jQuery Validate refers to these as “static rules”.  Instead of attaching the xVal rule set directly to the elements, it can be adapted to the static rule set that jQuery Validate can use directly.  The structure for the ViewModel rules will look like:

{
    rules: {
        StringRequired: {
            required: true
        },
        DoubleRange13_100: {
            number: true,
            range: ["13”, "100"]
        }
    }
    messages: {
        StringRequired: {
            required: "This string is required."
        },
        DoubleRange13_100: {
            range: "Must be between 13 and 100"
        }
    }
}

I've adapted some javascript to do this conversion - it's available here

.  To get the ViewModel’s rules into this format for javascript consumption, this line is added:
<script type="text/javascript">
    var validateOptions 
        = convertXvalToValidateOptions(
            <%= Html.ClientSideValidationRules<ValidateViewModel>()%>
        );
</script>

 

To get these attached to form elements as soon as the user activates them, Jeditable’s “plugin” feature is utilized:

$(function() { // <- on document ready
    // register plugin with Jeditable to tie in jQuery Validate
    $.editable.types['text'].plugin = bindValidate;
    
    // attach Jeditable to each element with class "editable"
    // Note: this must be done one-by-one so that the 
    // element's name can be assigned to Jeditable's "name" 
    // option which is used by jQuery Validate
    $('.editable').each(function() {
        var element = $(this);
        
        element.editable(
            'SaveUrlOrFunctionGoesHere',
            {
                // submit when the element is blurred
                onblur: 'submit',
                onsubmit: jeditableValidate,
                // assign the name of the input element 
                // from the element's name - this is needed 
                // because it's what jQuery Validate uses 
                // to bind the rules to the input element
                name: element.attr('name')
            }
        );
    });
});

// Jeditable plugin
function bindValidate(settings, self) {
    // attach jQuery Validate to 
    // Jeditable's dynamically created form
    $('form', self).validate(validateOptions);
}

// runs before values are submitted to server
function jeditableValidate(settings, self) {
    // validate the Jeditable dynamically created form
    return $('form', self).valid();
}

With this glue in place, the form elements will now be validated with the rules defined in the ViewModel.  All fields valid:

jQueryValidateJeditable1

…and here after both have invalid values:

jQueryValidateJeditable2

A few notes:

  • Any additional options to be sent to jQuery Validate can be attached to the validateOptions object. I’ve used this to place all error messages into a separate errorLabelContainer (like here).
  • I feel that AttachValidator function in xVal.jquery.validate.js from could become more loosely coupled by separating the rule conversion from the DOM element attachment.

I think both of these jQuery libraries provide a great benefit when creating interactive and helpful forms.  Kudos to Jörn Zaefferer and Mika Tuupola for the good work.  xVal is likewise an excellent library – thanks to Steve Sanderson.

Thursday, December 31, 2009

jQuery Validate and Jeditable, Part 1

I was recently tasked to add server-and-client-side form validation for an ASP.NET MVC site - which already uses in-place editing courtesy Jeditable.  I really like the field editing experience that Jeditable provides – it makes form entry in the browser interactive, is fairly straightforward to integrate, and it’s adaptable to many scenarios.  It does not, however, have any validation mechanism built in. 

Our project already used jQuery Validate for a few forms by using the HTML class definitions – like adding class=”required phone” to an INPUT element.  This works great, but doesn’t provide any server-side validation tie-in.

Earlier this year, I remembered having seen a presentation by Elijah Manor who mentioned using xVal for robust server and client side validation. And with the news that MVC 2 will have a built in validation technique similar to xVal, it was an easy decision to start investigating this library.

xVal is a pretty easy to get integrated.  The first step is to decorate the model with validation rules – I’ve decided to use .NET framework’s DataAnnotations, which ends up looking like:

public class ValidateViewModel
{
    [Required(ErrorMessage = "This string is required")]
    public string StringRequired { get; set; }

    [Range(13, 100, ErrorMessage = "Must be between 13 and 100")]
    public double DoubleRange13_100 { get; set; }
}
public class ValidatedController
{
    public ViewResult Index()
    {
        return View(new ValidateViewModel());
    }
}
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<ValidateViewModel>" %>
<asp:Content ContentPlaceHolderID="MainContent" runat="server">
<% using(Html.BeginForm()) {%>
    <%= Html.ValidationSummary() %>
    <label for="StringRequired">String Required:</label>
    <%= Html.TextBoxFor(m => m.StringRequired) %>
    <%= Html.ValidationMessageFor(m => m.StringRequired)%>
    
    <label for="DoubleRange13_100">Doulbe between 13 and 100:</label>
    <%= Html.TextBoxFor(m => m.DoubleRange13_100) %>
    <%= Html.ValidationMessageFor(m => m.DoubleRange13_100)%>
<%} %>
</asp:Content>

To validate this ViewModel server side, we use a DataAnnotationsValidationRunner like the one in xVal’s documentation:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Update(ValidateViewModel model)
{
    try {
        DataAnnotationsValidationRunner.ValidateModel(model);
        // it’s valid, do the actual update
        var domainObject = ValidateDomainModel.Find(model.id);
        Map(model, domainObject);
        domainObject.Update();
    }
    catch(RulesException ex) {
        ex.AddModelStateErrors(ModelState, null);
    }

    return ModelState.IsValid ? RedirectToAction("Completed")
                              : (ActionResult) View();
}
public static class DataAnnotationsValidationRunner
{
    private static IEnumerable<ErrorInfo> GetErrors(object instance)
    {
        return from prop in TypeDescriptor.GetProperties(instance).Cast<PropertyDescriptor>()
               from attribute in prop.Attributes.OfType<ValidationAttribute>()
               where !attribute.IsValid(prop.GetValue(instance))
               select new ErrorInfo(prop.Name, attribute.FormatErrorMessage(String.Empty), instance);
    }

    /// <summary>Validates the given <param name="model">model</param></summary>
    /// <exception cref="RulesException">Thrown if any errors are found</exception>
    public static void ValidateModel(object model)
    {
        var errors = GetErrors(model);
        if (errors.Any())
            throw new RulesException(errors);
    }
}

Aside: the project uses the Active record pattern via Castle ActiveRecord for data access without an intermediate business-logic layer.  For this case, the rules are defined on the ViewModel and are validated in the controller.  This does add some noise in the actions – I’m definitely interested in other methods for handling this. Such as - perhaps the validation could be placed inside the ViewModel?

The next step is to add client-side validation.  xVal’s built-in jQuery Validate rule generator makes this ridiculously simple – just reference jquery.validate.js and xVal.jquery.validate.js in the view, and then this single line:

<%= Html.ClientSideValidation<ValidateViewModel>() %>

The rules defined in the ViewModel will now be validated client side and enforced for server side actions.  This works great for a statically defined HTML form, but I learned that integrating with Jeditable’s dynamic inline forms to be not so straight forward.

Continued in Part 2