ASP.NET MVC UpdateModel() parameter conversion woes - c#

A forewarning: I've relatively new to MVC and its paradigm and to some of its inner workings, but I'm pretty comfortable with it. This is my second ground-up MVC application and I'm a little stumped with how to solve a "problem" one of our testers found.
What the users get is an edit screen given an effective date for daily LIBOR rates that come from Treasury (percentages). The rates are always between 0 and 100 and consequently I've tried to constrain that range using a RangeAttribute in the metadata for one of my domain objects. I've specified the range like so:
[Required, DisplayName("Published Rate"), Range(typeof(decimal), "0", "100")]
public object PublishedRate { get; set; }
Notice that I'm passing in string values as the RangeAttribute does not have an overloaded constructor that takes decimals. This seem to work great until a user goes and enters something out of the ordinary, like:
"0.000000000000000000000000000000001"
This causes UpdateModel() to fail; the ModelState shows this error (three times for the same ModelState value, curiously):
The parameter conversion from type 'System.String' to type 'System.Decimal' failed.
Digging into the errors reveals the cause. The first line below is what's reported by the validation for the field. I found it curious that this did not bubble up to the model validation errors (i.e. did not show up in the summary validation list for the model):
"0.000000000000000000000000000000001 is not a valid value for Decimal."
"Value was either too large or too small for a Decimal."
System.Web.Mvc.ValueProviderResult.ConvertSimpleType() and System.ComponentModel.BaseNumberConverter.ConvertFrom() are throwing the exceptions.
A user is never going to enter a value such as this, but I wouldn't mind knowing if there are any mechanisms built in to MVC that could or will prevent this (server-side, that is). There doesn't seem to be an issue with numbers like the following, it only seems to break with ones that are very small.
"0.555555555555555555555555555555555"
At the end of the day I really only need 9 digits of precision. The database table column backing these values is a decimal(9,6). I know I could implement a custom model binder for my model and manually collect the values from the Request, but there's got to be something a little easier, such as a custom FilterAttribute or something that can correct the value before its attempted to be bound to the model, I'm just not sure what, and am looking for suggestions.
I seem to recall reading about some issues with trying to constrain decimal values using a RangeAttribute but I can't recall the issue. Perhaps you MVC gurus out there can shed some light on the situation.

You could use a Regex attribute to contain the decimal to a precision of 9. This would also allow you to add a custom message when the Regex fails, such as "Your value may have a maximum of 9 places after the decimal." or something similar. Also if you have client side validation enabled, the Regex will work in both client and server side validation.

So after a couple of hours of head banging I settled on the following custom model binder for decimals. It makes sure that all decimal values are parseable before binding them.
public class TreasuryIndexRateDecimalBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var providerResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (providerResult != null)
{
decimal value;
if (!decimal.TryParse(providerResult.AttemptedValue, NumberStyles.Float, CultureInfo.CurrentCulture, out value))
{
// TODO: Decide whether to show an error
// bindingContext.ModelState.AddModelError(bindingContext.ModelName, "error message");
return 0m;
}
return Math.Round(value, 6);
}
return base.BindModel(controllerContext, bindingContext);
}
}
The binding is set up in Application_Start() to register it for all decimal values.
protected void Application_Start()
{
ModelBinders.Binders.Add(typeof(decimal), new TreasuryIndexRateDecimalBinder());
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
}
Unless somebody comes along with a more interesting approach I think I'll stick with this.

Related

Mapping Enum to string column with custom SqlMapper.ITypeHandler - Dapper

I have a large number of PL/SQL stored procs that return columns with single character strings representing some kind of status value from a fixed range. In the project I'm working on, these columns have been mapped by Dapper to string properties on the domain objects, which are awkward and unreliable to manage, so I'd like to switch to enums.
If I used enums with single character names like enum Foo {A, P} I'm pretty sure Dapper would map them correctly but I don't want that, I want enums with descriptive labels like so:
enum Foo {
[StringValue("A")]
Active,
[StringValue("P")]
Proposed
}
In the above example, StringValueAttribute is a custom attribute and I can use reflection to convert the "A" to Foo.Active, which works fine - except I need Dapper to perform that conversion logic for me. I wrote a custom type handler to do this:
public class EnumTypeHandler<T> : SqlMapper.TypeHandler<T>
{
public override T Parse(object value)
{
if (value == null || value is DBNull) { return default(T); }
return EnumHelper.FromStringValue<T>(value.ToString());
}
public override void SetValue(IDbDataParameter parameter, T value)
{
parameter.DbType = DbType.String;
parameter.Value = EnumHelper.GetStringValue(value as Enum);
}
}
//Usage:
SqlMapper.AddTypeHandler(typeof(Foo),
(SqlMapper.ITypeHandler)Activator.CreateInstance(typeof(EnumTypeHandler<>).MakeGenericType(typeof(Foo)));
The registration with SqlMapper.AddTypeHandler() seems to work fine, but when my DbConnection.Query() code runs, I get an error saying that the value 'A' could not be converted - the error is thrown from Enum.Parse, suggesting that Dapper isn't actually calling my type handler at all despite it being registered. Does anyone know a way around this?
Another user has reported this as an issue on Dapper's github site. Seems like it's a deliberate optimisation specifically around enums in Dapper, so I've changed my database model rather than trying to change the mapping code. I looked at trying to modify Dapper itself, but the source code of Dapper is optimised like nothing I've ever seen, emitting opcodes to perform conversions in the most performant way possible - no way I want to start trying to work out how to make changes there.

ASP.NET MVC - Default Range validation error message not being overridden

Say I have a model property like this:
[Range(1, 31, ErrorMessage = "O dia de fechamento deve possuir valores entre 1 e 31")]
public int DataInicial { get; set; }
Even with a custom error message set on the annotation, I'm still getting the default error message for the Range annotation "Please enter a value less than or equal to 31.", when I type something like "32" or more at the #Html.TextBoxFor(model => model.DataInicial) field.
I'm aware of this post, but I think if you can set custom messages at annotation level, It should work without setting an App_GlobalResources and a .resx file, setting third-party libraries or whatever... I know that adding a .resx file and put all those validation strings there, is a "best-practice", but...
So, where I could be wrong, since the messages are not showing correctly?
Thank you in advance.
I was running into the same issue and experimented a little. Just learning MVC myself and it wasn't too obvious, but the solution seems nice and makes sense. The issue just comes down to which validation attribute gets triggered.
I found ErrorMessage is specific to the validation it is associated with.
A simple example should make it real clear...
[Required(ErrorMessage="xxx may not be blank.")]
[Range(0.0,1000.0,ErrorMessage="The value entered must be 0 to 1000.00")]
public virtual double xxx () // snip...
If empty, upon validation you will get "xxx may not be blank". While a value has been entered but not valid you should see the other error "The value entered...".

Asp.net-MVC Custom Validation - NULL vs Empty

For security and consistency I would like to test on post-back if a field is missing? In Java (servlets in particular) when we perform a request.getParameter(key) the result is either a String value or otherwise NULL if the field is missing. In MVC I've created a custom validation that I call "ThrowOnNull". The behavior I'm trying to capture is: If an intended field is missing (null) I want to throw, otherwise return success.
Consider this Custom Validator (that doesn't work):
public class ThrowOnNull: ValidationAttribute
{
public ThrowOnNull() { }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null)
throw new Exception(validationContext.MemberName + " field expected, but missing."));
return ValidationResult.Success;
}
}
Is it possible to do what I want here? (this validator doesn't work as expected and it's because the framework is assigning NULL to an empty value [oh dear].)
UPDATE: Per #emodendroket, the following code example now works as expected:
public class ThrowOnMissing: ValidationAttribute
{
public ThrowOnMissing() { }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (!HttpContext.Current.Request.Form.AllKeys.Contains(validationContext.MemberName))
throw new Exception(validationContext.MemberName + " field expected, but missing.");
return ValidationResult.Success;
}
}
EDIT: I've cleaned up the question and example code significantly to make it all much clearer, hope it helps.
You're missing one important point - when you submit a form, all fields belonging to that form get submitted. If the user doesn't fill them, they're blank, so the validation for null can't really work like this...
You should probably redefine what "missing value" means.
EDIT:
After some discussion it seems you're not really concerned about null values, but about the security (false forms with missing fields and stuff like that). In that case you should simply use the standard - antiforgery token and maybe check the origin of the request. You should be more than fine this way. Checking for missing fields won't help a bit, because attacker can easily send those fields as well.
Over/Under posting is a real concern. Furthermore, if fields aren't submitted (due to a hack or DOS attack of some kind)
Not really.
Overposting is handled by the action method on the controller. It won't accept more parameters than you've specified.
Underposting will be handled pretty much the same way as if you didn't fill the text fields in the form, again a non-issue if you have validated your model correctly.
DDOS attack can't be prevented a believe me, some checking for missing fields won't help a bit if someone has a network powerful enough to cause DDOS. Just look up latest cases on attacks and you'll understand, that if HUGE servers can't withstand that, you certainly can't prevent it like this.
Your data validation shouldn't be too expensive either. It's a web, people don't like to wait too much.
If you want your own validator you can look at the dictionary HttpContext.Current.Request.Form. You can then do what you've proposed with this code:
if (!HttpContext.Current.Request.Form.AllKeys.Contains("prop"))
{
throw new Exception();
}
I think you're being unreasonably paranoid here about things. Note that I said Unreasonably paranoid, since a little paranoia is a good thing.
First, let us analyze the "threats" and determine the risks. You've presented several arguments:
Over-posting
Under-posting
Validation of empty vs null
The first item is not an issue in MVC if you are using a View Model. You simply can't post information that the view isn't expecting (well, you can, but it's ignored). If you're not using a view model (or not using a properly defined one) then you can use binding attributes to prevent posting of items you don't want bound.
The second, under-posting, if it's truly a concern for your model (99.999% of the time simply treating it as required is more than fine, but ok let's take your argument. The issue here is not the validation attribute, it's the model binder. You simply have to register a custom model binder that looks for missing values in your view model and throws if they are null. This is easily accomplished by reflecting over the bound model and comparing it to the posted values, then throw.
The problem with your RequiredThrowIfNull approach is.. what if it's not required and it's under posted? That's still an error according to your argument.
Finally, validation of empty vs null... you talk about expensive validation... I don't know what kind of expensive validation you could be talking about here, but server side there is noting in attributes that could be considered expensive.
The reason your attribute doesn't work is because validation attributes are called within a try/catch block by the framework already, and if it's null it's the very mechanism that treats it like empty (this mechanism also does things like catching parsing errors when a type is incorrect, such as characters in a datetime field.)
.NET is not Java, even though it largely works similarly... trying to re-implement Java patterns in .NET is going to lead to misery on your part because many basic philosophies are just different.
Even if we accept your argument of wanting to catch errors or be notified of hacking attempts, throwing is the wrong thing to do in most cases. Instead, just log the information from the model binder and continue on as normal. Only throw if you absolutely need to abort the request, and most of the time that simply isn't the case, even if you're being hacked... throwing will just cause the attacker to vary their attack until they no longer get the exception.
Frankly, this is an over-engineered, solution looking for a problem and there are many good .net specific ways of dealing with the REAL issues you are trying to solve.
#ignatandrei from ASP.NET answered the question:
by default MVC transforms empty string to null string.
http://forums.asp.net/t/2006146.aspx?Custom+Validation+and+NULL

How to conditionally validate properties using FluentValidation?

I'm trying to implement a rule using FluentValidation where two properties have a dependency on each other. They are Unit and UnitType. Unit is a string, and UnitType is an enum. I want rules where if the Unit is filled out then the UnitType cannot be None (0), and if the UnitType is not None (0) then the Unit must be filled out. Here's the rules I've tried so far to no avail:
this.RuleFor(
p =>
p.Unit).NotEmpty().When(
l =>
(l.UnitType != UnitType.None)).WithMessage("Unit № must be filled out and must be less than 8 characters long when the Unit Type is selected");
this.RuleFor(
p =>
p.UnitType).NotEqual(UnitType.None).When(
l =>
!String.IsNullOrEmpty(l.Unit)).WithMessage("Unit Type must be selected when the Unit № is filled out");
No matter how I tweak the rules, I just keep getting an error that says: 'Unit Type' must not be empty. Since my custom error messages are not showing up, I'm thinking that the rules are being skipped somehow...
I'd appreciate any suggestions on how to get this fixed.
Well, after taking some time away from this and coming back to it, I believe I finally have it resolved. Originally in my post model I had the UnitType property as non-nullable, which is what was triggering the validation even though it's default value was UnitType.None. From what I can tell, MVC, not FluentValidation, sees that it's non-nullable property, ignores the default value and then attempts to bind the property anyway with a null value from the form post. I can kind of understand why it behaves this way, but can't help but think that it shouldn't ignore default values.
So, I changed the property to be nullable and now my two rules work as I expect them to. Still, I'm not overly happy about the way it actually works because I was hoping to use the default auto-generated value further down when mapping with AutoMapper, and now I'll have to make sure to set a default value in the constructor where it's relevant. Realistically a non-issue in the long run, so long as I don't miss setting a default somewhere.

Unobstructive client validation error

I am using asp.net mvc 3 and I keep getting the following error.
Validation type names in unobtrusive
client validation rules must be
unique. The following validation type
was seen more than once: number
I have no clue as I have this
#Html.TextBoxFor(x => x.Mark)
// my viewmodel
[Required(ErrorMessage = "Message")]
[Number(ErrorMessage = "Message")]
public decimal Mark { get; set; }
If I change it from a decimal to string it will not complain. What is going on?
Edit
I think it is because of this the [Number(ErrorMessage = "Message")] annotation. I am using this library Data annotation extensions
It seems not not like that I am using decimals. Anyone know why?
If you are using type decimal, you do not need to use the [Numeric] attribute because MVC already sees you are using a numeric type and injects that in for you (which is causing the error). When you change to a string, the [Numeric] is then needed to tell the validation how you want that string to work.
In the next version of DataAnnotationsExtensions, I'll change the [Numeric] attribute so it won't collide with the MVC version in this case. But for now, removing the [Numeric] attribute will be just fine because [Numeric] on a numeric type is redundant anyway.
You probably have multiple model validators which are adding the same client rule twice, are you using a custom validatiOn provider?
Required will become duplicate since Mark is not nullable. I would change it to be decimal?

Categories

Resources