C# MVC/API Custom Validation Trigger - One Field Only - c#

I have a single field for an API model that I want custom validation for through whatever method possible.
The solution I need is: On modelstate validation fail for that specific field - I want to run custom code to modify the value of that field.
Due to the codebase I'm working with some constraints:
The validation trigger code has to be inside the model or bound to it somehow.
It's not reasonable for me to create custom validator for the entire model.
It's not reasonable for me to put code in controller on !modelstate.valid

My problem was I didn't think you could mix and match annotations with custom validation. You can.
Make your class inherit IValidateableObject
public class MyClass : IValidateableObject
{
}
Add Validate Method - "FieldName" is your class variable being validated
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if(your_condition_is_invalid)
{
yield return new ValidationResult(
errorMessage: "your error message",
memberNames: new[] { "FieldName" }
);
}
}
Below not applicable to this problem specific problem but I thought to include incase there is a frontend component.
The downside is, according to the person I asked, if you are using for a webapp, there are cases where you need to write custom frontend validation.
It doesn't automatically mesh with default mvc frontend validation / jquery unobtrusive.
If you plan to do this for an app where you have strict frontend validation standards then be ready to write a validator for that field.

Related

What is the point of the MVC implementation of IValidatableObject if it can't be used to perform full validation of the object?

Please can someone help me, because I'm having real trouble getting on with validating my objects that have bound in my controller actions in one swoop.
I thought that IValidatableObjects Validate() method would fire every time the binding takes place, but this isn't true... if there is a general model error it won't fire.
This leads me to wonder, how am I supposed to perform a full complex validation on my object and return the full set of validation errors? No one wants to fix all of the reported errors on a web form, then submit it to have more returned to them.
I thought I might just be able to perform all of my validation in the Validate() method, but this isn't true because seemingly there is no way of getting away from the general validation of models. For example if you were to attempt to bind a string to an int, it doesn't silently fail, it adds a model validation error and then doesn't fire Validate to perform further validation on the object.
So I can't perform all of my validation just using one method:
General model validation using validation attributes
Reasons
Can't perform advanced validation, such as conditional validation based on other values within the model
It isn't designed to perform anything more than individual field validation
Implement IValidatableObject and perform full validation in Validate() method
Reasons
There is no getting away or 'switching off' the general model validation so that it fails silently so that I can perform full validation on the object
IValidatebleObject isn't always fired if general model validation fails in any way, such as a failed binding
How can I perform full validation of my object in one go, no matter whether the binding was successful or not?
Why doesn't IValidatableObject fire the Validation() method regardless of the binding success?
The problem you seem to be running into is that validations occur in different parts of the framework for different reasons.
First, client validation occurs. If all your fields have unobtrusive client validation, then all validation will occur at once on the client.
Second comes model binding. If an error occurs in trying to bind an item to it's model entry, then those items will first fail. For instance, if you try to bind the string "xxx" to a DateTime, it's going to throw a validation error because it cannot convert "xxx" to a DateTime. And, since DateTime is non-nullable, it can't simply put null there.
Third comes your actual server-side data attribute validations. If you have only partial client-side validation (meaning not all fields have client-side validation) then you can get the odd situation that it throws validation errors for some items client-side, then after the user has fixed those problems and submits, the server-side validation finds errors and you get more errors thrown.
Finally, IValidatableObject is called. Unfortunately, IValidatableObject has no client-side validation, so you would either have to create client-side validation for these fields, or completely disable client-side validation in order to get server-side validation to occur all at once.
IValidatableObject is a rather general purpose interface, and is used in more places than just MVC. MVC uses it because it's a convenient already existing interface. And, it was present before client-side validation was added to MVC. A better approach is to create a ValidationAttribute derived attribute that implements client-side validation, then supplying the proper javascript plug-ins for unobtrusive-validation.
If validation fails in any of the steps, it does not go on to the next step. ie., if client-side validation fails, it doesn't call server-side. If Data attributes fail, it doesn't call IValidatableObject.
One way around this is described in this post:
How to force MVC to Validate IValidatableObject
A few different options come to mind. If you need complex validation to be done on the client use of a javascript framework such as Angular or Knockout would be beneficial, or failing that adding a jQuery validation plugin such as:
http://jqueryvalidation.org/
It may also be worthwhile reviewing the ModelStateDictonary methods and seeing if these could be of help - e.g. manually calling ModelState.AddModelError for complex validation scenarios in your controller.
http://msdn.microsoft.com/en-us/library/system.web.mvc.modelstatedictionary_methods(v=vs.118).aspx
example:
bool valid = true;
if (string.IsNullOrEmpty(model.JobNumber))
{
ModelState.AddModelError("jobNumber", "Please enter job number");
valid = false;
}
return View(model);

Best Practices ViewModel Validation in ASP.NET MVC

I am using DataAnnotations to validate my ViewModel on client side with jquery.validate.unobtrusive and on server side in ASP.NET MVC application.
Not so long time ago, I figured out that I can write validation like this:
[Required(ErrorMessage = "{0} is required")]
public string Name { get; set; }
That way I can easily define some general strings in config or in resources and always use it in DataAnnotations. So it will be easier to change validation messages in my whole application in future.
Also I know that there is a FluentValidation library that allows to add validation rules to already existing ViewModel. I know that there is a problem with Add/Edit ViewModels that could have similar fields but different ValidationRules.
Another problem that comes from client validation is that html newly added to DOM (using ajax request) should be parsed to enable validation. This is how I do it:
$('#some-ajax-form').data('validator', null);
$.validator.unobtrusive.parse('#some-ajax-form');
So I have some questions:
Is there some other useful practises that could help centralize all validation rules in application?
What's is a best way to solve Add/Edit ViewModel Validation problem? Can I use DataAnnotations with FluentValidation or separate Add and Edit ViewModels still is a best option?
Is there any better way to initialize validation on new DOM elements that received with ajax call other that I mention?
I'm not asking how to create my own DataValidators I know how to do it. I seeking of ways how to use them in more productive and easy maintainable way.
To answer your 3th question first: No there is no easier way then what you are doing. Two lines of code to get it working can hardly be easier. Although there is a plug-in you could use, like explained in the question unobtrusive validation not working with dynamic content
Your first question, how to centralize validation, I normally use a separate class file to store all my validation rules. This way I don't have to browse through every single class file to find the rules, but have them all in one place. If that's better, is matter of choice. The main reason I started to use it, is to be able to add validation to auto-generated classes, like classes from the Entity Framework.
So I have a file called ModelValidation.cs in my data layer, and have code for all my models like
/// <summary>
/// Validation rules for the <see cref="Test"/> object
/// </summary>
/// <remarks>
/// 2015-01-26: Created
/// </remarks>
[MetadataType(typeof(TestValidation))]
public partial class Test { }
public class TestValidation
{
/// <summary>Name is required</summary>
[Required]
[StringLength(100)]
public string Name { get; set; }
/// <summary>Text is multiline</summary>
[DataType(DataType.MultilineText)]
[AllowHtml]
public string Text { get; set; }
}
Now as you noticed I don't provide the actual error message. I use conventions by Haacked to add the messages. It makes it simple to add localized validation rules.
It basically comes down to a recource file containing something like:
Test_Name = "Provide name"
Test_Name_Required = "Name is required"
And these messages and naming will be used when you call regular MVC view code like
<div class="editor-container">
<div class="editor-label">
#Html.LabelFor(model => model.Name) <!--"Provide name"-->
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name) <!--"Name is required"-->
</div>
</div>
Your second question, about different validation for add/edit can be handled in two ways. The best way, would be to use views as they are actually intended. That means you don't pass your actual models to the views, but you create a view model that contains only the data. So you have a view model for Create with the proper validation rules and a view model for Edit with the proper rules, and when they pass you insert the result in your actual model.
This however requires a lot more code and manual work, so I can imagine you're not really willing to do it like this.
Another option would be to use conditional validation like explained by viperguynaz. Now instead of a boolean, my classes that require a change between edit/add have a primary key Id int. So I check if Id>0 to determine if it is an edit or not.
UPDATE:
If you want to update validation on every ajax call, you could use jQuery ajaxComplete. This will revalidate all forms after every ajax request.
$( document ).ajaxComplete(function() {
$('form').each(function() {
var $el = $(this);
$el.data('validator', null);
$.validator.unobtrusive.parse($el);
})
});
If this is something you want, depends on how often you receive a form via AJAX. If you have a lot of AJAX request, like polling a status every 10seconds, than you don't want this. If you have an occasional AJAX request, that mostly contains a form, then you could use it.
If your AJAX returns a form you want to validate, then yes, it is good practise to update the validation. But I guess a better question would be "Do I really need to send the form by AJAX?"
AJAX is fun and useful, but it should be used with care and thought.
Like others have said, there is no such tricks, no easy way to centralize your validations.
I have a couple of approaches that might interest you. Take note that this is how "we" solved the same problem before. Its up to you if you can find our solution maintainable and productive.
I know that there is a problem with Add/Edit ViewModels that could have similar fields but different ValidationRules.
Inheritance Approach
You can achieve centralized validation using a base class, and use subclasses for specific validations.
// Base class. That will be shared by the add and edit
public class UserModel
{
public int ID { get; set; }
public virtual string FirstName { get; set; } // Notice the virtual?
// This validation is shared on both Add and Edit.
// A centralized approach.
[Required]
public string LastName { get; set; }
}
// Used for creating a new user.
public class AddUserViewModel : UserModel
{
// AddUser has its own specific validation for the first name.
[Required]
public override string FirstName { get; set; } // Notice the override?
}
// Used for updating a user.
public class EditUserViewModel : UserModel
{
public override string FirstName { get; set; }
}
Extending the ValidationAttribute Approach
Using custom ValidationAtribute, you can achieve centralized validation. This is only the basic implementation, I am just showing you the idea.
using System.ComponentModel.DataAnnotations;
public class CustomEmailAttribute : ValidationAttribute
{
public CustomEmailAttribute()
{
this.ErrorMessage = "Error Message Here";
}
public override bool IsValid(object value)
{
string email = value as string;
// Put validation logic here.
return valid;
}
}
You would use as such
public class AddUserViewModel
{
[CustomEmail]
public string Email { get; set; }
[CustomEmail]
public string RetypeEmail { get; set; }
}
Is there any better way to initialize validation on new DOM elements that received with ajax call other that I mention?
This is how I rebind validators on dynamic elements (jQuery)
/**
* Rebinds the MVC unobtrusive validation to the newly written
* form inputs. This is especially useful for forms loaded from
* partial views or ajax.
*
* Credits: http://www.mfranc.com/javascript/unobtrusive-validation-in-partial-views/
*
* Usage: Call after pasting the partial view
*
*/
function refreshValidators(formSelector) {
//get the relevant form
var form = $(formSelector);
// delete validator in case someone called form.validate()
$(form).removeData("validator");
$.validator.unobtrusive.parse(form);
};
Usage (JQuery)
// Dynamically load the add-user interface from a partial view.
$('#add-user-div').html(partialView);
// Call refresh validators on the form
refreshValidators('#add-user-div form');
Jquery unobtrusive validation works by applying attributes to INPUT elements that instruct the client library to validate that element using a rule that is mapped to the respective attribute. For instance: the data-val-required html attribute is recognized by the unobtrusive library, and causes it to validate that element against the corresponding rule.
In .NET MVC, you can make this happen automatically for some specific rules by applying attributes to your model properties. Attributes like Required and MaxLength work because the Html helpers know how to read those attributes and add corresponding HTML attributes to their output that the unobtrusive library understands.
If you add validation rules to your models in IValidatableObject or using FluentValidation, the HTML Helper will not see these rules, and therefore not try to translate them to unobtrusive attributes.
In other words the "free" coordination you've seen thus far by applying attributes to your model and getting client validation is limited to validation attributes, and further, is limited (by default) only to those attributes that map directly to unobtrusive rules.
The bright side is, you are free to create your own custom validation attributes, and by implementing IClientValidatable, the Html Helper will add an unobtrusive attribute with the name of your choosing that you can then teach the unobtrusive library to respect.
This is a custom attribute we use that ensures that one date falls after another date:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class DateGreaterThanAttribute : ValidationAttribute, IClientValidatable
{
string otherPropertyName;
public DateGreaterThanAttribute(string otherPropertyName, string errorMessage = null)
: base(errorMessage)
{
this.otherPropertyName = otherPropertyName;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
ValidationResult validationResult = ValidationResult.Success;
// Using reflection we can get a reference to the other date property, in this example the project start date
var otherPropertyInfo = validationContext.ObjectType.GetProperty(this.otherPropertyName);
// Let's check that otherProperty is of type DateTime as we expect it to be
if (otherPropertyInfo.PropertyType.Equals(new DateTime().GetType()))
{
DateTime toValidate = (DateTime)value;
DateTime referenceProperty = (DateTime)otherPropertyInfo.GetValue(validationContext.ObjectInstance, null);
// if the end date is lower than the start date, than the validationResult will be set to false and return
// a properly formatted error message
if (toValidate.CompareTo(referenceProperty) < 1)
{
validationResult = new ValidationResult(this.GetErrorMessage(validationContext));
}
}
else
{
// do nothing. We're not checking for a valid date here
}
return validationResult;
}
public override string FormatErrorMessage(string name)
{
return "must be greater than " + otherPropertyName;
}
private string GetErrorMessage(ValidationContext validationContext)
{
if (!this.ErrorMessage.IsNullOrEmpty())
return this.ErrorMessage;
else
{
var thisPropName = !validationContext.DisplayName.IsNullOrEmpty() ? validationContext.DisplayName : validationContext.MemberName;
var otherPropertyInfo = validationContext.ObjectType.GetProperty(this.otherPropertyName);
var otherPropName = otherPropertyInfo.Name;
// Check to see if there is a Displayname attribute and use that to build the message instead of the property name
var displayNameAttrs = otherPropertyInfo.GetCustomAttributes(typeof(DisplayNameAttribute), false);
if (displayNameAttrs.Length > 0)
otherPropName = ((DisplayNameAttribute)displayNameAttrs[0]).DisplayName;
return "{0} must be on or after {1}".FormatWith(thisPropName, otherPropName);
}
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
//string errorMessage = this.FormatErrorMessage(metadata.DisplayName);
string errorMessage = ErrorMessageString;
// The value we set here are needed by the jQuery adapter
ModelClientValidationRule dateGreaterThanRule = new ModelClientValidationRule();
dateGreaterThanRule.ErrorMessage = errorMessage;
dateGreaterThanRule.ValidationType = "dategreaterthan"; // This is the name the jQuery adapter will use
//"otherpropertyname" is the name of the jQuery parameter for the adapter, must be LOWERCASE!
dateGreaterThanRule.ValidationParameters.Add("otherpropertyname", otherPropertyName);
yield return dateGreaterThanRule;
}
}
We may apply the attribute to the model as such:
[DateGreaterThan("Birthdate", "You have to be born before you can die")]
public DateTime DeathDate { get; set; }
This causes the Html helper to render the following two attributes on the INPUT element when calling Html.EditorFor on a model property that has this attribute:
data-val-dategreaterthan="You have to be born before you can die"
data-val-dategreaterthan-otherpropertyname="Birthdate"
So far so good, but now I have to teach unobtrusive validation what to do with those attributes. First, I have to create a named rule for jquery validation:
// Value is the element to be validated, params is the array of name/value pairs of the parameters extracted from the HTML, element is the HTML element that the validator is attached to
jQuery.validator.addMethod("dategreaterthan", function (value, element, params) {
return Date.parse(value) > Date.parse($(params).val());
});
And then add an unobtrusive adaptor for that rule that maps the attribute to the rule:
jQuery.validator.unobtrusive.adapters.add("dategreaterthan", ["otherpropertyname"], function (options) {
options.rules["dategreaterthan"] = "#" + options.params.otherpropertyname;
options.messages["dategreaterthan"] = options.message;
});
After Ive done all this, I can get this validation rule for "free" anywhere else in my application just by applying that attribute to the model.
To address your question of how to apply rules conditionally based on whether the model is being used in an add or an edit operation: this can probably be done by adding additional logic to your custom attributes and having both the IsValid method the GetClientValidation rules method attempt to glean some context from the model using reflection. But honestly, that seems like a mess to me. For this, I'd just rely on server validation and whatever rules you choose to apply using IValidatableObject.Validate() method.
There are various ways to have Client Validation, like one that Microsoft uses for MVC, works with ubobtrusive library created by itself for integrating with DataAnnotations. But, after some years of working with this helpful tool, I tired of it which is boring and tedious to employ in cases that we need separate ViewModels (and likely separate ViewModels for create/edit templates).
Another way is using MVVM which works well with MVC since the two paradigms are quite similar. In MVC you have a Model that is bounded just on the server side when the client send content to the server. While MVVM binds a local model with the UI directly on the client. Take a look at the Knockoutjs, the known one which help you to understand how to work with MVVM.
With this in mind, I'll answer your questions in order:
You can't centralize validation rules in the application unless by
creating shared classes and reuse them by calling in separate
Models/ViewModels.
If you wanna use Microsoft Validator, separating the Add/Edit
ViewModels is a best option because of its readability and easier way to
change.
I never said that the Knockoutjs is better, they are different
from each other, just gives you some flexibility to create views
based on model requirements. This also take you away from
centralizing validations :(

UpdateModel without validation

Is there a possibility in ASP.NET MVC3 to set the properties of a view model from a FormCollection without invoking validation specified by validation attributes on the properties?
UpdateModel<T>(T model) also invokes validation.
The background:
I can only validate the model if a connection to the database is present, but this connection can only be established, if the data from one specific form field is correct (kind of an access code for each organisation). If the connection is not established, an exception is thrown.
When the data entered in this field is incorrect, I don't want to loose the values entered in all other form fields, but present the already entered values again to the user and give him a change to correct the errors.
So what I need is basically something like conditional validation or no validation by the model binder at all.
Is there anything like this built-in in ASP.NET MVC or do I need to write my own UpdateModel method, calling a (custom) model binder?
Why not pass the viewmodel into your method, rather than formscollection? That way you can do
[HttpPost]
public ActionResult Update(UpdateViewModel model)
{
if (!Model.IsValid)
{
return View(model);
}
}
So if the validation fails your user is going to be directed back to the Update view, with the model already populated from the information they submitted.
Now, in my opinion, having a validation attribute require a database connection, which in turn can throw exceptions is the wrong way to go about this. Those sorts of validation I move to a validation service, which is injected into the controller (and takes in the repository stuff it needs via DI as well). Each validation service will take in a view model and return a collection of ValidationResult instances, which I then attach to the model validation results via an extension method to the Controller class (both lifted from the Project Silk stuff MS P&P is pushing out)
This allows for easier testing as you can mock up the repository and have it return the correct results for testing ...

Implementing a class-level validation attribute that writes to a property inside the class

I'm building an ASP.NET MVC site where I want to decorate my ViewModels with validation attributes. One of the things I want to validate is that the address that a user submits through a form is geocodable. For that, I have already created a custom ValidationAttribute and have applied to my StreetAddress property.
This is all good, except that I am actually making two geocoding requests - one in my ViewModel for validation and another in my Controller to input the latitude and longitude into my database. I want to cut down on unnecessary network usage and delays, so I need to pass the
result from the validation geocode into my controller.
To accomplish such a thing, I think I should create a Latitude and Longitude property in my ViewModel. The View itself won't touch these 2 properties, but the validation attribute will either report a failure in geocoding and return the View or write the results into the properties.
For a validation attribute to access 3 properties, it has to be applied to the whole class. I don't know how to do that yet, but that's what this question is for.
UPDATE: Thanks to this answer, I have figured out how to create a class-level validation attribute. The link in the answer also demonstrates how to read the contents of a property inside the class (via Reflection). I still haven't figured out how to write to a property, though.
My Questions
How do I create a ValidationAttribute that can be applied to a whole class? Below, I have posted the code that I want to transfrom into an attribute that can be applied to my ViewModel:
public class GeocodableAttribute : ValidationAttribute
{
public GeocodableAttribute() : base()
{
ErrorMessage = "We weren't able to find that location.";
}
public override bool IsValid(object value)
{
if (value == null) //we don't care if it's required or not.
{
return true;
}
var address = (string)value;
var response = Geocoder.CallGeoWS(address.Trim());
if(response.Status=="ZERO_RESULTS")
{
return false;
}
return true;
}
}
How do I have the attribute write to certain properties in the class that it is applied to? When I apply this attribute to my ViewModel, I want it to write successful geocoding results into two properties and return true. How can I implement that?
UPDATE #2: I think I just figured out how to write to a property. Should the following code work?
private static void WritePropertyValue(object obj, string propertyName, object valueToWrite)
{
if (obj == null) return null;
var type = obj.GetType();
var propertyInfo = type.GetProperty(propertyName);
if (propertyInfo == null) return null;
propertyInfo.SetValue(obj, valueToWrite, null);
}
Will this break client-side validation for other attributes/properties? I have other properties in my ViewModel that I have decorated with built-in ValidationAttributes, such as [Required] and [Range]. I have also enabled client-side validation for them. Will applying my new attribute to the whole ViewModel class completely break client-side validation or will validation for the other properties be performed on the client and then total validation will be performed on the server?
1) You can't access the outer class via a property level ValidationAttribute.
You could use a custom model binder to accomplish this. Simply detect the attributes and validate accordingly.
Creating a custom model binder:
http://www.singingeels.com/Articles/Model_Binders_in_ASPNET_MVC.aspx
2) No. Did you try?
This is almost a duplicate. I'd check out the question and answers for my dupe submission. It may contain a separate technique, class level validation, that may do what you need.

Refresh ModelState to remove errors

Refreshing the ModelState
Hi, I have a question about the ModelState in an ASP.NET MVC controller.
When the user selects a certain option from the view, the start date and end date for the "certification" will be set based on the other dates entered.
The problem with this is the certification dates come back as null and our CertificationMetaData class specifys the fields as [Required] so the ModelState is invalid as soon as the action loads.
Removing the ModelSate errors manually allows this to work but I was wondering if there is a better way to do this? Is there a way to refresh the ModelState? Should I make the fields not required? Or should I add a date value from the view with javascript?
public ActionResult Create(FormCollection fc, Certification certification, Absence absence)
{
if (certification.CertificationTypeID == 1)
{
certification.CertificationStartDate = absence.StartDate;
certification.CertificationEndDate = absence.StartDate.AddDays(7);
this.ModelState.Remove("CertificationStartDate");
this.ModelState.Remove("CertificationEndDate");
}
if (this.ModelState.IsValid)
{
// save
return RedirectToAction("Index");
}
return View();
}
Also as you can see I have hardcoded the ID value for the certification type. What is the best way to compare values with lookup table values? Is an enum the best way to go?
Thanks
The following approach refreshes the model state and allows you to keep your model design consistent with [required] attributes etc.
In my case I want my model to have a required field that normal level users using an API can't change, so I've done this:
ModelState.Remove("ChangeDate");
ModelState.Add("ChangeDate", new ModelState());
ModelState.SetModelValue("ChangeDate", new ValueProviderResult(club.ChangeDate, DateTime.Now.ToString(), null));
That way you don't need to remove your required fields, and you also don't need to supply a date in javascript.
Obviously this is a personal thing, but I wouldn't remove the error messages.
If I was going for the simple solution then I would remove the [Required] attribute and add validation code to the controller to add the error if the dates were missing or set them to the alternate value if it was the correct type.
If I was going for the more complex solution I would put the validation at the Model level. Possibly a base class or and interface that the model must implement. A ValidationHelper class with a static Validate(IValidate object) method that will inspect the ValidationAttributes and calls a Validate method on the Model. It would then return a collection of ValidationErrors. Then a custom ModelBinder would be written that understands the Model validation and maps these to ModelState errors.

Categories

Resources