Fork me on GitHub

FormFactory

The ASP.NET MVC html form builder

View project on GitHub

Reflection form generation

Because you shouldn't have to update your html when you update your object model

Convention based rendering

Specify custom templates for rendering different object types.

Twitter bootstrap support

Styled for bootstrap (by default)

Embeddable

FormFactory.RazorEngine can be used in non ASP projects like console apps, services or WebAPI projects.

What is it?

FormFactory renders complex object forms automatically. It refects over an object model or method signature, and builds an intermediate model representing the form and properties. These models are then rendered using customisable templates.

FormFactory can build complex nested forms with rich content pickers. By following a few simple code conventions, properties with multiple choices and suggested values can be written in a few lines of code.

How to use it

//In a cshtml file
Html.PropertiesFor(someObject).Render(Html);
            
Install the library
Nuget install-package FormFactory.AspMvc
or for non ASP MVC projects
Nuget install-package FormFactory.RazorEngine
OR Install the content files (.cshtml, .js, .css)
Nuget install-package FormFactory.Templates
Update your layouts to include
<link href="/Content/FormFactory/FormFactory.css" rel="stylesheet" type="text/css"/>
<script src="/Scripts/FormFactory/FormFactory.js" type="text/javascript"></script>
 
Note: To use FormFactory without installing FormFactory.Templates you must install and configure the EmbeddedResourceVirtualPathProvider

Rendering objects

Auto Complete

AJAX suggestions using [SuggestionsUrl("...someurl...")]

In AutoCompleteExample.cs


namespace FormFactory.Example.Models.Examples
{
    public class AutoCompleteExample
    {

        [Required]
        [Placeholder("Type to find your location")]
        public string Location { get; set; }
        public IEnumerable<string> Location_suggestions()
        {
            return CultureInfo.GetCultures(CultureTypes.SpecificCultures)
                .Select(c => c.Name).Distinct();
        }

        [Required]
        [DisplayName("Countrie")]
        [Placeholder("Type to find your location")]
        [Description("AJAX suggestions using [SuggestionsUrl(\"...someurl...\")]")]
        [SuggestionsUrl("/home/CountrySuggestions")]
        public string CountryViaAjax { get; set; }
    }
}

Choices

By creating a method called {property}_choices you can create select lists

In ChoicesExample.cs


namespace FormFactory.Example.Models.Examples
{
    public class ChoicesExample
    {
        [Description("By creating a method called {property}_choices you can create select lists")]
        public string Gender { get; set; }
        public IEnumerable<string> Gender_choices()
        {
            return "male,female,not specified".Split(',');
        }
    }
}

Date Time Pickers

In DateTimePickersExample.cs


namespace FormFactory.Example.Models.Examples
{
    public class DateTimePickersExample
    {
        public DateTimePickersExample()
        {
            DateOfBirth = DateTime.Parse("15 Jan 1980");
            LastAccessTime = DateTime.UtcNow;
            DateOfBirthAsDateTimeOffset = DateTime.Parse("15 Jan 1980");
            LastAccessTimeAsDateTimeOffset = DateTime.UtcNow;

        }

        [DataType(DataType.Date)]
        public DateTime DateOfBirth { get; set; }

        public DateTime LastAccessTime { get; set; }

        [DataType(DataType.Date)]
        public DateTimeOffset DateOfBirthAsDateTimeOffset { get; set; }

        public DateTimeOffset LastAccessTimeAsDateTimeOffset { get; set; }
    }
}

Editable Collections

Writable ICollections get rendered as re-orderable lists

In EditableCollectionsExample.cs


namespace FormFactory.Example.Models.Examples
{
    public class EditableCollectionsExample
    {

        public EditableCollectionsExample()
        {
            TopMovies = new List<Movie>()
            {
                new Movie() {Title = "Fight Club"},
                new Movie() {Title = "Bambi"},
            };

        }

        [Description("Writable ICollections get rendered as re-orderable lists")]
        public ICollection<Movie> TopMovies { get; set; }
    }

    public class Movie
    {
        [Required]
        [StringLength(64, MinimumLength = 2)]
        public string Title { get; set; }
    }
}

Enum

Enums are rendered as dropdowns

In EnumExample.cs


namespace FormFactory.Example.Models.Examples
{
    public class EnumExample
    {
        public EnumExample()
        {
            Position = Positions.SeniorSubcontractor;
        }

        [Description("Enums are rendered as dropdowns")]
        public Positions Position { get; set; }

    }

    public enum Positions
    {
        Contractor,
        [Display(Name = "Snr. Subcontractor")]
        SeniorSubcontractor
    }
}

List Rendering

settable IEnumerable<strings> with choices get rendered as multi-selects

In ListRenderingExample.cs


namespace FormFactory.Example.Models.Examples
{
    public class ListRenderingExample
    {
        public ListRenderingExample()
        {
            //These values will be pre-selected
            RestrictedMaterials = new[] {"Guns", "Explosives"};
        }

        [Description("settable IEnumerable<strings> with choices get rendered as multi-selects")]
        public IEnumerable<string> RestrictedMaterials { get; set; }

        public IEnumerable<string> RestrictedMaterials_choices()
        {
            return new[] { "Guns", "Knives", "Explosives", "Nuclear Waste", "Weaponised Viruses" };
        }

    }



    public class Hobby
    {
        public Hobby(string name, int years)
        {
            Name = name;
            Years = years;
        }

        public string Name { get; private set; }

        public int Years { get; private set; }
    }

}

Nested Forms

Choices can be objects too, FormFactory works recursively so you can create complex nested forms

In NestedFormsExample.cs


namespace FormFactory.Example.Models.Examples
{
    public class NestedFormsExample { 

        [Description("Choices can be objects too, FormFactory works recursively so you can create complex nested forms")]
        public PhoneModel PhoneModel  { get; set; }
        public IEnumerable<PhoneModel> PhoneModel_choices()
        {
            //this data could come from your database
            var data = new[]
            {
                new {Company = "Apple", Models = new[] { "iPhone 5s", "iPhone 6"}},
                new {Company = "Sony", Models = new[] { "Z3", "Z3 Compact"}}
            };

            return data.Select(i => new PhoneModel()
            {
                Company = i.Company,
                ModelChoices = i.Models
            }.DisplayName(i.Company));
        }

        
    }

    public class PhoneModel
    {
        
        [DataType("Hidden")] //We hide this field as it will have been displayed in the select list
        public string Company { get; set; }

        public string Model { get; set; }
        internal IEnumerable<string> ModelChoices;
        public IEnumerable<string> Model_choices()
        {
            return ModelChoices;
        }
    }


}

Nested Forms 2

Twitter
Facebook

In NestedFormsExample2.cs


namespace FormFactory.Example.Models.Examples
{
    public class NestedFormsExample2
    {
        public NestedFormsExample2()
        {
            //This data will be preselected by use of the .Selected() extension method
            ContactMethod = new SocialMedia() {SocialMediaType = new SocialMediaType.Facebook()};
        }

        public ContactMethod ContactMethod { get; set; }
        //you can use objects as choices to create complex nested menus
        public IEnumerable<ContactMethod> ContactMethod_choices()
        {
            yield return ContactMethod is NoContactMethod ? ContactMethod.Selected() : new NoContactMethod();
            yield return ContactMethod is SocialMedia ? ContactMethod.Selected() : new SocialMedia();
            yield return ContactMethod is PhoneContactMethod ? ContactMethod.Selected() : new PhoneContactMethod();
        }
    }


    public class NoContactMethod : ContactMethod
    {
    }

    public class PhoneContactMethod : ContactMethod
    {
        public PhoneContactMethod()
        {
            Type = new PhoneType.Landline();
        }

        public string Number { get; set; }

        public PhoneType Type { get; set; }

        public IEnumerable<PhoneType> Type_choices()
        {
            yield return Type is PhoneType.Mobile ? Type : new PhoneType.Mobile();
            yield return Type is PhoneType.Landline ? Type : new PhoneType.Landline();

        }

    }

    public class PhoneType
    {
        public class Mobile : PhoneType
        {
            public string Provider { get; set; }

            public IEnumerable<string> Provider_suggestions()
            {
                return "Orange,TMobile,Three".Split(',');
            }
        }

        public class Landline : PhoneType
        {
            public string Provider { get; set; }

            public IEnumerable<string> Provider_suggestions()
            {
                return "TalkTalk,BT".Split(',');
            }
        }
    }

    public class SocialMedia : ContactMethod
    {
        public SocialMedia()
        {
            SocialMediaType = new SocialMediaType.Twitter();
        }

        [DataType("Radio")]
        public SocialMediaType SocialMediaType { get; set; }

        public IEnumerable<SocialMediaType> SocialMediaType_choices()
        {
            yield return SocialMediaType is SocialMediaType.Twitter ? 
                SocialMediaType.Selected() : new SocialMediaType.Twitter();
            yield return SocialMediaType is SocialMediaType.Facebook ? 
                SocialMediaType.Selected() : new SocialMediaType.Facebook();
        }
    }

    public abstract class SocialMediaType
    {
        public class Twitter : SocialMediaType
        {
            [Required, Placeholder("@yourhandle")]
            public string TwitterHandle { get; set; }
        }

        public class Facebook : SocialMediaType
        {
            [Required]
            public string FacebookName { get; set; }
        }
    }
}

Simple Properties

Readonly properties are rendered as readonly
Writable properties can use MVC validation attributes

In SimplePropertiesExample.cs


namespace FormFactory.Example.Models.Examples
{
    public class SimplePropertiesExample
    {
        public SimplePropertiesExample()
        {
            Enabled = true;
        }

        [Description("Readonly properties are rendered as readonly")]
        public int DaysSinceStartOfYear { get { return DateTime.Today.DayOfYear; } }

        [Required()]
        [StringLength(32, MinimumLength = 8)]
        [Description("Writable properties can use MVC validation attributes")]
        public string Name { get; set; }

        public bool Enabled { get; set; }

    }
}

Form for MVC action:

Given this action...

    [HttpPost] public virtual ActionResult SignIn(string email, [DataType(DataType.Password)] string password)
        { //... } 

..writing this in the view...

 
using (var form = Html.FormForAction((HomeController c, string p0, string p1) => c.SignIn(p0, p1)))
{
    form.Render(); //renders the form
} //.Dispose() closes the form
    

 

...will render this form: