Created
April 8, 2011 06:14
-
-
Save joeriks/909378 to your computer and use it in GitHub Desktop.
HtmlFormsHelper
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()) | |
*@ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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""> </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); | |
} | |
}; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
For more information and a demo : Html Form Helper [blogged]