XForms Update

by 3. February 2009 22:57

Thanks for everyone who has expressed an interest in the project. I've been busy on other things for a while, but now the MVC release candidate is available I'm going to be focusing on getting the validation side of things in place and improving some other areas. Once this is done I will open up the project for contributors.

If anyone has any ideas or suggestions for the direction of the project, let me know.

Currently rated 5.0 by 5 people

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

Tags:

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

by 8. December 2008 00:25

Available for download via the MVC XForms codeplex project (Thanks for everyone who has expressed an interest!). 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

  5. JavaScript API and Event Model

One of my goals for the MVC XForms framework is to enable clean, terse view code. This is not as easy as it seems. I think it's very important that any API is as simple and understandable as possible (In this respect, lambda expressions are the best thing to have happened to C# as a language. The expressive simplicity they provide is awesome). I have gone through several refactorings of the MVC XForms controls API to make the syntax "clean" and readable.

At the moment I'm working on the implementation of the XForm "bind", which will facilitate constraints on the data model in the form of attributes and a lambda-based API. I'm delaying post three in the series until this functionality is nailed down (hopefully soon).

Overriding formatting

One thing that bugs me with web controls is that there isn't a clean way to override the default HTML rendered by most of them. I've aimed to make MVC XForms easily extensible and pluggable (taking the lead from the MVC framework). To this end, I've decoupled all the rendering code from the control logic.

All the formatting is done via the IFormatter interface. This is instantiated via a factory, so to plug in your own formatter simply override the default formatter on application startup:

FormatterFactory.Current.Formatter = new ExampleFormatter();

The default formatter is called DefaultFormatter and all its methods are virtual. If there is one part of the output you don't like, simply inherit from this class and override the offending method:

public class ExampleFormatter : DefaultFormatter {    
    public const string requiredFormat = "<span>THIS IS REQUIRED</span>";    
    public override string Required(ICoreControl control) {    
        if (!control.Required)    
            return string.Empty;    
        return requiredFormat;    
    }    
}

I might add format overriding to individual form controls for even more granular control.

Custom User-Controls

In the last post I outlined a basic method for creating "user-controls". This was simply an extension method that created an XFormGroup, outputted the control based on the template within and returned void. What if you want to have settings on your user-control?

To be consistent with rest of the framework, we can provide a Settings object with our control that will give the user a fluent interface to work with. For example, we might want a TimeControl that has two drop-downs for hour and minute to bind to a Time object (with hour and minute int properties). This can be implemented like this:

public class TimeControl<TModel, TModelContext> : XFormGroup<TModel, TModelContext, Time> {    
    public TimeControlSettings<TModel, TModelContext> TimeControlSettings { get; set; }    
    public TimeControl(IContainerControl<TModel, TModelContext> parent, Expression<Func<TModelContext, Time>> bind)    
        : base(parent, bind) {    
        CssClass.Add("time");    
        TimeControlSettings = new TimeControlSettings<TModel, TModelContext>(this);    
    }    
}    
public class TimeControlSettings<TModel, TModelContext> {    
    public TimeControl<TModel, TModelContext> TimeControl { get; set; }    
    public TimeControlSettings(TimeControl<TModel, TModelContext> timeControl) {    
        TimeControl = timeControl;    
    }    
    public void Render() {    
        var resp = TimeControl.XForm.HttpContext.Response;    
        TimeControl.Settings.Template(con => {    
            resp.Output.Write("<label>{0}</label>", TimeControl.Label);    
            resp.Write(con.Select1(x => x.Hour, Enumerable.Range(0, 24), h => h, h => string.Format("{0:00}", h))    
                .Label("")    
                .CssClass("hour")    
                .Nullable("Hr")    
            );    
            resp.Write(con.Select1(x => x.Minute, Enumerable.Range(0, 12).Select(i => i * 5), m => m, m => string.Format("{0:00}", m))    
                .Label("")    
                .CssClass("minute")    
                .Nullable("Min")    
            );    
        });    
    }    
    public TimeControlSettings<TModel, TModelContext> CssClass(string cssClass) {    
        TimeControl.CssClass.Add(cssClass);    
        return this;    
    }    
    public TimeControlSettings<TModel, TModelContext> Label(string dateLabel) {    
        TimeControl.Label = dateLabel;    
        return this;    
    }    
} 

The TimeControlSettings object is used to hide the TimeControl properties and provide a clean fluent interface for a simple API. Now we just need to expose this control via an extension method:

public static TimeControlSettings<TModel, TModelContext> Time<TModel, TModelContext>(
    this IContainerControl<TModel, TModelContext> parent,
    Expression<Func<TModelContext, Time>> bind) {
    var time = new TimeControl<TModel, TModelContext>(parent, bind);
    return time.TimeControlSettings;
} 

Now we can use the control to bind to a time property on our model:

<% form.Time(x => x.StartTime).Label("Please enter a start time:").CssClass("startTime"); %>

One thing I would like to get working would be the use of ascx pages for user-controls. The use of generics means that I found no way to do it. It doesn't seem like it's possible to have the ascx page inherit from a generic type. Perhaps it is possible to inject this dynamically at runtime somehow. If anyone knows a technique to do this, let me know.

Exposing child control settings

Perhaps we might want to allow our API user to change the settings of a child control of our user-control. We can expose the child control settings by adding an HourSettings property to our control, then adding the following to the TimeControlSettings:

public TimeControlSettings<TModel, TModelContext> DateSettings(Action<Select1Settings> hourSettings) {
    hourSettings(TimeControl.HourSettings);
    return this;
} 

This would then make the following possible:

<% form.Time(x => x.StartTime).Label("Please enter a start time:")    
    .CssClass("startTime").HourSettings(hr =>  hr.Label("Hour:").CssClass("startTimeHour")); %>  

Mvc.XForms.UI

The UI project will be the place where common extensions of the MVC XForms framework will be kept. At the moment it just has a basic grid control used in the example project. Future enhancements will include:

  • Editing for the grid, hopefully matching the functionality of the web forms gridview (but with XForms controls and with no postbacks...)
  • Autocomplete
  • Date picker with Time picker

Currently rated 2.5 by 2 people

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

Tags:

MVC XForms Release 1 on codeplex

by 3. December 2008 00:11

I have finally got round to publishing the codeplex project. I would really appreciate questions and feedback. Anything from XForms, generics and fluent API to project setup and unit tests.

Be the first to rate this post

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

Tags:

MVC XForm container controls - XForm, Group and Repeat

by 11. November 2008 01:04

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

  5. JavaScript API and Event Model

Group

XForms spec: "The group element is used as a container for defining a hierarchy of form controls. Groups can be nested to create complex hierarchies."

This means that Group essentially gives you a "context" to work from. An example could be an address of a person:

<% form.Group(p => p.Address).Template(address => { %>
	<%= address.Input(a => a.Address1).Label("Address") %>
	<%= address.Input(a => a.Address2).Label("") %>
	<%= address.Select1(a => Country, ViewData.Model.Countries, c => c.ID, c => c.Name) %>
<% }); %>

Which would generate the following HTML:

<div id="Address" class="xf group">
	<div class="xf input text">
		<label for="Address_Address1">Address</label>
		<input id="Address_Address1" type="text" value="" name="Address_Address1"/>
	</div>
	<div class="xf input text">
		<input id="Address_Address2" type="text" value="" name="Address_Address2"/>
	</div>
	<div class="xf select1">
		<label for="Address_Country_ID">Country</label>
		<select id="Address_Country_ID" name="Address_Country_ID">
			<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>

The HTML element rendered for the group is "div" by default. This can be changed using the Tag fluent setting. For instance, you might want to do:

<% form.Group(c => c.Address).Tag("fieldset").Template(address => { %>

The beauty of the Group control is that it nicely encapsulates form controls for child objects of the main form model object. It's the perfect control to use for creating reusable components. Say you have a "Link" object that has properties "Name" and "Url". It's likely that this will be reused over different objects and forms. It's fairly straight forward to add an extension method using the Mvc XForms framework that will encapsulate this:

public static class LinkExtensions {
	public static void Link<TModel, TModelContext>(this IContainerControl<TModel, TModelContext> parent, Expression<Func<TModelContext, Link>> bind) {
		var link = new XFormGroup<TModel, TModelContext, Link>(parent, bind);
		link.CssClass.Add("link");

		var response = link.XForm.HttpContext.Response;

		link.Settings.ItemTemplate(x => {
			response .Write(x.Input(l => l.Name));
			response .Write(x.Input(l => l.Url).Label("URL"));
		});
	}
}

Now we can reuse this control wherever an object has a "Link" child object by simply using the following:

<% form.Link(x => x.RelatedLink); %>

All "container controls" (Group, Repeat and XForm) implement the IContainerControl<TModel, TModelContext> interface (the generic arguments here are representing the parent > child relationship). This means that any custom controls you create will appear on intellisense and will be type-safe.

A more advanced way of creating a user control so that you can define your own fluent settings will be discussed in a future post.

Repeat

The XForms Repeat module is the most powerful control in the XForm spec and has no direct correlation with HTML forms. It's not simply a repeating structure in the same sense as the web forms repeater control; it defines a template of XForm controls for displaying and editing collections of objects, such as a list of products in an order. It is most commonly used with triggers to enable inserting and deleting of repeater items (which all happens client-side - no post backs).

The best way to show the power of the repeat is with an example. Say you have a Company object with a list of links:

<%= form.Trigger("AddLink").Label("Add Link")
	.Insert(i => i.Nodeset(c => c.Links).At(At.Last))%>
<% form.Repeat(c => c.Links).ItemTemplate(link => { %>
	<%= link.Input(l => l.Name) %>
	<%= link.Input(l => l.Url) %>
	<%= link.Trigger("Del").Label("x").Delete() %>
<% }); %>

This generates the following HTML (given we have one link in the model):

<div class="xf trigger">
	<input id="AddLink" type="button" value="Add Link"/>
</div>
<ul id="Links" class="xf repeat">
	<li>
		<div class="xf input text">
			<label for="Links-0_Name">Name</label>
			<input id="Links-0_Name" type="text" name="Links-0_Name" value="MVC XForms"/>
		</div>
		<div class="xf input text">
			<label for="Links-0_Url">Url</label>
			<input id="Links-0_Url" type="text" name="Links-0_Url" value="http://www.codeplex.com/mvcxforms"/>
		</div>
		<div class="xf trigger">
			<input id="Links-0_Del" type="button" value="x"/>
		</div>
	</li>
</ul>

This is all the code that is required to have a fully functioning repeat with client-side inserting and deleting.

The trigger has an insert action that defines which repeat to insert a new item into (the "Nodeset" setting) and where in the list to insert it (the "At" setting). The repeat itself has an item template with two inputs and a delete trigger. The default setting for insert and delete actions on repeaters is to delete the current item. Some of the fluent settings for repeat include:

  • Tag: The html tag for the repeat container. By default this is "ul".
  • ItemTag: The html tag for the item containers . By default this is "li". A common scenario might be to use Tag("table").ItemTag("tr") to immediately switch from using an unordered list to a table.
  • DefaultItem: This can be used to set the properties of a newly inserted item.

An example of using the repeat fluent settings:

<%= form.Trigger("AddLink").Label("Add Link")
	.Insert(i => i.Nodeset(c => c.Links).At(At.Last))%>
<% form.Repeat(c => c.Links)
	.Tag("table")
	.ItemTag("tr")
	.DefaultItem(new Link { Name = "Enter link name", Url = "http://" } )
	.ItemTemplate(link => { %>
	<td><%= link.Input(l => l.Name) %></td>
	<td><%= link.Input(l => l.Url) %></td>
	<td><%= link.Trigger("Del").Label("x").Delete() %></td>
<% }); %>

A (small) javascript file is included in the project that facilitates the repeat functionality. Mvc XForms will work without any javascript, but actions, triggers and repeats all require script to allow the client-side interactivity with no post-back.

The way the repeat inserting works is that a json object is built to represent the repeat. Properties of the json include a template that is the HTML required for inserting a new item and any events that are bound to child controls. When an item is inserted, the template is inserted into the DOM, the IDs of the child controls are updated and any events are re-bound to the child controls. Events are only re-bound if they have been bound in the first place using the Action fluent settings on a control. An API of sorts exists in the script that enables javascript functions to be bound to insert and delete actions for further control over functionality. This will probably be the subject of a future post.

XForm

The XForm is the parent element and synonymous with an HTML form. The main parts of an XForm are the Model and Controls. An XForms Model consists of Instances, Submissions and Binds.

Instances

In XForms, these are XML data documents that the form controls bind to. This is what gets submitted to the server. In MVC XForms, the model is moved from the client to the server and CLR objects used instead of XML documents (although in the future there will hopefully be more support for "proper" XForms, including XML instances). It is possible to have multiple instances in XForms, but in MVC XForms, we have one instance for simplicity. This is defined as follows:

<% Html.XForm(ViewData.Model.Customer).Form(form => { %>

This outputs the HTML form tag and gives the XForm its "context". The Form method is a lambda which describes the XForm.

Submissions

These define where and how the data for the XForm is submitted. Multiple submissions are allowed per XForm for defining multiple submission targets. XForms has a much richer submissions module than HTML, but MVC XForms (at the moment) just uses the normal submission methods that HTML allows. The fluent settings for the XForm include:

  • Action: The URL for the XForm submission.
  • Method: The submission method.

For example, to POST form data including file uploads, the following might be used:

<% Html.XForm(ViewData.Model.Customer)
	.Action("Customer", "Edit")
	.Method(SubmissionMethod.FormDataPost)
	.Form(form => { %>

Binds

Binds are constraints on the model. Each bind points to a part of the instance and model item properties are then applied to the instance nodes. This allows various data constraints and manipulation. For example, you might want a property on the model to be required and have a length greater than 6 characters. This would be expressed in XForms as:

<bind nodeset="/customer/name" required="true()" constraint="string-length(.) > 6"/>

Currently MVC XForms does not support this functionality, for the time being you must use the ModelValidator. This is scheduled to be introduced in Release 2 and will be implemented via both attributes and a lambda API.

For more information see http://www.w3.org/TR/xforms11/#concepts-model

Be the first to rate this post

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

Tags:

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:

Powered by BlogEngine.NET 1.4.5.0
Theme by Mads Kristensen