I have a Model with a couple of properties and I implemented Darin's answer from my previous question: ASP.NET MVC: Custom Validation by DataAnnotation. This enabled me to validate 4 properties at once (because I needed them together not to exceed a certain length).
The problem is, when I Annonate only 1 of the 4 properties with this custom validator, only that textbox's backgroundcolor will turn red when validation failed. When I annonate all 4 properties all 4 textboxes will turn red, but the error message will be displayed 4 times. Which is ugly. So I set #Html.ValidationSummary(false) so the error messages go in the summary, but all 4 error messages will be summarized (which is logical).
How can I make sure the error message will be displayed only once, while having all 4 textboxes turn red?
Thanks in advance for helping this MVC noob. Appreciate it.
I have not tested but I think the solution might be to pass a list of "Members" to the ValidationResult on the IsValid overriden method :
[I took the example of your former question]
public class CombinedMinLengthAttribute: ValidationAttribute
{
public CombinedMinLengthAttribute(int minLength, params string[] propertyNames)
{
this.PropertyNames = propertyNames;
this.MinLength = minLength;
}
public string[] PropertyNames { get; private set; }
public int MinLength { get; private set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var properties = this.PropertyNames.Select(validationContext.ObjectType.GetProperty);
var values = properties.Select(p => p.GetValue(validationContext.ObjectInstance, null)).OfType<string>();
var totalLength = values.Sum(x => x.Length) + Convert.ToString(value).Length;
if (totalLength < this.MinLength)
{
List<string> props = this.PropertyNames.ToList();
props.Add(validationContext.MemberName);
return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName), props);
}
return null;
}
}
It seems that ValidationResult.MemberNames has not been implement for MVC, see this.
In some situations, you might be tempted to use the second constructor
overload of ValidationResult that takes in an IEnumerable of
member names. For example, you may decide that you want to display the
error message on both fields being compared, so you change the code to
this:
return new ValidationResult(
FormatErrorMessage(validationContext.DisplayName), new[] { validationContext.MemberName, OtherProperty });
If you run your code, you will find absolutely no difference. This is
because although this overload is present and presumably used
elsewhere in the .NET framework, the MVC framework completely ignores
ValidationResult.MemberNames.
Related
I am working on WinForms with EF 6.2.
I am trying to implement custom validation logic for my entities with Entity Framework.
At first, I succeeded to override the DbEntityValidationResult ValidateEntity method in my DbContext and it's working fine.
But now I have a lot of entities, it becomes very messy and I would like to implement the custom validation directly in my entities classes.
So I tried to implement the IValidatableObject interface.
Here is a simple example of an entity :
public class Inspection : IValidatableObject
{
public int Id { get; set; }
[Required(AllowEmptyStrings = false, ErrorMessage = "You must enter a description")]
[StringLength(maximumLength: 15, ErrorMessage = "The description cannot exceed 15 characters")]
public string Description { get; set; }
public DateTime? ActualDate { get; set; }
public DateTime? ValidityDate { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (ActualDate > ValidityDate)
{
ValidationResult result = new ValidationResult("Actual Date connot be > to ValidityDate");
yield return result;
}
}
}
Now I read a lot of things but cannot figure where do I have to call the Validate method of my entities, and what value I have to pass in ValidationContext parameter.
Every tutorial I've seen targets MVC scenarios so I wonder if it is possible to use it with Winforms.
I maybe have missed something, or maybe it is not the correct approach for validation in Winforms/EF.
Please can you give me some piece of advice ?
Finally I found the solution (and my error) thanks to the MSDN article :
http://msdn.microsoft.com/en-us/data/gg193959.aspx
When I read it the first time, I missed a part while implementing the interface.
I forgot to define the memberNames parameter of the ValidationResult class.
So I just changed :
ValidationResult result = new ValidationResult("Actual Date connot be > to ValidityDate");
to
ValidationResult result = new ValidationResult("Actual Date connot be > to ValidityDate",
new[] { nameof(ActualDate), nameof(ValidityDate) });
And it worked as expected.
I am still wondering what value to pass in ValidationContext parameter if the Validate method of the interface is called manually, but it is another question.
So to answer my own question : Yes, it is possible to use IValidatableObject with Winforms.
I'm using C# web api and want to create a filter to all requests.
I have a designated class to every request so I just want to add some data annotations and get the validation over with.
The problem is that I'm getting true every time on actionContext.ModelState.IsValid
I have added my filter in the config:
config.Filters.Add(new RequestValidationFilter());
validation method looks like every other in the web
public class RequestValidationFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ModelState.IsValid == false)
{
var errors = actionContext.ModelState
.Values
.SelectMany(m => m.Errors
.Select(e => e.ErrorMessage));
actionContext.Response = actionContext.Request.CreateErrorResponse(
HttpStatusCode.BadRequest, actionContext.ModelState);
actionContext.Response.ReasonPhrase = string.Join("\n", errors);
}
}
}
I have the following method:
[HttpPost, Route("User/Login")]
public async Task<Responses.Login> Login(Requests.Login request)
{
...//some login logic
}
And also, I have my model which is:
public class Requests
{
public class Login
{
[Required(AllowEmptyStrings = false, ErrorMessage = "Email address cannot be empty!")]
[MinLength(5)]
public string Email;
[Required]
public string Password;
}
}
I'm sending both an empty request or request which Email and Password are null and still the actionContext.ModelState.IsValid evaluate it as true
Attached is an image when email was sent but password wasn't.
Following a comment, here is my request via Advanced Rest Client chrome plugin
NOTE
the image actually shows that Keys and Values are empty when in fact they are supplied..
EDIT
number of things i've also tried:
removing all other filters, why? maybe the context was messed up by another reading.
sending valid request in terms of fields, but email was 1 char long.why? maybe Requiredis working differently than others, still nothing about the min-length issue.
instead of nested objects, i created a seperate stand-alone class for the Login object. why? thought maybe the fact that it's nested the validation is not recursive.
Looping the Arguments list one-by-one and validate as object, answer is always true. never fails, why? cause Im almost admitting defeat.
instead of adding filter to config as i described in the question, tried GlobalConfiguration.Configuration.Filters.Add(new RequestValidationFilter()); instead
You need to add { get; set; } after your model properties:
public class Login
{
[Required(AllowEmptyStrings = false, ErrorMessage = "Email address cannot be empty!")]
[MinLength(5)]
public string Email { get; set; }
[Required]
public string Password { get; set; }
}
This is necessary because the default model validation for ASP.NET only includes properties with a public get method. From PropertyHelper.cs, here's some of the code which determines whether a property on the model will be included in validation:
// Indexed properties are not useful (or valid) for grabbing properties off an object.
private static bool IsInterestingProperty(PropertyInfo property)
{
return property.GetIndexParameters().Length == 0 &&
property.GetMethod != null &&
property.GetMethod.IsPublic &&
!property.GetMethod.IsStatic;
}
This method is used to filter the properties that are used in the default model binding in MVC/Web API. Notice that it's checking whether the GetMethod exists on the property and that it's public. Since your class didn't have the get methods on its properties, they were being ignored.
If you want to know more, you can kill a lot of time looking through the ASP.NET MVC source. I think the code on github is for a newer version of ASP.NET, but it seems like a lot of the same logic applies in the version you are using.
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.
Many forms in my project have a part number input. Currently in the controller I often times test the part number to see if the part exists or user has access or if the part is obsolete, then based on the condition I reload the view passing a message through a string parameter then viewbag for display to show why the form submit failed. I'm trying to clean this up and instead use model property validation.
I have a few working well however one of the validations I want to test is if the part is obsolete and has a suggested alternate part number to use. Based on the property value (part number) I have a service layer method that will return a bool of if the part is obsolete and another one that will return the suggested use part number. If possible I'd like to trigger the validation on the bool check, then pass that alternate part number into the validation message that gets generated.
Here is the validation code in it's current form:
public class PartAlternateValidation : ValidationAttribute
{
public override bool IsValid(object value)
{
if (value == null)
{
return false;
}
GP10Service gp10svc = new GP10Service();
//string altPart = gp10svc.GetPartAlternate(value.ToString());
return gp10svc.CheckPartAlternate(value.ToString());
}
}
Did quite a bit of searching around but couldn't find anything that specifically discussed this. Thinking maybe I could have an out string parameter with the bool in IsValid, but not sure how to then pass that to the message (or call FormatErrorMessage method from the IsValid method?). Thinking maybe there is a way using ModelState.AddModelError, however I believe the key on these is tied to the property, correct? So I'm fuzzy on how I could detect when the property fails a particular validation and use the property value as a variable in generating the message that way.
Probably something simple, has been a good excuse to do more research and I will continue reading but any suggestions or tips would be welcome.
Thanks.
Please see below tip helps..
Model:
namespace Mvc4Test.Models
{
public class Part
{
[AlternateValidation(Suggetion = "Please Use 123 (This is a Suggestion)")]
public string PartNumber { get; set; }
}
public class AlternateValidation : ValidationAttribute
{
public string Suggetion { get; set; }
protected override ValidationResult IsValid(object value,ValidationContext validationContext)
{
if (value != null)
{
if (value.ToString() == "123")
{
return ValidationResult.Success;
}
else
{
return new ValidationResult(Suggetion);
}
}else
return new ValidationResult("Value is Null");
}
}
}
View:
#Html.EditorFor(model => model.PartNumber)
#Html.ValidationMessageFor(model => model.PartNumber)
How would you implement validation for entity framework entities when different validation logic should be applied in certain situations?
For example, validate the entity in one way if the user is an admin, otherwise validate in a different way.
I put validation attributes on context-specific, dedicated edit models.
The entity has only validations which apply to all entities.
Before I start talking about how to do this with VAB, let me say that you will have to really think your validation rules over. While differentiating validations between roles is possible, it does mean that the object that a user in one roles saves, is invalid for another user. This means that a user in a certain role might need to change that object before it can save it. This can also happen for the same user when it is promoted to another role. If you're sure about doing this, please read on.
This seems like a good job for Enterprise Library's Validation Application Block (VAB), since it allows validation of these complex scenarios. When you want to do this, forget attribute based validation; it simply won't work. You need configuration based validation for this to work.
What you can do using VAB is using a configuration file that holds the actual validation. It depends a bit on what the actual validation rules should be, but what you can do is create a base configuration that always holds for every object in your domain. And next create one or more configurations that contain only the extended validations. Say, for instance, that you've got a validation_base.config file, a validation_manager.config and a validation_admin.config file.
What you can do is merge those validations together depending on the role of the user. Look for instance at this example that creates three configuration sources, based on the configuration file:
var base = new FileConfigurationSource("validation_base.config");
var mngr = new FileConfigurationSource("validation_manager.config");
var admn = new FileConfigurationSource("validation_admin.config");
Now you have to merge these files into (at least) two configurations. One containing the base + manager and the other that contains the base + admin rules. While merging is not something that is supported out of the box, this article will show you how to do it. When using the code in that article, you will be able to do this:
var managerValidations =
new ValidationConfigurationSourceCombiner(base, mngr);
var adminValidations =
new ValidationConfigurationSourceCombiner(base, admn);
The last thing you need to do is to wrap these validations in a class that return the proper set based on the role of the user. You can that like this:
public class RoleConfigurationSource : IConfigurationSource
{
private IConfigurationSource base;
private IConfigurationSource managerValidations;
private IConfigurationSource adminValidations;
public RoleConfigurationSource()
{
this.base = new FileConfigurationSource("validation_base.config");
var mngr = new FileConfigurationSource("validation_manager.config");
var admn = new FileConfigurationSource("validation_admin.config");
managerValidations =
new ValidationConfigurationSourceCombiner(base, mngr);
adminValidations =
new ValidationConfigurationSourceCombiner(base, admn);
}
public ConfigurationSection GetSection(string sectionName)
{
if (sectionName == ValidationSettings.SectionName)
{
if (Roles.UserIsInRole("admin"))
{
return this.adminValidations;
}
else
{
return this.managerValidations;
}
}
return null;
}
#region IConfigurationSource Members
// Rest of the IConfigurationSource members left out.
// Just implement them by throwing an exception from
// their bodies; they are not used.
#endregion
}
Now this RoleConfigurationSource can be created once and you can supply it when you validate your objects, as follows:
static readonly IConfigurationSource validationConfiguration =
new RoleConfigurationSource();
Validator customerValidator =
ValidationFactory.CreateValidator<Customer>(validationConfiguration);
ValidationResults results = customerValidator.Validate(customer);
if (!results.IsValid)
{
throw new InvalidOperationException(results[0].Message);
}
Please note that the Validation Application Block is not an easy framework. It take some time to learn it. When your application is big enough, your specific requirements however, will justify its use. If you choose the VAB, start by reading the "Hands-On Labs" document. If you have problems, come back here at SO ;-)
Good luck.
Until I hear a brighter idea, I'm doing this:
public partial class MyObjectContext
{
ValidationContext ValidationContext { get; set; }
partial void OnContextCreated()
{
SavingChanges += new EventHandler(EntitySavingChanges);
}
private void EntitySavingChanges(object sender, EventArgs e)
{
ObjectStateManager
.GetObjectStateEntries(EntityState.Added | EntityState.Modified | EntityState.Deleted)
.Where(entry => entry.Entity is IValidatable).ToList().ForEach(entry =>
{
var entity = entry.Entity as IValidatable;
entity.Validate(entry, ValidationContext);
});
}
}
interface IValidatable
{
void Validate(ObjectStateEntry entry, ValidationContext context);
}
public enum ValidationContext
{
Admin,
SomeOtherContext
}
public partial class MyEntity : IValidatable
{
public ValidationContext ValidationContext { get; set; }
public void Validate(ObjectStateEntry entry, ValidationContext context)
{
// this validation doesn't apply to admins
if (context != ValidationContext.Admin)
{
// validation logic here
}
}
}