Im new to ASP MVC but im coming across from using Castle Monorail so i sort of know my way around and learning as i go.
The scenario I have is: Im displaying a form whos inputs are entirly data driven, I wanted to use the unobtrusive validation which worked really nicely with Data Annotations, but since i dont have a model i can annotate (the viewmodel is a list containing
input name and data type with a few flags) i did some digging to see how the client-side validation attributes were used.
What i still dont understand is how the unobtrusive js script identifies form inputs to validate, the wierd thing was that it would happily parse hidden fields Html.Hidden() and add validation attributes to them (which i diddnt want!) but it would not process
the text boxes / select lists at all so i had to add all the attributes manually... i must be missing something here..
So i tried adding the attributes via the Html Helpers. Using Html.TextBox I needed to specify data-val-XXX attributes and writing something like:
Html.TextBox(field.field_name,null, new { data-val = "true", data-val-range-min = 0, data-val-range-max = 2, data-val-range = "Quantity must be between 0 and 2"})
This would fail, it seems the helper wont accept attributes with a dash/hypen. Not a huge problem, i bypassed the helpers and wrote the markup by hand but it feels like im missing a trick somewhere.
So now my inputs are being generated, ive given them the correct attributes and the client side validation is working nicely... except for the select lists which im using the helpers to generate, so these get validated serverside for now. I dont like the fact my view looks like tag soup (im seriously considering rewriting it using razor) and im left feeling that this could be done better.
I would really like to use the Html Helpers for the rest of the inputs, but im left thinking i may have to roll my own which seems unnecessary since i know they already do what i want them to (when using Data Annotations). If anyone can shed some light on what the "proper" way to do this is (ie add validation to dynamic form inputs) or anything else ive overlooked i'd apreciate it :)
The full view is below:
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<dynamic>" %>
<script src="<%: Url.Content("~/Scripts/jquery.validate.min.js") %>" type="text/javascript"></script>
<script src="<%: Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js") %>" type="text/javascript"></script>
<script>
jQuery(document).ready(function ($) {
$("[type='date']").datepicker();
});
</script>
<% using (Ajax.BeginForm("DynamicForm", "TreeAdmin", new AjaxOptions
{
UpdateTargetId = "TreeAdminResult"
})) { %>
<div>
<fieldset>
<legend>Additional Information</legend>
<%: Html.ValidationSummary(true, "Please correct the errors and try again.") %>
<% foreach (Hinet.Data.Models.TemplateField field in ViewBag.Fields)
{
var previousFormValue = string.Empty;
if(ViewBag.FormCollection != null)
{
previousFormValue = ViewBag.FormCollection[field.field_name];
}
%>
<div class="editor-label">
<%: Html.Label(field.field_name, field.field_name) %>
</div>
<div class="editor-field">
<%
switch (field.DataType)
{
case Hinet.Data.Models.TemplateField.FieldType.String:
%>
<input id="<%:field.field_name%>" name="<%:field.field_name%>"
type="text" value="<%:previousFormValue%>" <%if(field.IsRequired){ %> data-val-required="" <% } %>
data-val="true" data-val-length-max="45" data-val-length="The field must be less than 45 characters"/>
<%
break;
case Hinet.Data.Models.TemplateField.FieldType.Decimal:
%>
<input id="<%:field.field_name%>" name="<%:field.field_name%>"
type="number" value="<%:previousFormValue%>" <%if(field.IsRequired){ %> data-val-required="" <% } %>
data-val="true" data-val-number="The field must be a number" data-val-range="Must be less than 1Trilion"/>
<%
if(field.IsCurrency)
{
%>
<%: Html.DropDownList(field.field_name+"_Currency",(SelectList)ViewBag.Currencies,"Select...")%>
<%
}
break;
case Hinet.Data.Models.TemplateField.FieldType.Integer:
if (field.IsCurrencyType)
{
%>
<%: Html.DropDownList(field.field_name, (SelectList)ViewBag.Currencies, "Select...")%>
<%
}
else
{
%>
<input id="<%:field.field_name%>" name="<%:field.field_name%>"
type="number" value="<%:previousFormValue%>" <% if (field.IsRequired) { %> data-val-required="" <% } %>
data-val="true" data-val-number="The field must be a number" data-val-range="Must be less than 1Billion" />
<%
}
break;
case Hinet.Data.Models.TemplateField.FieldType.DateTime:
%>
<input id="<%:field.field_name%>" name="<%:field.field_name%>"
type="date" value="<%:previousFormValue%>" <%if(field.IsRequired){ %> data-val-required="" <% } %>
data-val="true" data-val-date="The field must be a date" />
<%
break;
}%>
<% =Html.ValidationMessage(field.field_name)%>
</div>
<% } %>
<p>
<input type="submit" value="Submit" />
</p>
<%: Html.Hidden("treeId", (int)ViewBag.TreeId) %>
<%: Html.Hidden("entityId", (int)ViewBag.EntityId) %>
</fieldset>
</div>
<% } %>
The simplest way is to define a "BrokerAttribute" that you apply to the value property of your list of name/values. The BrokerAttribute has just one parameter, the name of another field that you put in the items of your list. This filed contains the Type,
and parameters of THE ACTUAL validation attribute to apply.
In order to implemet the Broker attribute, for server validation you access the filed passed in the attribute create an instance of the validation attribute to apply, via reflection and call its isValid method.
However you can't simply store the field containing the validation rules on the client say into an hidden filed otherwise a malicious user might bypass server validation by modifying this field. Thus you have to store it either in the session or in tempdata,
or ...into an encrypted and signed hidden field...so you are
For the client validation of your Broker attribute you must implement a BrokerAdapter that ineritts from DataAnnotationsModelValidator<BrokerAttribute>
Here you have to ofverrode the methoid:
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
that is the one responsible fro the production of the client code. In turn this attribute need just to call the GetClientValidationRules() of the adapter of the validation attribute to apply. In order to have this adapter from the type fo the attribute
the piece of code taken form the sources of Mvc3 show the association for the standard attributes:
internal static Dictionary<Type, DataAnnotationsModelValidationFactory> AttributeFactories = new Dictionary<Type, DataAnnotationsModelValidationFactory>() { { typeof(RangeAttribute), (metadata, context, attribute) => new RangeAttributeAdapter(metadata, context, (RangeAttribute)attribute) }, { typeof(RegularExpressionAttribute), (metadata, context, attribute) => new RegularExpressionAttributeAdapter(metadata, context, (RegularExpressionAttribute)attribute) }, { typeof(RequiredAttribute), (metadata, context, attribute) => new RequiredAttributeAdapter(metadata, context, (RequiredAttribute)attribute) }, { typeof(StringLengthAttribute), (metadata, context, attribute) => new StringLengthAttributeAdapter(metadata, context, (StringLengthAttribute)attribute) }, }; For your custom attributes...build a table or...similar
tomas-bell
0 Points
1 Post
MVC3 data driven form - Unobtrusive Validation without Data Annotations
Jul 23, 2011 07:00 PM|LINK
Im new to ASP MVC but im coming across from using Castle Monorail so i sort of know my way around and learning as i go.
The scenario I have is: Im displaying a form whos inputs are entirly data driven, I wanted to use the unobtrusive validation which worked really nicely with Data Annotations, but since i dont have a model i can annotate (the viewmodel is a list containing input name and data type with a few flags) i did some digging to see how the client-side validation attributes were used.
http://msdn.microsoft.com/en-sg/vs2010trainingcourse_aspnetmvccustomvalidation_topic5 - Was useful in explaining how the attributes worked and what the "pre-built" ones were
What i still dont understand is how the unobtrusive js script identifies form inputs to validate, the wierd thing was that it would happily parse hidden fields Html.Hidden() and add validation attributes to them (which i diddnt want!) but it would not process the text boxes / select lists at all so i had to add all the attributes manually... i must be missing something here..
So i tried adding the attributes via the Html Helpers. Using Html.TextBox I needed to specify data-val-XXX attributes and writing something like:
Html.TextBox(field.field_name,null, new { data-val = "true", data-val-range-min = 0, data-val-range-max = 2, data-val-range = "Quantity must be between 0 and 2"})This would fail, it seems the helper wont accept attributes with a dash/hypen. Not a huge problem, i bypassed the helpers and wrote the markup by hand but it feels like im missing a trick somewhere.
So now my inputs are being generated, ive given them the correct attributes and the client side validation is working nicely... except for the select lists which im using the helpers to generate, so these get validated serverside for now.
I dont like the fact my view looks like tag soup (im seriously considering rewriting it using razor) and im left feeling that this could be done better.
I would really like to use the Html Helpers for the rest of the inputs, but im left thinking i may have to roll my own which seems unnecessary since i know they already do what i want them to (when using Data Annotations). If anyone can shed some light on what the "proper" way to do this is (ie add validation to dynamic form inputs) or anything else ive overlooked i'd apreciate it :)
The full view is below:
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<dynamic>" %> <script src="<%: Url.Content("~/Scripts/jquery.validate.min.js") %>" type="text/javascript"></script> <script src="<%: Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js") %>" type="text/javascript"></script> <script> jQuery(document).ready(function ($) { $("[type='date']").datepicker(); }); </script> <% using (Ajax.BeginForm("DynamicForm", "TreeAdmin", new AjaxOptions { UpdateTargetId = "TreeAdminResult" })) { %> <div> <fieldset> <legend>Additional Information</legend> <%: Html.ValidationSummary(true, "Please correct the errors and try again.") %> <% foreach (Hinet.Data.Models.TemplateField field in ViewBag.Fields) { var previousFormValue = string.Empty; if(ViewBag.FormCollection != null) { previousFormValue = ViewBag.FormCollection[field.field_name]; } %> <div class="editor-label"> <%: Html.Label(field.field_name, field.field_name) %> </div> <div class="editor-field"> <% switch (field.DataType) { case Hinet.Data.Models.TemplateField.FieldType.String: %> <input id="<%:field.field_name%>" name="<%:field.field_name%>" type="text" value="<%:previousFormValue%>" <%if(field.IsRequired){ %> data-val-required="" <% } %> data-val="true" data-val-length-max="45" data-val-length="The field must be less than 45 characters"/> <% break; case Hinet.Data.Models.TemplateField.FieldType.Decimal: %> <input id="<%:field.field_name%>" name="<%:field.field_name%>" type="number" value="<%:previousFormValue%>" <%if(field.IsRequired){ %> data-val-required="" <% } %> data-val="true" data-val-number="The field must be a number" data-val-range="Must be less than 1Trilion"/> <% if(field.IsCurrency) { %> <%: Html.DropDownList(field.field_name+"_Currency",(SelectList)ViewBag.Currencies,"Select...")%> <% } break; case Hinet.Data.Models.TemplateField.FieldType.Integer: if (field.IsCurrencyType) { %> <%: Html.DropDownList(field.field_name, (SelectList)ViewBag.Currencies, "Select...")%> <% } else { %> <input id="<%:field.field_name%>" name="<%:field.field_name%>" type="number" value="<%:previousFormValue%>" <% if (field.IsRequired) { %> data-val-required="" <% } %> data-val="true" data-val-number="The field must be a number" data-val-range="Must be less than 1Billion" /> <% } break; case Hinet.Data.Models.TemplateField.FieldType.DateTime: %> <input id="<%:field.field_name%>" name="<%:field.field_name%>" type="date" value="<%:previousFormValue%>" <%if(field.IsRequired){ %> data-val-required="" <% } %> data-val="true" data-val-date="The field must be a date" /> <% break; }%> <% =Html.ValidationMessage(field.field_name)%> </div> <% } %> <p> <input type="submit" value="Submit" /> </p> <%: Html.Hidden("treeId", (int)ViewBag.TreeId) %> <%: Html.Hidden("entityId", (int)ViewBag.EntityId) %> </fieldset> </div> <% } %>francesco ab...
All-Star
20912 Points
3279 Posts
Re: MVC3 data driven form - Unobtrusive Validation without Data Annotations
Jul 24, 2011 08:30 AM|LINK
The simplest way is to define a "BrokerAttribute" that you apply to the value property of your list of name/values. The BrokerAttribute has just one parameter, the name of another field that you put in the items of your list. This filed contains the Type, and parameters of THE ACTUAL validation attribute to apply.
In order to implemet the Broker attribute, for server validation you access the filed passed in the attribute create an instance of the validation attribute to apply, via reflection and call its isValid method.
However you can't simply store the field containing the validation rules on the client say into an hidden filed otherwise a malicious user might bypass server validation by modifying this field. Thus you have to store it either in the session or in tempdata, or ...into an encrypted and signed hidden field...so you are
For the client validation of your Broker attribute you must implement a BrokerAdapter that ineritts from DataAnnotationsModelValidator<BrokerAttribute>
Here you have to ofverrode the methoid:
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
that is the one responsible fro the production of the client code. In turn this attribute need just to call the GetClientValidationRules() of the adapter of the validation attribute to apply. In order to have this adapter from the type fo the attribute the piece of code taken form the sources of Mvc3 show the association for the standard attributes:
internal static Dictionary<Type, DataAnnotationsModelValidationFactory> AttributeFactories = new Dictionary<Type, DataAnnotationsModelValidationFactory>() {{
typeof(RangeAttribute),
(metadata, context, attribute) => new RangeAttributeAdapter(metadata, context, (RangeAttribute)attribute)
},
{
typeof(RegularExpressionAttribute),
(metadata, context, attribute) => new RegularExpressionAttributeAdapter(metadata, context, (RegularExpressionAttribute)attribute)
},
{
typeof(RequiredAttribute),
(metadata, context, attribute) => new RequiredAttributeAdapter(metadata, context, (RequiredAttribute)attribute)
},
{
typeof(StringLengthAttribute),
(metadata, context, attribute) => new StringLengthAttributeAdapter(metadata, context, (StringLengthAttribute)attribute)
},
}; For your custom attributes...build a table or...similar
Mvc Controls Toolkit | Data Moving Plug-in Videos