MVC 3 validation: how to override DataAnnotations attribute in specific use cases? - asp.net-mvc-3

In an ASP.NET MVC3 app I have a model that represents a user address with the typical Name, StreetAddress 1 & 2, City, Region, PostalCode and Country properties. The model currently has DataAnnotation attributes that apply to US addresses. I now need to support international addresses that will have different validation and messages depending on the Country value that is included in the model. How do I define and override the existing US DataAnnotation attribute values when the country is something like India or Japan instead of US?
For example the existing PostalCode property is defined as this:
private string _postalCode;
[StringLength(10, ErrorMessage = "Zip Code maximum length 10 characters")]
[Display(Name = "Zip Code")]
[Required(ErrorMessage = "Zip Code is required")]
[RegularExpression(#"^\d{5}(-\d{4})?$", ErrorMessage = "Invalid Zip Code")]
public string PostalCode
{
get { return _postalCode; }
set
{
if (_postalCode != value)
{
_postalCode = value;
}
}
}
I know if I had a specific India address model then the postal code would look something like this:
private string _postalCode;
[StringLength(6, ErrorMessage = "Postal Code maximum length 6 characters")]
[Display(Name = "Postal Code")]
[Required(ErrorMessage = "Postal Code is required")]
[RegularExpression(#"^([0-9]{6})$", ErrorMessage = "Invalid Postal Code")]
public string PostalCode
{
get { return _postalCode; }
set
{
if (_postalCode != value)
{
_postalCode = value;
}
}
}
How can I implement the proper client and server side validations using this model when a user selects a particular country?
I'm expecting to either do an ajax call to retrieve an updated partial view when the country is changed, or send enough data to the client so I can adjust the client side prompts and validation by modifying the appropriate attributes on the input elements and resetting validation, but how can I get the server side model to validate properly when issuing a Model.IsValid() call?

With complex validations, I find it easiest to imeplement IValidatableObject interface
IEnumerable<ValidationResult> Validate(
ValidationContext validationContext
)
Basically something like this
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
MyAddress model = validationContext.ObjectInstance as MyAddress;
if (model.Country == "India")
{
// validate as india
}
}
This seemlessly integrates with default validation system, so you won't need any additional configurations. But to note, this is only the server side validation.

Related

.NET Core 2.2 Web API - Field Validation Decorators not firing on manual assignment

I have a model class (simplified below):
public class Person
{
[JsonRequired]
[RegularExpression(#"^[ -'A-Za-z]{2,30}$", ErrorMessage ="The field firstName must be between 2 and 30 characters and contain alpha characters only.")]
[JsonProperty(PropertyName = "firstname")]
public String Firstname { get; set; }
[RegularExpression(#"^[ -'A-Za-z]{2,26}$", ErrorMessage = "The field middleName must be between 2 and 26 characters and contain alpha characters only.")]
[JsonProperty(PropertyName = "middlename")]
public string Middlename { get; set; }
}
The decorators work when I fire the API from Postman - and the error is returned.
I am currently creating UNIT Tests and want to check the validation but manual assignment through code allows it through e.g.
Person testPerson = new Person();
testPerson.middlename = "Bob123";
and therefore the Unit Test passes (or fails depending on your point of view!)
Is there a simple way through Unit Tests to check for this type of validation?
OK. So the below does what I need:
Person testPerson = new Person();
testPerson.middlename = "Bob123";
var validationResults = new List<ValidationResult>();
var context = new ValidationContext(testPerson);
Validator.TryValidateObject(testPerson, context, validationResults, true);
And looking at the "validationResults" reveals the particular error message of the field that fails - e.g. middlename.

ASP.NET Core 2.2 Razor Pages - User Input Validation for IP Address

I am struggling to find an example or solution to validate user input on a Razor Page form control for an IP Address.
The IP Address entered could be any value but I just want to check/verify that the format entered is correct i.e. usual checks against too many digits, incorrect range for an octet beyond .254 etc.
I assumed there would be a built in validation attribute that I could add to the Model Class but unsure whether this would require a NuGet add on.
Correct me if I'm wrong but would assume validating this server side may be the better solution here and reduce code in the long run. But given this is more just for ensuring correct user input rather than being a security feature then am happy to explore all avenues, thanks in advance...
Model Class:
[Required]
[Display(Name = "IP Address")]
public string IpAddress { get; set; }
Razor Page:
<div class="form-group">
<label asp-for="ConnectorModel.IpAddress" class="control-label"></label>
<input asp-for="ConnectorModel.IpAddress" class="form-control" />
<span asp-validation-for="ConnectorModel.IpAddress" class="text-danger"></span>
</div>
After further testing I found I was only able to use the above solution for just a single view model instance, however my app requires the user input validation across multiple pages. Through trial and error I found changing the code as per below allowed me to use the same validation attribute class across multiple razor pages. Credit to Nan in helping me reach the final solution.
View Model:
[Required]
[IPAddressAttribute] // This calls the custom validation attribute class
[StringLength(15)] // No need for a message, custom attribute handles this.
[Display(Name = "IP Address")]
public string IpAddress { get; set; }
ValidationAttribute Class:
public class IPAddressAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
string IpAddress = (string)value;
const string regexPattern = #"^([\d]{1,3}\.){3}[\d]{1,3}$";
var regex = new Regex(regexPattern);
if (string.IsNullOrEmpty(IpAddress))
{
return new ValidationResult("IP address is null");
}
if (!regex.IsMatch(IpAddress) || IpAddress.Split('.').SingleOrDefault(s => int.Parse(s) > 255) != null)
return new ValidationResult("Invalid IP Address");
return ValidationResult.Success;
}
}
In .NET Core, you can simply create a class that inherits from ValidationAttribute. You can see the full details in this doc .
Based on your requirement ,you can create the attribute like :
public class IPAddressAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
YourViewModel yourviewmodel = (YourViewModel)validationContext.ObjectInstance;
const string regexPattern = #"^([\d]{1,3}\.){3}[\d]{1,3}$";
var regex = new Regex(regexPattern);
if (string.IsNullOrEmpty(yourviewmodel.IpAddress))
{
return new ValidationResult("IP address is null");
}
if (!regex.IsMatch(yourviewmodel.IpAddress )|| yourviewmodel.IpAddress.Split('.').SingleOrDefault(s => int.Parse(s) > 255)!=null)
return new ValidationResult("Invalid IP Address");
return ValidationResult.Success;
}
}
And in your view model used like :
[IPAddressAttribute]
[Display(Name = "IP Address")]
public string IpAddress { get; set; }

MVC Model doesn't catch all model errors

This is part of a an MVC model class:
[Required]
[StringLength(2, ErrorMessage = "Two characters.")]
public string StateProvince { get; set; }
If I submit my form with a blank StateProvince, I will get an error message. If I submit it with one character in StateProvince, no problem. Here's how I get back error messages:
if (!ModelState.IsValid)
{
MyMessage ErrMsg = new MyMessage();
ErrMsg.StatusCode = 101;
ErrMsg.StatusMsg = string.Join("; ", ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage));
return "[" + JsonConvert.SerializeObject(ErrMsg) + "]";
}
MyMessage is structured like so:
public class MyMessage
{
public int StatusCode { get; set; }
public string StatusMsg { get; set; }
}
Am I right to conclude that the StringLength decoration only serves to create client-side validation in the html input field?
Since I'm coding the HTML myself and not using Asp.Net to generate it, then should I just omit the StringLength decoration on the model field and instead write server-side validation code for that in the controller? (And that violates MVC, right?)
Oops! Sorry! StringLength was working. It only prevents excess characters, not minimum number of characters. I know there's code for enforcing an exact length; I've seen it before and will look it up.

Model Validation / ASP.NET MVC 3 - Conditional Required Attribute

I'm having trouble with my ASP.NET MVC 3 application. I have 2 propertiesin my model whereby I only want 1 of them required in my view based on whichever one is empty. So for example, if I enter a phone number then email is no longer required and vice versa, but if I leave both empty, then either 1 should be required, below is my model:
[Display(Name = "Contact Phone Number:")]
[MaxLength(150)]
public string ContactPhoneNumber { get; set; }
[Display(Name = "Contact Email Address:")]
[MaxLength(100)]
public string ContactEmailAddress { get; set; }
Would I need to create a custom attribute to validate my model and if so, how would I achieve this?
You can implement IValidatableObject on your class and provide a Validate() method that implements your custom logic. Combine this with custom validation logic on the client if you prefer to ensure that one is supplied. I find this easier than implementing an attribute.
public class ContactModel : IValidatableObject
{
...
public IEnumerable<ValidationResult> Validate( ValidationContext context )
{
if (string.IsNullOrWhitespace( ContactPhoneNumber )
&& string.IsNullOrWhitespace( ContactEmailAddress ))
{
yield return new ValidationResult( "Contact Phone Number or Email Address must be supplied.", new [] { "ContactPhoneNumber", "ContactEmailAddress" } );
}
}
}
To get everything working at client side you'll need to add the following script to your view:
<script type="text/javascript">
$(function() {
$('form').validate();
$('form').rules('add', {
"ContactPhoneNumber": {
depends: function(el) { return !$('#ContactEmailAddress').val(); }
}
});
});
</script>
Annotation-based conditional validation can be defined using ExpressiveAnnotations:
[RequiredIf("ContactPhoneNumber == null",
ErrorMessage = "At least email or phone should be provided.")]
public string ContactEmailAddress { get; set; }
[RequiredIf("ContactEmailAddress == null",
ErrorMessage = "At least email or phone should be provided.")]
public string ContactPhoneNumber { get; set; }
Here is a MSDN blog entry about conditional validations: http://blogs.msdn.com/b/simonince/archive/2011/02/04/conditional-validation-in-asp-net-mvc-3.aspx
I know you already have a solution, but I had a similar situation, so maybe my solution will prove helpful to someone else. I implemented a custom attribute with client-side validation. Here is my blog post: http://hobbscene.com/2011/10/22/conditional-validation/

one property from my ViewModel will not populate ModelMetadata

I'm experiencing very odd behavior in the way an ASP.NET MVC3 view model is emitted -- for one field, ModelMetadata is not propagated. I'm using the templated helpers after Brad Wilson, though updated for Razor. Here's my view model:
public class FamilyBaseViewModel : ViewModelBase
{
[Display(Order = 10)]
public string FamilyName { get; set; }
[Display(Order = 30)]
[StringLength(50, ErrorMessage = "Street name can only be 50 characters long.")]
public string Street { get; set; }
}
public class FamilyPrivateViewModel : FamilyBaseViewModel
{
[Display(Name = "Date Started", Order = 20)]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:d}")]
[DataType(DataType.Date)]
public DateTime? DateStarted { get; set; }
}
The object.cshtml template runs through the properties and uses Html.Display to show them:
// object.cshtml
<ol>
#foreach (var prop in
ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForDisplay
&& !ViewData.TemplateInfo.Visited(pm)
&& pm.ModelType != typeof(System.Data.EntityState)))
{
<li>
#Html.Display(prop.PropertyName)
</li>
}
</ol>
In the above scenario, all three fields have the right descriptors in the object.cshtml call (prop.DisplayName, prop.TemplateHint), but when the first property -- FamilyName -- is passed to String.cshtml, the ViewData.ModelMetadata is not populated at all. As a result, the template can't display a label (except "String"), nor assign the ID of the control, etc.
Street and DateStarted are emitted normally, with the ID and all. So I'm completely at a loss as to why the one property would fail to set the ViewData properties -- nor do I know how to step through past the Html.Display call to see what might be happening.
Any ideas for a next place to look?
So the problem was in the controller action, which for unrelated reasons used "FamilyName" for a ViewData value:
ViewBag.FamilyName = familyName;
And this caused all heck to break loose in the mapping of model fields with the same name -- that is, ModelMetadata will not propagate. So, the lesson is: don't give ViewData dictionary items keys with the same name as a field in your view model.

Resources