ASP.Net MVC XForms

by 7. November 2008 20:09

Available for download via the MVC XForms codeplex project. The project includes all source code and a test project that requires no database or external setup. Just download, open up visual studio and press play...

Planned posts in this series:

  1. Introduction to MVC XForms and basic form controls

  2. XForm container controls - Group and Repeat

  3. Automatic validation and deserialization

  4. Extending the framework - Overriding formatting, custom user-controls and Mvc.XForms.UI

Any suggestions, possible enhancements, comments, criticism, etc, please feel free to leave a comment here, on codeplex or email me. This is a personal project and I can provide no guarantees that the code functions as intended or will be maintained. It's been developed purely out of fascination with xforms, c# 3.5, lambda expressions, LINQ and fluent API's. I would love some feedback, as this is my first attempt at a framework and a blog (so go easy...).

What is it?

MVC XForms is a simple, strongly-typed UI framework for MVC based on the W3C XForms specification. It provides a base set of form controls that allow updating of any complex model object, even complex nested lists. The goals are:

  • To allow automatic form population, deserialization and validation based on the (arbitrarily complex) model.
  • To produce semantic HTML forms using the logic of XForms.
  • To output clean, terse HTML.
  • No javascript, unless necessary and always optional and unobtrusive.
  • To enable clean, terse view code.
  • To make the framework as extensible and customisable as possible without compromising simplicity or the above goals.
  • Use convention over configuration and a fluent API.

Why XForms?

I have never been overly enamoured with the way .Net has abstracted form controls. "TextBox", "DropDownList", "CheckBox", etc all suffer from the same problem; by their definition they mix presentation with semantic meaning. For example:

  • "ListBox" and "CheckBoxList" both involve making multiple selections from a list.
  • "DropDownList" and "RadioButtonList" both define single selections from a list.

The XForms specification rips up standard HTML forms and re-defines what it is that makes up a form, free from presentational concerns. In the above examples, "ListBox" and "CheckBoxList" become "Select" and "DropDownList" and "RadioButtonList" become "Select1". Similarly, "TextBox", "CheckBox" and "Calendar" are all abstracted to simply "Input". How these are rendered depends on data-types and stylesheets (the appearance attribute also gives XForms processors a hint).

Why ASP.Net MVC?

I have written previous versions of this project on various platforms. Old .Net implementations relied on reflection and/or XML property settings (a la webforms). With the advent of C#3.5, lambda expressions, extension methods and MVC, a platform has been put in place that allows a much more terse, readable and (most importantly) strongly-typed syntax. Lambda expressions are key to this, they fulfil the role of XPath in XForms as "pointers" to properties.

Lets see some code...

<%@ Page Language="C#" Inherits="Mvc.XForms.Examples.Customer.Details" MasterPageFile="~/Views/Shared/Site.Master" %>
<asp:Content ContentPlaceHolderID="Content" runat="server">
<% Html.XForm(ViewData.Model.Customer).Form(form => { %>
	<%= form.Input(c => c.Name) %>
	<%= form.Input(c => c.DateOfBirth) %>
	<%= form.Select1(a => a.Country, ViewData.Model.Countries, c => c.ID, c => c.Name) %>
	<%= form.Input(c => c.IsActive).Label("Is Active?") %>
	<%= form.Submit() %>
<% }); %>
</asp:Content>

This will output (by default) the following HTML:

<form action="" method="post" class="xf xform">
	<div class="xf input text">
		<label for="Name">Name</label>
		<input id="Name" type="text" name="Name" value=""/>
	</div>
	<div class="xf input date">
		<label for="DateOfBirth">Date Of Birth</label>
		<input id="DateOfBirth" type="text" name="DateOfBirth" value=""/>
	</div>
	<div class="xf select1">
		<select id="Country" name="Country">
			<option value="1" selected="selected">UK</option>
			<option value="2">USA</option>
			<option value="3">France</option>
			<option value="4">Italy</option>
		</select>
	</div>
	<div class="xf input boolean">
		<input id="IsActive" type="checkbox" value="true" name="IsActive"/>
		<input type="hidden" value="false" name="IsActive"/>
		<label for="IsActive">Is Active?</label>
	</div>
	<div class="xf submit">
		<input type="submit"/>
	</div>
</form>

All the form controls are driven by the model, so populating a form is as simple as creating a new object, populating it in the controller and passing it via ViewData. Using lambda expressions ensures type safety and requires no reflection. A deserializer (an amended version of the MVC Contrib NameValueDeserializer) is included in the project that will automatically repopulate an object, no matter how "nested" the control is (a control with c => c.Orders[1].Products[2].Price will automatically deserialize).

Form Controls

MVC XForms uses a fluent-style API. This means (hopefully) that it is very simple to use as all the settings on form controls are "chained" and easily discoverable via intellisense. Every control takes a "bind" parameter; a lamda expression that points to the property on the model object that the control is bound to (Those familiar with xforms will see that this corresponds to the bind attribute, which takes an XPath expression). The rest of the constructor parameters are kept to a minimum to reduce method overloading and to allow the controls to be setup with a fluent API. The basic controls are as follows:

Input

This is the basic control for form input. The actual HTML control rendered depends on the datatype of the "bind". If the bind points to a boolean value, eg:

<%= form.Input(c => c.IsActive) %>

This is rendered as a checkbox. A datetime is rendered with a "date" class, to enable a jQuery selector to add datepicker functionality. Everything else renders as a normal text input. All control rendering and formatting can be overridden by creating a class that implements Mvc.XForms.Controls.Formatters.IFormatter and registering it in global.asax. The rendering shown here is provided by a default formatter. The idea is that you could potentially use the same form for output to custom HTML, silverlight or other 3rd party rendering.

Fluent settings for Input include:

  • ID: will add an ID to the controls containing div.
  • Label: override the label text,
  • CssClass: append a class to the controls containing div.
  • Hint: adds a tooltip.
  • Help: adds a <span class="help"></span> containing some text after the control. The example stylesheet and jquery scripts hide the span, display a help symbol and show the text in a "pop-up" div when rolled over.
  • Action: this binds a script function to a control. The default script uses jQuery selectors to bind the function. This makes it more obvious than normal jQuery what events are bound to a particular control whilst still maintaining the separation of script that makes jQuery so beautiful.
  • Required: outputs a <span>*</span> in the label text and adds a "required" class to the containing div.
  • ReadOnly: makes the control readonly and adds a "readonly" class to the containing div.

An example (if unrealistic) usage of the input fluent API:

<%= form.Input(c => c.Name)
    .Label("Customer Name")
    .CssClass("long")
    .Hint("The name of the customer")
    .Help("Some more info about customer names...")
    .Action(XFormsEvent.DOMActivate, "function(ev) { alert('You clicked the name field'); }")
    .Action(XFormsEvent.ValueChanged, "SomeScriptFunction")
    .Action(XFormsEvent.DOMFocusOut, "function(ev) { alert('You left the name field'); }")
    .Required()%>

This would render as:

<div class="xf required input text long">
    <label for="Name">Customer Name<span>*</span></label>
    <input id="Name" type="text" value="" name="Name" title="The name of the customer"/>
    <span class="help">Some more info about customer names...</span>
</div>

The necessary script (jQuery) for all form controls on the page is output together at the end of the form.

Secret, TextArea, Upload, Hidden and Output

These are essentially the same as the input control.

  • Secret is synonymous with input type="password".
  • TextArea is self-explanatory.
  • Upload allows file uploads.
  • Hidden is not strictly an XForms control, but it is included because the concession from XForms we are making with MVC XForms is that this needs to work with simple HTML. The core idea behind XForms is that the form binds to a client-side model, so hidden fields are not necessary. With MVC XForms, we are using the model server-side, so Hidden is necessary to overcome this limitation (in a future version, an optional javascript model may be added).
  • Output simply outputs the value of the bound property. You could just use the model object directly, but this control is useful in repeaters.

Select1

This is the equivalent of an HTML select (dropdownlist). There a 3 overloads for this:

  • For an enum, all you need is the bind:
<%= form.Select1(c => c.CustomerStatus) %>
  • For a list of simple types where the option value and label are the same, all you need is the bind and a list:
<%= form.Select1(c => c.CustomerRanking, Enumerable.Range(1, 10)) %>
  • For a list of complex types, you need to specify the value and label selectors:
<%= form.Select1(c => c.Country, ViewData.Model.Countries, c => c.ID, c => c.Name) %>

Fluent settings include:

  • Appearance: giving this a value of Appearance.Full will cause the control to render as radio buttons.
  • Nullable: adds a null option to the start of the list, takes an optional string label.
  • SelectNullForEmptyRequest: this takes a boolean value which determines if the null value should be selected when the Request is empty. This is because value types such as int and enum cannot be null, so we need this if we want to provide some text in the Select1 such as "Please select...". Typical usage might be .SelectNullForEmptyRequest(!ViewData.Model.IsEditing).

Select

This is the same as the Select1, but allows multiple selections. Default rendering is a select multiple="multiple" (a listbox). Appearance.Full will render as a checkbox list.

Trigger

This is equivalent to an HTML button. Appearance.Minimal will render as a link. Easy to attach actions to and used a lot in conjunction with the repeater. An example might be:

<%= form.Trigger("MyTriggerID")
    .Label("Click me!")
    .Action(XFormsEvent.DOMActivate, "function(ev) { alert('You clicked me'); }") %>

Submission

At the moment this control direct corresponds to an HTML input type="submit". A future enhancement will use the XForms submission model, so different submissions could be used for the same form. For instance, you may wish to submit to a webservice, ajax method or different controller actions depending on various factors.

Range

Range allows you to select from a sequential range of values. Not currently implemented, but is on the to do list.

This gives you a basic idea of what is going on with MVC XForms. The idea is to make form development as easy and flexible as possible. Please leave a comment and hopefully be interested enough to read the next post on group and repeater controls  :)

Currently rated 4.3 by 4 people

  • Currently 4.25/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

Comments

Powered by BlogEngine.NET 1.4.5.0
Theme by Mads Kristensen