Validate all strings - validation

I want to prohibit the use of the | character for all strings submitted to my website but I don't want to have to apply a validator attribute to every string property because its unmanageable.
I could validate all strings in a model binder (I'm currently using one to trim all strings) but I don't think that would integrate with the standard validation framework. i.e. generating client side validation.
Any ideas how to do this?

I didn't get this working on the client however I can trap all dodgy strings in a custom model binder and make the form invalid with the following...
public class CustomStringModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueResult == null || string.IsNullOrEmpty(valueResult.AttemptedValue))
{
return null;
}
if (valueResult.AttemptedValue.Contains("|"))
{
bindingContext.ModelState.AddModelError(bindingContext.ModelName, "The | character is prohibited.");
}
return valueResult.AttemptedValue.Trim();
}
}

Related

ASP.NET MVC3 ValueProvider drops string input to a double property

I'm attempting to validate the input of a text box which corresponds to a property of type double in my model. If the user inputs "foo" I want to know about it so I can display an error. However, the ValueProvider is dropping the value silently (no errors are added to the ModelState).
In a normal submission, I fill in "2" for the text box corresponding to myDouble and submit the form.
Inspecting controllerContext.HttpContext.Request.Form shows that myDouble=2, among other correct inputs. bindingContext.ValueProvider.GetValue("myDouble") == 2, as expected. The bindingContext.ModelState.Count == 6 and bindingContext.ModelState["myDouble"].Errors.Count == 0. Everything is good and the model binds as expected.
Then I fill in "foo" for the text box corresponding to myDouble and submitted the form.
Inspecting controllerContext.HttpContext.Request.Form shows that myDouble=foo, which is what I expected. However, bindingContext.ValueProvider.GetValue("myDouble") == null and bindingContext.ModelState.Count == 5 (The exact number isn't important, but it's one less than before). Looking at the ValueProvider, is as if myDouble was never submitted and the model binding occurs as if it wasn't. This makes it difficult to differentiate between a bad input and no input.
Is this the expected behavior of ValueProvider? Is there a way to get ValueProvider to report when conversion fails without implementing a custom ValueProvider? Thanks!
Part of the problem here is that your model has a type of double.
The problem is that double cannot be null, and as such will default to a value of 0, thus on submit.. if the ValueProvider returns null, the value of the field will still be 0 and validation will pass.
You should make the double nullable, by using double? and then add a Required attribute to the property. If the type is not required, then you can add a regular expression validator.
You can implement custom model binding logic using by implementing IModelBinder. This will put the data validation logic at the model binding level - thus being usable for any type of ValueProvider. In your situation, the model binder would determine that when myDouble = "foo" is not a double and add an exception to the ModelState errors showing the invalid value.
public class CustomDoubleBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
if (bindingContext == null)
{
throw new ArgumentNullException("bindingContext");
}
decimal tempDouble = 0m;
if (bindingContext.ValueProvider.GetValue(bindingContext.ModelName) != null)
{
if (double.TryParse(bindingContext.ValueProvider.GetValue(bindingContext.ModelName).AttemptedValue, out tempDecimal))
{
bindingContext.ModelState[bindingContext.ModelName].Errors.Add("Error parsing double value: " + bindingContext.ValueProvider.GetValue(bindingContext.ModelName).AttemptedValue);
}
}
return tempDouble;
}
}
Having created this custom model binder, you will then need to register it in the Global.asax:
protected void Application_Start()
{
ModelBinders.Binders[typeof(double)] = new CustomDoubleBinder();
}

Dynamic Form Building and Passing Query Parameters

I am working on a form that is generated dynamically based on some meta-data tables in my database. I create input tags with names like setting_1, setting_53, setting_22 where the number is the primary key of the meta-data. Since the content is dynamic, I am using FormCollection as the sole parameter on POST requests.
Question 1: Is there a FormCollection-like class for GET requests? I want direct access to the query parameters.
Question 2: If I need to pass these query parameters around, is there an easy/safe way to build my URLs?
One of my big concerns is that some of the settings are populated via OAuth, so the user will be redirected to an external page. I will have to pass the query string as "state" which I will need to recover once the user returns. I will need to use this state to pick up where the user left off in the form entry process. All the more reason why I need a very fool-proof mechanism for passing query parameters around.
Has anyone dealt with dynamic pages like these? Are there good patterns and practices for passing these pages around?
Well, you can certainly look at Request.QueryString inside of a controller action.
But if it were me doing it, I'd write a custom model binder instead.
Here's a sample model binder. I haven't tested this!
public class MyModelBinder: DefaultModelBinder
{
private static void BindSettingProperty(
ControllerContext controllerContext,
ModelBindingContext bindingContext,
PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.PropertyType != typeof(IDictionary<string, string>))
{
throw new InvalidOperationException("This binder is for setting dictionaries only.");
}
var originalValue = propertyDescriptor.GetValue(bindingContext.Model) as IDictionary<string, string>;
var value = originalValue ?? new Dictionary<string, string>();
var settingKeys = controllerContext.HttpContext.Request.QueryString.AllKeys.Where(k => k.StartsWith("setting_", StringComparison.OrdinalIgnoreCase));
foreach (var settingKey in settingKeys)
{
var key = settingKey.Substring(8);
value.Add(key, bindingContext.ValueProvider.GetValue(settingKey).AttemptedValue);
}
if (value.Any() && (originalValue == null))
{
propertyDescriptor.SetValue(bindingContext.Model, value);
}
}
protected override void BindProperty(
ControllerContext controllerContext,
ModelBindingContext bindingContext,
PropertyDescriptor propertyDescriptor)
{
if (propertyDescriptor.Name.StartsWith("setting_", StringComparison.OrdinalIgnoreCase)
{
BindSettingProperty(controllerContext, bindingContext, propertyDescriptor);
}
else
{
base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
}
}
}

Using abstract view model in MVC 3

I have an input form containing several input fields. Each input field has a ElementModel which has properties basically for the label and the value. The input fields to display are specified in a XML document, so I have kind of a dynamic view with only a list of elements.
The problem is, that each element should be either displayed as a decimal or as percentage value. And of course, if it's a percentage value, the user shoud be able to input something like "45%" and the value in the model should then be 0.45.
My first thought when I found this article was to use an abstract view model class with an abstract property for the value and to define a PercentageElementModel deriving from my base ElementModelclass that makes use of a custom model binder. Unfortunately, if I use that abstract base class in my view, the data annotations made in the PercentageElementModelare ignored.
Do you have any idea of how I can solve this? I don't want to use strings in my view model and do the parsing by myself as this will break the MVC pattern. Are there some other ways to achieve my goal?
Here are some code snippets:
public abstract class ElementModel
{
public string ElementName { get; set; }
public ElementType ElementType { get; set; }
public abstract double? ElementValue { get; set; }
}
public class PercentageElementModel : ElementModel
{
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:P2}")]
public override double? ElementValue { get; set; }
}
I came up with another solution as my problem is more a matter of display formatting than of validation: I wrote a custom ModelBinder which checks the input string of the text box. If it ends with a trailing '%' sign, I will devide the value by 100. Here's the code.
public class DoubleModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
var modelState = new ModelState { Value = valueResult };
double? actualValue = null;
try
{
// cut trailing '%'
if (valueResult.AttemptedValue.EndsWith("%"))
{
var strValue = valueResult.AttemptedValue.Substring(0, valueResult.AttemptedValue.Length - 1);
actualValue = double.Parse(strValue, valueResult.Culture) / 100;
}
else
{
actualValue = Convert.ToDouble(valueResult.AttemptedValue, CultureInfo.CurrentCulture);
}
}
catch (FormatException e)
{
modelState.Errors.Add(e);
}
bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
return actualValue;
}
}
The rest happens in the view itself. To display the correct format, I use a simple ToString() method with a percentage format string assigned if needed. And if the user enters a numeric value without '%', a jQuery's blur() event will append the '%' sign to the user's input. Perfectly works for me although this is not the best answer for my own question.

Default ASP.NET MVC 3 model binder doesn't bind decimal properties

For some reason, when I send this JSON to an action:
{"BaseLoanAmount": 5000}
which is supposed to be bound to a model with a decimal property named "BaseLoanAmount", it doesn't bind, it just stays 0. But if I send:
{"BaseLoanAmount": 5000.00}
it does bind the property, but why? Can't 5000 be converted to a decimal event if it doesn't have decimal numbers?
After stepping into asp.net mvc's source code, it seemsd the problem is that for the conversion asp.net mvc uses the framework's type converter, which for some reason returns false for an int to decimal conversion, I ended up using a custom model binder provider and model binder for decimals, you can see it here:
public class DecimalModelBinder : DefaultModelBinder
{
#region Implementation of IModelBinder
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult.AttemptedValue.Equals("N.aN") ||
valueProviderResult.AttemptedValue.Equals("NaN") ||
valueProviderResult.AttemptedValue.Equals("Infini.ty") ||
valueProviderResult.AttemptedValue.Equals("Infinity") ||
string.IsNullOrEmpty(valueProviderResult.AttemptedValue))
return 0m;
return Convert.ToDecimal(valueProviderResult.AttemptedValue);
}
#endregion
}
To register this ModelBinder, just put the following line inside Application_Start():
ModelBinders.Binders.Add(typeof(decimal), new DecimalModelBinder());
ModelBinders.Binders.Add(typeof(decimal?), new DecimalModelBinder());
Try sending it like this:
{ "BaseLoanAmount": "5000" }

MVC Model Binding a Complex Type to a Simple Type and Vice Versa

Here's a scenario:
I have an autocomplete plugin (custom) that keeps a hidden field of JSON objects (using a specific struct).
I've created an Html helper that helps me easily bind to a specific custom model (basically, it has a JSON property that is for two-way binding and a property that lets me deserialize the JSON into the appropriate struct):
public class AutoCompleteModel {
public string JSON { get; set; }
public IEnumerable<Person> People {
get {
return new JavaScriptSerializer().Deserialize<Person>(this.JSON);
}
set {
this.JSON = new JavaScriptSerializer().Serialize(value);
}
}
}
This works great and I can model bind using the default binder #Html.Autocomplete(viewModel => viewModel.AutoCompleteModelTest). The HTML helper generates HTML like:
<input type="text" id="AutoCompleteModelTest_ac" name="AutoCompleteModelTest_ac" value="" />
<input type="hidden" id="AutoCompleteModelTest_JSON" name="AutoCompleteModelTest.JSON" value="{JSON}" />
The problem is this is not the best way for consumers. They have to manually set the People property to an array of Person structs. In my data layer, my domain objects probably will not be storing the full struct, only the person's ID (a corporate ID). The autocomplete will take care of looking up the person itself if only given an ID.
The best scenario will be to call it like this:
#Html.Autocomplete(domainObject => domainObject.PersonID) or
#Html.Autocomplete(domainObject => domainObject.ListOfPersonIDs
I would like it to work against the string property AND against the custom AutoCompleteModel. The autocompleter only updates a single hidden field, and that field name is passed back on postback (the value looks like: [{ "Id":"12345", "FullName":"A Name"},{ "Id":"12347", "FullName":"Another Name" }]).
The problem is, of course, that those domain object properties only have an ID or array of IDs, not a full Person struct (so cannot be directly serialized into JSON). In the HTML helper, I can transform those property values into a struct, but I don't know how to transform it back into a simple type on POST. The solution I need would transform an ID into a new Person struct on page load, serializing it into the hidden field. On POST, it would deserialize the generated JSON back into a simple array of IDs.
Is a custom model binder the solution I need? How can I tell it to work both with a custom model AND simple types (because I don't want it applied to EVERY string property, just need it to deal with the values given by the HTML helper).
I figured it out, it's possible!
To clarify, I needed to: transform a string or string array (of IDs) into a JSON structure for my hidden field value, then on post back, deserialize the JSON in the hidden field and transform the struct back into a simple string or string array (of IDs) for my domain object's property.
Step 1: Create a HTML helper
I had done this already, but only for accepting my custom AutoCompleteModel type. I needed one for a string and an Enumerable of string type.
All I did was generate my Person struct(s) from the value of the property and serialize them into JSON for the hidden field the Autocompleter uses (this is an example of the string helper, I also have a nearly identical one for IEnumerable<string>):
public static MvcHtmlString AutoComplete<TModel>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, string>> idProp)
where TModel : class
{
TModel model = htmlHelper.ViewData.Model;
string id = idProp.Compile().Invoke(model);
string propertyName = idProp.GetPropertyName();
Person[] people = new Person[] {
new Person() { ID = id }
};
// Don't name the textbox the same name as the property,
// otherwise the value will be whatever the textbox is,
// if you care.
MvcHtmlString textBox = htmlHelper.TextBox(propertyName + "_ac", string.Empty);
// For me, the JSON is the value I want to postback
MvcHtmlString hidden = htmlHelper.Hidden(propertyName, new JavaScriptSerializer().Serialize(people));
return MvcHtmlString.Create(
"<span class=\"AutoComplete\">" +
textBox.ToHtmlString() +
hidden.ToHtmlString() +
"</span>");
}
Usage: #Html.AutoComplete(model => model.ID)
Step 2: Create a custom model binder
The crux of my issue was that I needed this binder to only apply to certain properties, and they were strings or string arrays.
I was inspired by this article because it used Generics. I decided, hey, we can just ask people what property they want to apply the binder for.
public class AutoCompleteBinder<T> : DefaultModelBinder
where T : class
{
private IEnumerable<string> PropertyNames { get; set; }
public AutoCompleteBinder(params Expression<Func<T, object>>[] idProperties)
{
this.PropertyNames = idProperties.Select(x => x.GetPropertyName());
}
protected override object GetPropertyValue(
ControllerContext controllerContext,
ModelBindingContext bindingContext,
PropertyDescriptor propertyDescriptor,
IModelBinder propertyBinder)
{
var submittedValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (submittedValue != null && this.PropertyNames.Contains(propertyDescriptor.Name))
{
string json = submittedValue.AttemptedValue;
Person[] people = new JavaScriptSerializer().Deserialize<Person[]>(json);
if (people != null && people.Any())
{
string[] IDs = people.Where(x => !string.IsNullOrEmpty(x.ID)).Select(x => x.ID).ToArray();
bool isArray = bindingContext.ModelType != typeof(string) &&
(bindingContext.ModelType == typeof(string[]) ||
bindingContext.ModelType.HasInterface<IEnumerable>());
if (IDs.Count() == 1 && !isArray)
return IDs.First(); // return string
else if (IDs.Count() > 0 && isArray)
return IDs.ToArray(); // return string[]
else
return null;
}
else
{
return null;
}
}
return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
}
}
GetPropertyName() (translate LINQ expression into a string, i.e. m => m.ID = ID) and HasInterface() are just two utility methods I have.
Step 3: Register
Register the binder on your domain objects and their properties in Application_Start:
ModelBinders.Binders.Add(typeof(Employee), new AutoCompleteBinder<Employee>(e => e.ID, e => e.TeamIDs));
It's only a little bit annoying to have to register the binder for specific properties, but it's not the end of the world and provides a nice, smooth experience working with my autocompleter.
Any comments are welcome.

Resources