I am using ASP.NET C# MVC2 and I have the following field within a model with the following data annotation validation attributes:
[DisplayName("My Custom Field")]
[Range(long.MinValue, long.MaxValue, ErrorMessage = "The stated My Custom Field value is invalid!")]
public long? MyCustomField{ get; set; }
In the form this field should allow the user to leave it blank and display a validation message should the user attempt to enter a value that cannot be expressed as a number. From a validation point of view, this is working as intended and displaying the following error messages:
The stated My Custom Field value is invalid!
The field My Custom Field must be a number.
The first validation message is the custom validation message that I wrote and the second validation message is the one the MVC2 automatically generates. I need to get rid of the second one since its redundant. How do I do this? In my view I have the following markup
<% Html.EnableClientValidation(); %>
<% using (Html.BeginForm())
{ %>
<%:Html.ValidationSummary(false)%>
<% Html.ValidateFor(m => m.MyCustomField); %>
This issue you have here is because the property being bound is a numeric, and model binding is automatically handling the fact that the string could not be converted to a number. It's not the RangeAttribute's doing.
You might instead consider having a new property as a string and deriving your own RangeAttribute which works at the string level, parsing the number first.
Then you have your existing property wrap that string:
[DisplayName("My Custom Field")]
[MyCustomRangeAttribute(/* blah */)] //<-- the new range attribute you write
public string MyCustomFieldString
{
get; set;
}
public int? MyCustomField
{
get
{
if(string.IsNullOrWhiteSpace(MyCustomField))
return null;
int result;
if(int.TryParse(MyCustomField, out result))
return result;
return null;
}
set
{
MyCustomFieldString = value != null ? value.Value.ToString() : null;
}
}
Your code can continue to work on the int? property quite happily, but - all model binding is done on the string property.
You will also ideally add [Bind(Exclude"MyCustomField")] to the model type - to ensure that MVC doesn't try and bind the int? field. Or you can just make it internal. If it's in the web project and you only need to reference it in the web project.
You could also consider the really hacky approach - and finding that error in your controller method via ModelState.Errors and removing it before you return your view result...
Related
Imagine, you have this model
public Class SomeModel
{
public string someString1 { get; set; }
public string someString2 { get; set; }
}
Now you want to validate this model. In FluentValidation you write this.RuleFor(m => m.{property} for each property on the model/class and when a validation error occurs, you get the key of the property that failed validation and a message.
Now, what i am wondering about, is how you can set a key for the whole model when it returns an error from validation, which validates two or more properties. (e.g. this.RuleFor(m => m).TestIfPropsAreEqual(); ) What is the correct way to approach this problem? I don't really want to write the method on a property, because that wouldn't be correct, because you are validating the whole model not a specific property.
EDIT: Forgot to mention that when validating the whole model, the returned key is empty.
https://fluentvalidation.net/start#overriding-the-property-name is the answer. I can give the a name to class or new name to a property.
I have a correctly working tag helper for the <select> element which contains the following working code:
TagHelperAttribute forAttribute;
if (!context.AllAttributes.TryGetAttribute("asp-for", out forAttribute))
{
throw new Exception("No asp-for attribute found.");
}
var forInfo = (Microsoft.AspNetCore.Mvc.ViewFeatures.ModelExpression)forAttribute.Value;
I have the same code in a different tag helper, for a novel element I'm calling <date-picker>. In this second tag helper, the cast to ModelExpression fails because forAttribute.Value is, in fact, not a ModelExpression but rather a string (it is the property name, "DueDate", to which I'm trying to bind the tag).
It seems my novel date-picker tag is not aware that the asp-for value should be applied to the Razor page model.
How can I ensure that my date-picker receives a correct ModelExpression on which to base my output?
To ensure your attribute has the proper binding, you need to define a property in your tag helper class, with the HtmlAttributeName attribute. For example:
[HtmlAttributeName("asp-for")]
public ModelExpression For { get; set; }
The reason why the attribute is required is that the HtmlAttributeNameAttribute class does a lot behind the scene to bind the proper value. You can see how the value is bound on github.
Also, this simplifies how you access the value of the attribute, as you don't need to go through the entire list. So instead of this:
TagHelperAttribute forAttribute;
if (!context.AllAttributes.TryGetAttribute("asp-for", out forAttribute))
{
throw new Exception("No asp-for attribute found.");
}
var forInfo = (Microsoft.AspNetCore.Mvc.ViewFeatures.ModelExpression)forAttribute.Value;
You can write :
// you can call the For property
var forInfo = For;
create the attribute TAG and property as shown below:
[HtmlAttributeName("asp-for")]
public ModelExpression AspFor { get; set; }
Access the attribute model value,name etc. as in the following way:
AspFor.Name
AspFor.Model
There are some properties in my view model that are optional when saving, but required when submitting. In a word, we allow partial saving, but the whole form is submitted, we do want to make sure all required fields have values.
The only approaches I can think of at this moment are:
Manipulate the ModelState errors collection.
The view model has all [Required] attributes in place. If the request is partial save, the ModelState.IsValid becomes false when entering the controller action. Then I run through all ModelState (which is an ICollection<KeyValuePair<string, ModelState>>) errors and remove all errors raised by [Required] properties.
But if the request is to submit the whole form, I will not interfere with the ModelState and the [Required] attributes take effect.
Use different view models for partial save and submit
This one is even more ugly. One view model will contain all the [Required] attributes, used by an action method for submitting. But for partial save, I post the form data to a different action which use a same view model without all the [Required] attributes.
Obviously, I would end up with a lot of duplicate code / view models.
The ideal solution
I have been thinking if I can create a custom data annotation attribute [SubmitRequired] for those required properties. And somehow make the validation ignores it when partial saving but not when submitting.
Still couldn't have a clear clue. Anyone can help? Thanks.
This is one approach I use in projects.
Create a ValidationService<T> containing the business logic that will check that your model is in a valid state to be submitted with a IsValidForSubmission method.
Add an IsSubmitting property to the view model which you check before calling the IsValidForSubmission method.
Only use the built in validation attributes for checking for invalid data i.e. field lengths etc.
Create some custom attributes within a different namespace that would validate in certain scenarios i.e. [RequiredIfSubmitting] and then use reflection within your service to iterate over the attributes on each property and call their IsValid method manually (skipping any that are not within your namespace).
This will populate and return a Dictionary<string, string> which can be used to populate ModelState back to the UI:
var validationErrors = _validationService.IsValidForSubmission(model);
if (validationErrors.Count > 0)
{
foreach (var error in validationErrors)
{
ModelState.AddModelError(error.Key, error.Value);
}
}
I think there is more precise solution for your problem. Lets say you're submitting to one method, I mean to say you are calling same method for Partial and Full submit. Then you should do like below:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult YourMethod(ModelName model)
{
if(partialSave) // Check here whether it's a partial or full submit
{
ModelState.Remove("PropertyName");
ModelState.Remove("PropertyName2");
ModelState.Remove("PropertyName3");
}
if (ModelState.IsValid)
{
}
}
This should solve your problem. Let me know if you face any trouble.
Edit:
As #SBirthare commented that its not feasible to add or remove properties when model get updated, I found below solution which should work for [Required] attribute.
ModelState.Where(x => x.Value.Errors.Count > 0).Select(d => d.Key).ToList().ForEach(g => ModelState.Remove(g));
Above code will get all keys which would have error and remove them from model state. You need to place this line inside if condition to make sure it runs in partial form submit. I have also checked that error will come for [Required] attribute only (Somehow model binder giving high priority to this attribute even you place it after/below any other attribute). So you don't need to worry about model updates anymore.
My approach is to add conditional checking annotation attribute, which is learned from foolproof.
Make SaveMode part of the view model.
Mark the properties nullable so that the values of which are optional when SaveMode is not Finalize.
But add a custom annotation attribute [FinalizeRequired]:
[FinalizeRequired]
public int? SomeProperty { get; set; }
[FinalizeRequiredCollection]
public List<Item> Items { get; set; }
Here is the code for the Attribute:
[AttributeUsage(AttributeTargets.Property)]
public abstract class FinalizeValidationAttribute : ValidationAttribute
{
public const string DependentProperty = "SaveMode";
protected abstract bool IsNotNull(object value);
protected static SaveModeEnum GetSaveMode(ValidationContext validationContext)
{
var saveModeProperty = validationContext.ObjectType.GetProperty(DependentProperty);
if (saveModeProperty == null) return SaveModeEnum.Save;
return (SaveModeEnum) saveModeProperty.GetValue(validationContext.ObjectInstance);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var saveMode = GetSaveMode(validationContext);
if (saveMode != SaveModeEnum.SaveFinalize) return ValidationResult.Success;
return (IsNotNull(value))
? ValidationResult.Success
: new ValidationResult(string.Format("{0} is required when finalizing", validationContext.DisplayName));
}
}
For primitive data types, check value!=null:
[AttributeUsage(AttributeTargets.Property)]
public class FinalizeRequiredAttribute : FinalizeValidationAttribute
{
protected override bool IsNotNull(object value)
{
return value != null;
}
}
For IEnumerable collections,
[AttributeUsage(AttributeTargets.Property)]
public class FinalizeRequiredCollectionAttribute : FinalizeValidationAttribute
{
protected override bool IsNotNull(object value)
{
var enumerable = value as IEnumerable;
return (enumerable != null && enumerable.GetEnumerator().MoveNext());
}
}
This approach best achieves the separation of concerns by removing validation logic out of controller. Data Annotation attributes should handle that kind of work, which controller just need a check of !ModelState.IsValid. This is especially useful in my application, because I would not be able to refactor into a base controller if ModelState check is different in each controller.
I'm trying to utilize model state to validate requests to my WebAPI, but I'm having some trouble with how to control the error messages for some properties. For example, given the following model
public class Stuff {
[Range(0, Double.PositiveInfinity)]
public double? SomeProp { get; set; }
}
(and some infrastructure defined at the bottom of the post) a request with a payload such as { someProp: "1.2" } will model bind correctly - and negative or missing values will give errors - but if I enter something that isn't a valid number (e.g. { someProp: "hello" }) the model state has an error such as
Error converting value "hello" to type 'System.Nullable`1[System.Double]'. Path 'someStuff', line 1, position 41.
(I guess the position is in the JSON object, here cut from a larger request, so don't mind the exact numbers...). For another property, which isn't nullable but instead has a [Required] attribute, the error message about a missing required property is included in the model state dict in addition to the above message.
I do understand why "hello" cannot be bound to a double? (or double), but regardless of constraints I set in attributes, it seems that the value has to be model bound before they are even checked, and if model binding fails I both get that not-so-friendly message above, as well as all other errors that apply when the value is unset. The only constraint I've been able to apply before attempting to bind the value is a Regex, but if that fails I get a similarly user-unfriendly message like
The field SomeProp must match the regular expression '<my regex>'.
Is there a way to work around this, so that I can give the user a better error message when entering something non-numeric into a textbox that should specify a numeric value?
Footnotes: my validation infrastructure
[HttpPost]
[Validate]
public IHttpActionResult Validate(Stuff stuff) {
return Ok();
}
where the Validate attribute is defined like this:
public class ValidationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ModelState.IsValid)
{
actionContext.Response = actionContext.Request
.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
}
I'm working on an MVC app, and a bit of model code I've written has unfolded somewhat like this:
public class SomeModel
{
public int? CodeA { get; set; }
public int? CodeB { get; set; }
[RequiredIf("CodeA", 3, ErrorMessage = "(required for [Something]!)")]
[RequiredIf("CodeB", 99, ErrorMessage = "(required for [Other]!)")]
public string Foo { get; set; }
// SNIP: Unimportant details
}
Note: The RequiredIf() implementation I am using is found here.
I have decorated property Foo, which a user has the ability to edit in certain circumstances, with two RequiredIf() attributes. There are two different cases in which it is required to be filled out. In all other circumstances, the front end will parse the user's input and populate it for them 'behind' the scenes.
Question: If only one case (e.g. CodeA = 3, CodeB = 4) is satisified, and the user fails to enter anything thus causing a negative validation, will the model still be marked as Invalid and a ErrorMessage logged for it? Or, since the Code B condition is satisfied, will that override the validation performed if CodeA is in a state that it is required (and not entered)?
Another way of asking: are validations additive, or is there an implicit limit to the results of only one validation at a time?
Validation is negative. For validation to pass, ALL validators must confirm the field is valid. So for your Foo, if the CodeA validator passes and the CodeB validator fails, validation will fail. Modelstate will contain a single error for that field. If both fail, modelstate will contain two errors for that field.