NuGet Package of the Week: A different take on ASP.NET MVC Forms with ChameleonForms
One of the nice things about any modular system (like ASP.NET) is the ability to swap out the parts you don't like. As the authors of ChameleonForms state, HTML forms is a pain. It's repetitive, it's repetitive, and it's boring. While ASP.NET MVC's Form Helpers help a lot, they felt that helper methods like Html.EditorForModel didn't go far enough or give you enough flexibility. ChameleonForms adds its own templating model and attempts to be as DRY as possible. It also takes a number of issues head on like better handling for drop-down lists and lists of radio buttons, and it even supports Twitter Bootstrap 3 to you can bang out HTML forms ASAP.
ChameleonForms also is a nice example of a tidy and well-run small open source project. They've got a public Trello backlog board, excellent documentation, a continuous integration build, a good example project, and of course, they're on NuGet. Check out the other projects that the folks in the "MRCollective" work on as well, as they've got their own GitHub organization.
Often ChameleonForms tries to use C# for the whole form, rather than switching back and forth from Div to Html Helper. For example:
@using (var f = Html.BeginChameleonForm()) {
using (var s = f.BeginSection("Signup for an account")) {
@s.FieldFor(m => m.FirstName)
@s.FieldFor(m => m.LastName)
@s.FieldFor(m => m.Mobile).Placeholder("04XX XXX XXX")
@s.FieldFor(m => m.LicenseAgreement).InlineLabel("I agree to the terms and conditions")
}
using (var n = f.BeginNavigation()) {
@n.Submit("Create")
}
}
This is the whole form using usings for scoping, and it's nice and clean. How about a comparison example? Here's standard ASP.NET MVC:
@using (Html.BeginForm())
{
<fieldset>
<legend>A form</legend>
<dl>
<dt>@Html.LabelFor(m => m.RequiredString, "Some string")</dt>
<dd>@Html.TextBoxFor(m => m.RequiredString) @Html.ValidationMessageFor(m => m.RequiredString)</dd>
<dt>@Html.LabelFor(m => m.SomeEnum)</dt>
<dd>@Html.DropDownListFor(m => m.SomeEnum, Enum.GetNames(typeof(SomeEnum)).Select(x => new SelectListItem {Text = ((SomeEnum)Enum.Parse(typeof(SomeEnum), x)).Humanize(), Value = x})) @Html.ValidationMessageFor(m => m.SomeEnum)</dd>
<dt>@Html.LabelFor(m => m.SomeCheckbox)</dt>
<dd>@Html.CheckBoxFor(m => m.SomeCheckbox) @Html.LabelFor(m => m.SomeCheckbox, "Are you sure?") @Html.ValidationMessageFor(m => m.SomeCheckbox)</dd>
</dl>
</fieldset>
<div class="form_navigation">
<input type="submit" value="Submit" />
</div>
}
And here is the same form with ChameleonForms.
@using (var f = Html.BeginChameleonForm()) {
using (var s = f.BeginSection("A form")) {
@s.FieldFor(m => m.RequiredString).Label("Some string")
@s.FieldFor(m => m.SomeEnum)
@s.FieldFor(m => m.SomeCheckbox).InlineLabel("Are you sure?")
}
using (var n = f.BeginNavigation()) {
@n.Submit("Submit")
}
}
But these are basic. How about something more complex? This one has a bunch of variety, a number overloads and customizations, as well as a FileUpload (note that the form is a Multipart form):
@using (var f = Html.BeginChameleonForm(method: FormMethod.Post, enctype: EncType.Multipart))
{
<p>@f.LabelFor(m => m.SomeCheckbox).Label("Are you ready for: ") @f.FieldElementFor(m => m.SomeCheckbox) @f.ValidationMessageFor(m => m.SomeCheckbox)</p>
<p>@f.FieldElementFor(m => m.RequiredStringField).TabIndex(4)</p>
using (var s = f.BeginSection("My Section!", InstructionalText(), new{@class = "aClass"}.ToHtmlAttributes()))
{
using (var ff = s.BeginFieldFor(m => m.RequiredStringField, Field.Configure().Attr("data-some-attr", "value").TabIndex(3)))
{
@ff.FieldFor(m => m.NestedField).Attr("data-attr1", "value").TabIndex(2)
@ff.FieldFor(m => m.SomeEnum).Attr("data-attr1", "value")
@ff.FieldFor(m => m.SomeEnum).Exclude(SomeEnum.SomeOtherValue)
}
@s.FieldFor(m => m.SomeCheckbox).AsDropDown()
using (var ss = s.BeginSection("Nested section"))
{
@ss.FieldFor(m => m.FileUpload).Attr("data-attr1", "value")
}
@s.FieldFor(m => m.RequiredStringField).OverrideFieldHtml(new MvcHtmlString("Custom html <b>she-yeah</b>!"))
@s.FieldFor(m => m.TextAreaField).Cols(60).Rows(5).Label("Some Label").AutoFocus().TabIndex(1)
@s.FieldFor(m => m.SomeCheckbox).InlineLabel("Some label").WithHint("Format: XXX")
@s.FieldFor(m => m.SomeCheckbox).AsRadioList().WithTrueAs("True").WithFalseAs("False")
@s.FieldFor(m => m.ListId)
@s.FieldFor(m => m.ListId).AsRadioList()
@s.FieldFor(m => m.SomeEnums)
@s.FieldFor(m => m.SomeEnumsList).AsRadioList()
@s.FieldFor(m => m.Decimal)
@s.FieldFor(m => m.Int).AsInputGroup().Append(".00").Prepend("$")
@s.FieldFor(m => m.DecimalWithFormatStringAttribute)
@s.FieldFor(m => m.NullableInt)
@s.FieldFor(m => m.Child.ChildField)
@s.FieldFor(m => m.Child.SomeEnum).AsRadioList()
@s.FieldFor(m => m.RequiredStringField).Disabled()
@s.FieldFor(m => m.RequiredStringField).Readonly()
}
using (var n = f.BeginNavigation())
{
@n.Submit("Submit")
@n.Reset("Reset")
}
}
ChameleonForms also has a special NuGet package if you're using TwitterBootstrap that changes how forms with the BeginChameleonForm method render.
ChameleonForms also has some convenient extra abilities, like being able to automatically infer/create a [DisplayName] so you don't have to. If you're doing Forms in English and your preferred Display Name will end up just being your variable name this can be a useful time saver (although you may have opinions about its purity.)
So instead of the tedium of:
[DisplayName("Email address")]
public string EmailAddress { get; set; }
[DisplayName("First name")]
public string FirstName { get; set; }
You can just say this once, picking just one...this is an example where they use HumanizedLabels.
HumanizedLabels.Register(LetterCasing.AllCaps) => "EMAIL ADDRESS"
HumanizedLabels.Register(LetterCasing.LowerCase) => "email address"
HumanizedLabels.Register(LetterCasing.Sentence) => "Email address"
HumanizedLabels.Register(LetterCasing.Title) => "Email Address"
If you've got a lot of Forms to create and they're just no fun anymore, you should definitely give ChameleonForms a try. If you're a Twitter Bootstrap shop, doubly so, as that's where ChameleonForms really shines.
I'll do a few other posts exploring different ways to for Forms in ASP.NET MVC in the coming weeks. Be sure to explore the NuGet Package of the Week Archives as well!
PLUG: Did you know I have a YouTube channel? Subscribe over here. I've got tutorials on how to effectively use Windows 8 and 8.1, Build to Build walkthroughs of the latest versions of Windows 10, and I just started a new series I'm sure you'll want to share with your family called "How to REALLY use Microsoft Office." Help me out and spread the word!
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
It's a professional ASP.NET MVC template that helps you get over 97/100 on YSlow right out of the box! All while being secure out of the box using extra HTTP headers like Content-Security-Policy (CSP) etc.
It also includes popular NuGet packages like Elmah (Error Handling and Logging), Glimpse (Easier Debugging), NWebsec (Security) and Autofac (IOC Container), all preconfigured and ready to go right out the box!
Nice find! This seems to work in a similar manor as Fubu Html Tags which has an aim of providing a 'JQuery like syntax' for creating HTML elements in C#. I've found this to be a helpful tool to use for my own HTML helpers. Would be interested to know if these guys use this library under the hood.
I could be wrong, but it doesn't seem like ChameleonForms has any support for validation, like MVC does. However, I can see that it is on their Trello board though!
Dan
I'm one of the ChameleonForms developers.
One of the things that you get with ChameleonForms is consistency across all of your forms. One of the problems we often find with forms is that they are tedious in terms of markup and you will often miss out tags here and there causing the forms to look slightly different. ChameleonForms takes that possibility away because it abstracts the form template for you to ensure consistency.
You will probably get your designers to help write the html for the template (example here) and to write the CSS. If they are writing the form itself, then they would need a similar knowledge for using the MVC helpers as they would for Chameleon - they are very similar except Chameleon has less HTML markup scattered across the form (not that there is anything stopping you adding HTML markup for those rare situations that need it because the standard template doesn't cover it). We find that intellisense is your friend and generally it means it's a lot easier to quickly whip up a form as compared to having to remember a bunch of HTML markup.
We don't use Fubu - we have our own concept called field configuration.
We support the validation that is built-in with MVC - it's added automatically as part of the template (that comes by default, or you can customise). The items on the trello board I suspect you are referring to are our original strategy and are actually done :)
But can't say it's more readable than normal razor-syntax. As one who sees the finished code for the multipart form-example for the first time, it feels harder to read and understand what the result will actually be. Though I guess it's much more straightforward when I'm the one writing it from the beginning.
(I'm not saying not to use it. It's just an observation.)
There's also some additional validation you can get out of the box, for example:
https://github.com/MRCollective/ChameleonForms/wiki/list (validating that user selected items exist in a given list property, or in an enum)
https://github.com/MRCollective/ChameleonForms/wiki/datetime (extends DisplayFormat to parse and validate a submitted date on the server)
HTML is easy to code & easy for everyone (including designers) to understand. A designer would not have any idea how to modify the forms in the example & it just complicates things for no reasons at all.
This reminds me of asp.net web forms & is what is wrong with asp.net in general.. PLEASE DO NOT TOUCH MY HTML & STOP MAKING THINGS MORE COMPLICATED THAN THEY NEED TO BE....
http://formfactory.apphb.com/
https://github.com/mcintyre321/FormFactory
Thanks for the feedback!
We find it often makes sense to template your forms in one place, allowing your models to drive the HTML that's produced. This gives you consistency between all the forms in your application, all the usual benefits of type safety, as well as the ability to customise individual fields when you do run into a special case.
In saying that, the right tool for the job always depends on your environment. When I've had designers and developers working on the same application before, I've seen great results splitting the frontend content completely from the backend code using a framework like AngularJS.
We find it often makes sense to template your forms in one place, allowing your models to drive the HTML that's produced.
We have that. They're called display and editor templates. They've been around since ASP.NET MVC 2 and manage to work for that purpose without shaping C# and method chaining into a contorted DSL that obfuscates the rendered end-result.
Don't particularly want to start a debate, but we cover why we think ChameleonForms is needed including some discussion about display and editor templates in the documentation.
We're pretty happy with the DSL, find it very quick and easy to read and write and have had a lot of success using it across a number of projects for quite a number of years in both it's current incarnation and a previous one it was based on that we developed in a former company we worked for. We understand it's not for everyone and like Matt said above, we are firm believers in right tool for the job be it plain HTML, Angular JS forms, Editor templates or ChameleonForms :) If you have a technique that works for you then awesome!!
That said, the implementation of this idea is wonderfully solid, I just have issues with the idea that it is necessary.
Having a domain-specific language or good framework for spitting out a specific structure of HTML does not seem like an attempt to subvert HTML - it seems like an attempt to make using it richly more simple.
In the new world of web development, we need complete control of the markup. And I mean COMPLETE control. I don't even want C# code to generate textboxes or labels for me, even if it's convenient. And even though the helpers can be overridden and any attributes can be added, it's a matter of principle. And as someone mentioned, it seems to go against the idea of ASP.NET MVC 6.
As a developer I see the advantage of this, but there's no chance in hell this will even be looked at by a web designer.
And with intellisense it's even easier if they are open minded enough to use VS (now even more free than before) instead of whatever notepad clone. I like the 'ChameleonForms' (IMO you need a better name) idea and any frontdev worth their salt won't have a problem with it.
That being said I'm going to continue to use Fubu's HtmlTags and my MvcPowerTools, which allow me to define in code html conventions (widgets) based on the model/property type. I'm finding this approach more maintainable than (re)writing each form line by line, which I think it's the Chameleon's biggest flaw.
Probably would not use this as it ads more overhead than I'd like. The file upload example looks like a maintenance nightmare. Even with the DSL when I'm writing code in VS I don't want to have to assume what my "rendered" HTML is going to look like. A lot of sites I'm building these days are built with AJAX/JS templates anyway.
Like Scott said nicely run project though seems it serves a purpose for some!
Nice to see the Scott giving ChameleonForms some airtime, I've used it on a bunch of stuff and it's great for the LOB work I do most of the time.
Checkout what these guys repo https://github.com/MRCollective for other cool stuff they are into I really liked the Octopus work too.
I also have to say that most .net programmers only know c# well & very few know html & javascript properly.. In today's world html & javascript should be the most important languages to use websites & web apps. C# should only be used for back end stuff like getting/saving data to databases.
The BForms open source framework consists of a collection of ASP.NET MVC HTML Helpers, Javascript AMD modules, custom CSS made with SASS that extends Twitter Bootstrap v3 and makes development of rich user interfaces easy.
BForms Documentation
Comments are closed.