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 :(
Related
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.
I am making a form with MVC and am using the [ControlType]For([expression]) helper methods (EG Html.TextBoxFor(...))
To bind the data to the controls I am using the following ViewModel:
public class UserViewModel
{
//The Model that will be used to bind data to the elements
public UserModel User { get; set; }
//Used to bind selectable options to DropDownLists
public SelectList DescentTypes { get; set; }
public SelectList GenderTypes { get; set; }
}
When using this the name of the controls get set to name="Property.SubProperty" (EG name="User.Id") but I would rather it show as name="Id" on my html form.
Is it possible to do this without having to write a lot of custom code so the framework can translate it back to the ViewModel (UserViewModel) or just the Model (User) itself?
I'd advise leaving the default naming in place unless you have a very good reason to alter it. IDs (which it appears your question is leaning towards) are more flexible.
Changing IDs
IDs aren't submitted with a form, so you can set them as desired without breaking model binding. By default, they are hierarchical, but you can override them inline:
#Html.TextBoxFor( o => o.UserName, new { id = "foo" } )
Of course, this is manual work.
If the big concern is external JS/CSS, I'd suggest using class names and data-* attributes in your (CSS/jQuery/whatever) selectors rather than IDs.
#Html.TextBoxFor( o => o.User.UserName, new { data_role="grand-total" } )
It's still manual, but it's descriptive and independent of an ID.
Sometimes I use a snippet of script in my views to initialize a larger JS class with data that is most easily available directly within the view. This lets the bulk of the script reside in an external file while allowing dynamic values to be used to initialize it. This is useful for more than just IDs.
Altering Generated Markup and Binding
For reference, let's say you wanted to change ID and name.
Write your own HtmlHelper extension methods to create the markup you want. You could probably wrap the existing methods that do not take an expression and pass explicit values to them to indicate the name that you want.
Write your own ModelBinder to map the raw form collection.
Determine a strategy for dealing with hierarchical objects (which is the main reason the naming convention exists in the first place).
Item #3 could be addressed by decorating properties to indicate how the naming should be performed and how model binding should map. This could become complicated quickly.
public class UserViewModel
{
// use this metadata to determine how to handle properties on this member
[Flatten]
public UserModel User { get; set; }
public SelectList DescentTypes { get; set; }
public SelectList GenderTypes { get; set; }
}
Alternatives
Flatten your view model by adding User's properties directly to it. It looks like your are composing your view model from domain model(s). This isn't usually a good idea. I'd suggest reading the pros/cons of binding directly to domain models.
Leave the naming alone. It really isn't hurting anything and it makes life easy. You can avoid ever directly working with names/IDs in your client code by using helper methods.
For example:
// JavaScript + Razor
var name = "#Html.NameFor( o => o.User.Id )";
alert(name);
I have a fairly simple (to explain) requirement for my ASP.NET MVC web application:
On any page, be able to display a message based on an event that happened on a preceding page. Messages may include dynamic content, such as information entered by the user in a form submission.
Similar to this...
The approach must work in POST-REDIRECT-GET scenarios.
If the user has two browser tabs (which share session state) open on the application, then any message should only be displayed in the tab in which the related event happened.
For example:
After submitting a feedback form, a message thanking the user (by name)
When logging in, a message telling the user the date/time when they last logged in to the application
My first thought was to use a strongly-typed view with an appropriate model (with a string property) for this. However this would mean changing every single existing non-strongly-typed view to use this model, and updating all existing models to inherit from this new model.
This seems like overkill.
An alternative is to store the message in TempData:
// In my controller
TempData["Message"] = "Some kind of message";
// In my shared master view
#if (TempData["Message"] != null)
{
<div class="message">#TempData["Message"]</div>
}
Using TempData is covered in a bit more detail in this blog posting. Interestingly, the author also suggests using a custom HTTP header as an alternative. An interesting idea, but does not work with POST-REDIRECT-GET.
Is using TempData the preferred option, or is there a "better" alternative? (e.g. something more strongly typed)
My first thought was to use a strongly-typed view with an appropriate model (with a string property) for this. However this would mean changing every single existing non-strongly-typed view to use this model, and updating all existing models to inherit from this new model.
This is the only consistent, reusable, testable solution to do this that I can imagine, despite the extra work it may cause.
It is best practice to use ViewModels to communicate between View and Controllers. You can have a base View Model and all other View Models derived from that as below:
public class BaseVM
{
public string Message{ get; set;}
}
public class CreateViewModel: BaseVM
{
public string CustoomerName{ get; set;}
}
You can populate the Message property while returning the model to the controller as below:
public ActionResult Step2()
{
//Some Logic
step2Model.Message = "Yes, Success..!!";
return View(step2Model);
}
After that, on each view page, you can check if that property has something in it.You can do so as below:
#if(!string.IsNullOrEmpty(Model.Message))
{
//Show message
}
EDIT:
OP is well aware of this approach, but still keeping this answer as it has a snippet to show how to do this in code. Secondly, when it comes to use ViewModels, I agree with following comment by CodeCaster in his answer.
This is the only consistent, reusable, testable solution to do this that I can imagine, despite the extra work it may cause.
You could make a convention in your project that a message to display would always go into ViewBag.InfoMessage dynamic variable. So, in your layout file you would display it if it's passed into the view from a controller.
More strict way would be to create a base model class with InfoMessage property and derive all other models / viewmodels from that base.
To persist the message through POST-REDIRECT-GET scenario, use a session variable which you'd clear once the value is sent to the view - to avoid displaying on more than one page.
Here some code:
public class BaseViewModel
{
public string InfoMessage { get; set; }
}
public class SpecificViewModel : BaseViewModel
{
// other model properties
}
In your controller then:
SpecificViewModel vm = new SpecificViewModel();
vm.InfoMessage = Session["InfoMessage"] as string;
Session["InfoMessage"] = null;
// other code
return View(vm);
Of course your view will have strongly typed model: SpecificViewModel.
I’m tracing down an unexpected behavior in MVC3, having to do with how it gets model metadata.
I had previously talked to one of my developers about using the same EditorTemplate for some data which is collected in two different areas of the system. The data fields are almost identical, except for the [Required] attribute. In one page certain fields are required, in the other page they are not. Theoretically this can be accomplished by using a base model which has the common attributes on each field, and inheriting those models, overriding the properties and adding additional validation attributes. For example:
class BaseModel
{
[Display(Name=”My Label”)]
public virtual string MyLabel { get; set ;}
}
class RequiredModel : BaseModel
{
[Required]
public override string MyLabel { get; set ;}
}
Then the View can be strongly typed to BaseModel, and calls to #Html.EditorFor(m=>m.MyLabel) in the view should pick up the correct attributes, depending on whether the actual instance of the model is a BaseModel or RequiredModel.
That’s the theory.
And in fact, it works well if you use the “old” HTML helper, e.g. #Html.TextBox(“MyLabel”). Those call ModelMetadata.FromStringExpression(field), which correctly gets the metadata from RequiredModel if the concrete model instance is RequiredModel. The newer helper methods call ModelMetadata.FromLambdaExpression(expression), which does NOT correctly get the metadata from the correct concrete instance.
Is this a bug in MVC? Intentional behavior? Is there a workaround, or a better way to address this problem?
This is of course a trivial example, the actual code we're dealing with has about 20 fields with some complex business rules and interaction, which is the same on both pages EXCEPT for which fields are required.
That's the theory.
No, that's not the theory. At least not mine.
My theory is to use separate view models for each views because the requirements of your views are different. So you would have this:
public class UpdateViewModel
{
[Display(Name = "My Label")]
public string MyLabel { get; set ;}
}
and:
public class CreateViewModel
{
[Display(Name = "My Label")]
[Required]
public string MyLabel { get; set ;}
}
Personally that's what I would do. I would totally sacrify DRY into designing view models because the requirements of my view change often and I want to have total control.
Obviously in practice I don't even bother with doing validation using declarative DataAnnotations attributes. They limit me. I use FluentValidation.NET which addresses problems like yours in a pretty elegant manner (by simply defining two different validators for the same view model - in case you decide to violate my theory and use the same view model in different views).
Now feel free to downvote my answer. I have just given my 2¢.
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.