Skip to content

Instantly share code, notes, and snippets.

@joeriks
Created April 8, 2011 06:14
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joeriks/909378 to your computer and use it in GitHub Desktop.
Save joeriks/909378 to your computer and use it in GitHub Desktop.
HtmlFormsHelper
EDIT : HtmlFormsHelper has evolved to https://github.com/joeriks/DynaForms
@using HtmlFormHelpers
@{
var form = new HtmlFormDescriptor("project-form", project);
// Create a new project, or get it from the database (using PetaPoco in this example).
Project project;
if (UrlData[0]==null)
{
project = new Project();
// Set a value that is not used in or affected by the form
project.UserId = App.CurrentUserId;
}
else
{
project = App.Db.Fetch<Project>("SELECT * FROM Project WHERE ID=@0",UrlData[0]).SingleOrDefault();
}
// Get project types for a dropdown (create a dictionary with keys & values).
var projectTypes = App.Db.Fetch<ProjectType>("SELECT * FROM ProjectType").ToDictionary<ProjectType, string, string>(k => k.Id.ToString(), v => v.Name);
form.Add("Name", "Project name", required: true)
.Add("ClientNumber", "Client number", required: true)
.Add("Type", "Project type", type: "select", dropDownValues: projectTypes)
.Add("Note", "Client number", type: "textarea")
.Add("Submit",type:"submit");
if (IsPost)
{
form.TryUpdateModel(Request.Form);
if (!form.Validation.IsValid)
{
@form.Html();
}
else
{
// Form is valid - save data
}
}
else
{
@form.Html();
}
}
@* Uncomment this to add client side validation
<script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.5.2.min.js"></script>
<script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jquery.validate/1.8/jquery.validate.min.js"></script>
@Html.Raw(form.ClientSideScript())
*@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Collections.Specialized;
using System.Text.RegularExpressions;
using System.Dynamic;
using System.Text;
namespace HtmlFormHelpers
{
public static class ObjectExtensions
{
public static dynamic ToExpando(this object o)
{
// Stolen from Rob Conery's Massive.cs
var result = new ExpandoObject();
var d = result as IDictionary<string, object>; //work with the Expando as a Dictionary
if (o.GetType() == typeof(ExpandoObject)) return o; //shouldn't have to... but just in case
if (o.GetType() == typeof(NameValueCollection) || o.GetType().IsSubclassOf(typeof(NameValueCollection)))
{
var nv = (NameValueCollection)o;
nv.Cast<string>().Select(key => new KeyValuePair<string, object>(key, nv[key])).ToList().ForEach(i => d.Add(i));
}
else
{
var props = o.GetType().GetProperties();
foreach (var item in props)
{
d.Add(item.Name, item.GetValue(o, null));
}
}
return result;
}
}
public class HtmlFormDescriptor
{
public List<HtmlFieldDescriptor> Fields { get; set; }
public string Name { get; set; }
public ExpandoObject Model { get; set; }
public class HtmlFieldDescriptor
{
public string LabelText { get; set; }
public string Type { get; set; }
public string FieldName { get; set; }
public bool Required { get; set; }
public bool Email { get; set; }
public int MinLength { get; set; }
public int MaxLength { get; set; }
public string RegEx { get; set; }
public int? Min { get; set; }
public int? Max { get; set; }
public Dictionary<string, string> DropDownValueList { get; set; }
public string Label()
{
if (LabelText == "")
return FieldName;
else
return LabelText;
}
//public string RequiredMessage { get; set; }
//public string EmailMessage { get; set; }
//public string MinLengthMessage { get; set; }
//public string MaxLengthMessage { get; set; }
//public string RegExMessage { get; set; }
}
public HtmlFormDescriptor(string name, object model = null)
{
Name = name;
Model = model.ToExpando();
Fields = new List<HtmlFieldDescriptor>();
}
public class ValidationResult
{
public struct FieldError
{
public string Field;
public string Error;
public string Label;
}
public bool IsValid { get; set; }
public List<FieldError> Errors { get; set; }
public ValidationResult()
{
Errors = new List<FieldError>();
IsValid = true;
}
public void AddError(string fieldName, string errorMessage, string label)
{
Errors.Add(new FieldError() { Field = fieldName, Error = errorMessage, Label = label });
}
public string ValidationResultMessage(string rowTemplate)
{
string retval = "";
if (!IsValid)
{
foreach (var r in Errors)
{
retval += rowTemplate.Replace("{name}", r.Field).Replace("{label}", r.Label).Replace("{error}", r.Error);
}
}
return retval;
}
}
public bool ContainsFieldName(string fieldName)
{
foreach (var x in Fields)
{
if (x.FieldName == fieldName)
{
return true;
}
}
return false;
}
public HtmlFormHelpers.HtmlFormDescriptor Add(string fieldName, string labelText = "", string type = "text", bool required = false, bool email = false, bool isNumeric = false, int maxLength = 0, int minLength = 0, int? max = null, int? min = null, string regEx = "", Dictionary<string, string> dropDownValues = null)
{
var f = new HtmlFieldDescriptor();
f.FieldName = fieldName;
f.LabelText = labelText;
f.Type = type;
f.Required = required;
f.Email = email;
f.MinLength = minLength;
f.MaxLength = maxLength;
f.RegEx = regEx;
f.Min = min;
f.Max = max;
f.DropDownValueList = dropDownValues;
Fields.Add(f);
return this;
}
public static bool IsValidRegex(string value, string regEx)
{
var rx = new Regex(regEx);
return rx.IsMatch(value);
}
public static bool IsValidEmail(string email)
{
// source: http://thedailywtf.com/Articles/Validating_Email_Addresses.aspx
var emailRegEx = @"^[-!#$%&'*+/0-9=?A-Z^_a-z{|}~](\.?[-!#$%&'*+/0-9=?A-Z^_a-z{|}~])*@[a-zA-Z](-?[a-zA-Z0-9])*(\.[a-zA-Z](-?[a-zA-Z0-9])*)+$";
return IsValidRegex(email, emailRegEx);
}
private ValidationResult validationResult;
public ValidationResult Validation
{
get { return validationResult; }
set { validationResult = value; }
}
public ValidationResult Validate(object model, ValidationResult validationResult = null)
{
if (validationResult == null)
validationResult = new ValidationResult();
var modelDictionary = (IDictionary<string, object>)(model.ToExpando());
foreach (var x in Fields)
{
// var property = model.GetType().GetProperty(x.FieldName);
// if (property != null) value = (property.GetValue(model, null) ?? "").ToString();
var value = "";
if (modelDictionary.ContainsKey(x.FieldName))
{
value = modelDictionary[x.FieldName].ToString();
}
if (x.Required && string.IsNullOrEmpty(value))
{
validationResult.AddError(x.FieldName, "Required field", x.Label());
}
if (x.Email && !IsValidEmail(value))
{
validationResult.AddError(x.FieldName, "Invalid email address", x.Label());
}
if (x.MinLength != 0 && value.ToString().Length < x.MinLength)
{
validationResult.AddError(x.FieldName, "Mininum length is " + x.MinLength.ToString(), x.Label());
}
if (x.MaxLength != 0 && value.ToString().Length > x.MaxLength)
{
validationResult.AddError(x.FieldName, "Maximum length is " + x.MaxLength.ToString(), x.Label());
}
if (x.RegEx != "" && !IsValidRegex(value.ToString(), x.RegEx))
{
validationResult.AddError(x.FieldName, "Invalid", x.Label());
}
}
validationResult.IsValid = (validationResult.Errors.Count == 0);
this.validationResult = validationResult;
return validationResult;
}
public HtmlString Html(object model = null, string action = "#", string method = "post")
{
var sb = new StringBuilder();
sb.Append("<form id=\"" + Name + "\" method=\"" + method + "\" action=\"" + action + "\">\n");
if (model == null && this.Model != null) model = this.Model; else model = model.ToExpando();
var modelDictionary = (IDictionary<string, object>)model;
// Is there a global error message?
string errorMessage = "";
if (validationResult != null && !validationResult.IsValid)
{
foreach (var e in validationResult.Errors)
{
if (e.Field == "")
{
if (errorMessage != "") errorMessage += ", ";
errorMessage += e.Error;
}
}
}
if (errorMessage != "") sb.Append("<div>" + errorMessage + "</div>");
// Create the Html tags for the form
foreach (var h in Fields)
{
var labelText = h.Label();
string value = "";
if (model != null)
{
foreach (var item in modelDictionary)
{
if (item.Key == h.FieldName)
{
object v = item.Value;
if (v != null)
value = v.ToString();
}
}
}
errorMessage = "";
if (validationResult != null && !validationResult.IsValid)
{
foreach (var e in validationResult.Errors)
{
if (e.Field == h.FieldName)
{
if (errorMessage != "") errorMessage += ", ";
errorMessage += e.Error;
}
}
}
if (h.Type == "text")
{
sb.Append(" <div class=\"labelinput\">\n");
sb.Append(" <label for=\"" + h.FieldName + "\">" + labelText + "</label>\n");
sb.Append(" <input type=\"" + h.Type + "\" id=\"" + h.FieldName + "\" name=\"" + h.FieldName + "\" value=\"" + value + "\"/>" + errorMessage + "\n");
sb.Append(" </div>\n");
}
if (h.Type == "textarea")
{
sb.Append(" <div class=\"labeltextarea\">\n");
sb.Append(" <label for=\"" + h.FieldName + "\">" + labelText + "</label>\n");
sb.Append(" <textarea id=\"" + h.FieldName + "\" name=\"" + h.FieldName + "\">"+ value + "</textarea>\n");
sb.Append(" </div>\n");
}
if (h.Type == "checkbox")
{
sb.Append(" <div class=\"labelcheckbox\">\n");
sb.Append(" <label for=\"" + h.FieldName + "\">" + labelText + "</label>\n");
sb.Append(" <input type=\"" + h.Type + "\" id=\"" + h.FieldName + "\" name=\"" + h.FieldName + "\" value=\"" + value + "\"/>" + errorMessage + "\n");
sb.Append(" </div>\n");
}
if (h.Type == "select")
{
sb.Append(" <div class=\"labelselect\">\n");
sb.Append(" <label for=\"" + h.FieldName + "\">" + labelText + "</label>\n");
sb.Append(" <select id=\"" + h.FieldName + "\" name=\"" + h.FieldName + "\">\n");
foreach (var item in h.DropDownValueList)
{
if (item.Key != value)
sb.Append(" <option value=\"" + item.Key + "\">");
else
sb.Append(" <option value=\"" + item.Key + "\" selected=\"selected\">");
sb.Append(item.Value.ToString());
sb.Append(" </option>\n");
}
sb.Append("</select>\n");
}
if (h.Type == "submit")
{
sb.Append(" <div class=\"submit\">\n");
sb.Append(" <input type=\"" + h.Type + "\" id=\"" + h.FieldName + "\" name=\"" + h.FieldName + "\" value=\"" + labelText + "\"/>" + errorMessage + "\n");
sb.Append(" </div>\n");
}
if (h.Type == "hidden")
{
sb.Append(" <input type=\"" + h.Type + "\" id=\"" + h.FieldName + "\" name=" + h.FieldName + " value=\"" + value + "\"/>\n");
}
}
sb.Append("</form>\n");
return new HtmlString(sb.ToString());
}
public string ClientSideScript()
{
var script = @"<script type=""text/javascript"">
jQuery(document).ready(function() {
jQuery('#{formname}').validate({{json}});
});
</script>";
var retval = script.Replace("{formname}", Name).Replace("{json}", jQueryValidateRulesJson());
return retval;
}
public string jQueryValidateRulesJson()
{
var jsonString = "rules: {\n";
var rules = "";
foreach (var h in Fields)
{
var fieldRules = "";
if (h.Required)
{
fieldRules += "required:true,";
}
if (h.Email)
{
fieldRules += "email:true,";
}
if (h.MaxLength != 0)
{
fieldRules += "maxlength:" + h.MaxLength.ToString() + ",";
}
if (h.MinLength != 0)
{
fieldRules += "minlength:" + h.MinLength.ToString() + ",";
}
if (h.Max != null)
{
fieldRules += "max :" + h.Max.ToString() + ",";
}
if (h.Min != null)
{
fieldRules += "min :" + h.Min.ToString() + ",";
}
if (fieldRules != "")
{
fieldRules = fieldRules.TrimEnd(',');
if (rules != "") { rules += ",\n"; }
rules += h.FieldName + ": {";
rules += fieldRules + "}";
}
}
jsonString += rules;
jsonString += "}\n";
return jsonString;
}
/// <summary>
/// Loop through each member of the model and try set the value with a given dictionary (f ex a Request.Form)
/// </summary>
/// <param name="dictionary"></param>
/// <returns></returns>
public ValidationResult TryUpdateModel(NameValueCollection dictionary)
{
var validationResult = new ValidationResult();
var modelDictionary = (IDictionary<string, object>)this.Model;
try
{
foreach (var key in modelDictionary.Keys.ToArray<string>())
{
if (dictionary.AllKeys.Contains(key) && ContainsFieldName(key))
{
((IDictionary<string, object>)this.Model)[key] = dictionary[key];
}
}
}
catch (Exception ex)
{
validationResult.AddError("", System.Web.HttpUtility.HtmlEncode(ex.Message), "");
validationResult.IsValid = false;
}
this.validationResult = validationResult;
if (!validationResult.IsValid)
return validationResult;
else
return Validate(this.Model, validationResult);
}
}
}
using System;
using System.Collections.Generic;
using System.Web;
using System.Linq;
using Nancy;
using HtmlFormHelpers;
using SisoDb;
/// <summary>
/// Requires the NuGet packages Nancy.Hosting.Aspnet and SisoDb
/// and a local Database in .\Sqlexpress called sisodemo
/// </summary>
///
public class NancyStart : Nancy.NancyModule
{
public class Product
{
public Guid SisoId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string SomeIgnoredProperty { get; set; }
}
public static SisoDb.ISisoDatabase db;
public static Nancy.ViewEngines.SuperSimpleViewEngine viewEngine;
public static string FlashMessage;
string htmlListProductNames()
{
IEnumerable<Product> products;
var retval = "";
using (var uow = db.CreateUnitOfWork())
{
products = uow.GetAll<Product>();
dynamic model = new System.Dynamic.ExpandoObject();
model.Products = products;
var tableTemplate = @"
<table>
<tr>
<td>Name</td>
<td>Description</td>
<td colspan=""2"">&nbsp;</td>
</tr>
@Each.Products
<tr>
<td>@Current.Name</td>
<td>@Current.Description</td>
<td><a href=""/products/edit/@Current.SisoId"">Edit</a></td>
<td><a href=""/products/delete/@Current.SisoId"">Delete</a></td>
</tr>
@EndEach
</table>";
retval = viewEngine.Render(tableTemplate, model);
}
return retval;
}
public string htmlPageView(string title, string htmlContent, string flashMessage = "")
{
string htmlPageTemplate = @"<html><head>
<title>{title}</title>
{css}
{scripts}
</head>
<body>
{menu}
{flash}
<h1>{title}</h1>
{content}
</body>
</html>";
string htmlMenu = @"<ul class=""menu""><li><a href=""/products"">Products</a></li></ul>";
string htmlCss = @"<style type=""text/css"">
div.labelinput, div.submit {clear:both;}
.labelinput label {width:100px;display:block;float:left;}
.labelinput input {width:200px;display:block;float:left;}
div.flash {width:97%;margin:auto;padding:10px;background-color:#cdcdcd;}
</style>";
string htmlScripts = @"<script type=""text/javascript"" src=""http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.5.2.min.js""></script>
<script type=""text/javascript"" src=""http://ajax.aspnetcdn.com/ajax/jquery.validate/1.8/jquery.validate.min.js""></script>";
string htmlFlashTemplate = @"<div class=""flash"">{message}</div>";
string htmlFlash = "";
if (!String.IsNullOrEmpty(flashMessage)) htmlFlash = htmlFlashTemplate.Replace("{message}", flashMessage);
string view = "";
view = htmlPageTemplate
.Replace("{title}", title)
.Replace("{css}", htmlCss)
.Replace("{scripts}", htmlScripts)
.Replace("{menu}", htmlMenu)
.Replace("{flash}", htmlFlash)
.Replace("{content}", htmlContent);
return view;
}
public NancyStart()
: base("/products")
{
if (db == null)
{
var cnInfo = new SisoConnectionInfo(
@"sisodb:provider=Sql2008||plain:Data source=.\sqlexpressr2;
Initial catalog=sisodemo;Integrated security=SSPI;");
db = new SisoDbFactory().CreateDatabase(cnInfo);
}
if (viewEngine == null)
{
viewEngine = new Nancy.ViewEngines.SuperSimpleViewEngine();
}
var productForm = new HtmlFormDescriptor("product-form", includeClientSideScript: true);
productForm.Add("Name", required: true);
productForm.Add("Description");
productForm.Add("Submit", type: "submit");
Get["/"] = x =>
{
string htmlInsertForm = "<h2>New product</h2>" + productForm.Html(action: "/products/insert");
string htmlFlash = FlashMessage;
FlashMessage = "";
return htmlPageView("List products", htmlListProductNames() + htmlInsertForm, htmlFlash);
};
Get["/edit/{id}"] = x =>
{
Product product;
string htmlEditForm = "";
using (var uow = db.CreateUnitOfWork())
{
product = uow.GetById<Product>((System.Guid)x.id);
htmlEditForm = productForm.Html(product);
}
return htmlPageView("Edit product", htmlEditForm);
};
Get["/delete/{id}"] = x =>
{
string htmlDeleteResult = "";
using (var uow = db.CreateUnitOfWork())
{
uow.DeleteById<Product>((System.Guid)x.id);
uow.Commit();
htmlDeleteResult += "<p>Successfully deleted the product</p>";
}
FlashMessage = htmlDeleteResult;
return Response.AsRedirect(this.ModulePath);
};
Post["/edit/{id}"] = x =>
{
var htmlEditResult = "";
Product product;
using (var uow = db.CreateUnitOfWork())
{
product = uow.GetById<Product>((System.Guid)x.id);
htmlEditResult = productForm.Html(product);
}
//var x = ((Nancy.DynamicDictionary)Request.Form).
var validationResult = (HtmlFormDescriptor.ValidationResult)productForm.TryUpdateModel<Product>(Request.Form, ref product);
if (validationResult.IsValid)
{
using (var uow = db.CreateUnitOfWork())
{
uow.Update<Product>(product);
uow.Commit();
htmlEditResult = "<p>Updated product id " + product.SisoId.ToString() + "</p>";
}
FlashMessage = htmlEditResult;
return Response.AsRedirect(this.ModulePath);
}
else
{
htmlEditResult = "<h1>Something is not valid</h1>";
htmlEditResult += productForm.Html(product);
return htmlPageView("Edit product", htmlEditResult);
}
};
Post["/insert"] = x =>
{
var htmlInsertResult = "";
var myProduct = new Product();
var validationResult = (HtmlFormDescriptor.ValidationResult)productForm.TryUpdateModel<Product>(Request.Form, ref myProduct);
if (validationResult.IsValid)
{
using (var uow = db.CreateUnitOfWork())
{
uow.Insert(myProduct);
uow.Commit();
htmlInsertResult = "<p>New product inserted with id " + myProduct.SisoId.ToString() + "</p>";
}
FlashMessage = htmlInsertResult;
return Response.AsRedirect(this.ModulePath);
}
else
{
htmlInsertResult = "<h1>Something is not valid</h1>";
htmlInsertResult += productForm.Html(myProduct);
return htmlPageView("Insert product", htmlInsertResult);
}
};
}
}
@joeriks
Copy link
Author

joeriks commented Apr 9, 2011

For more information and a demo : Html Form Helper [blogged]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment