I have next (simplified) view model:
public class RegisterModel
{
public string UserName { get; set; }
[MustExistIf("SomeProperty", "some value", "SomeOtherProperty", ErrorMessage = "You have to select something")]
public string LastName { get; set; }
public AddressModel Address { get; set; }
}
public class AddressModel
{
public string Street { get; set; }
public string House { get; set; }
}
and I have custom validator
public class MustExistIfAttribute : ValidationAttribute, IClientValidatable
{
private string _masterName { get; set; }
private object _masterValue { get; set; }
private string _dependantName { get; set; }
public MustExistIfAttribute(string masterName, object masterValue, string dependantName)
{
this._masterName = masterName;
this._masterValue = masterValue;
this._dependantName = dependantName;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
// get value of master property
var masValue = _getValue(validationContext.ObjectInstance, _masterName);
// get value of property whch depends on master property
var depValue = _getValue(validationContext.ObjectInstance, _dependantName);
if (masValue.Equals(_masterValue)) // if value in request is equal to value in specified in data annotation
{
if (depValue == null) // if dependant value does not exist
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
public override bool IsValid(object value)
{
return base.IsValid(value);
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var modelClientValidationRule = new ModelClientValidationRule
{
ValidationType = "mustexistif",
ErrorMessage = FormatErrorMessage(metadata.DisplayName)
};
modelClientValidationRule.ValidationParameters.Add("mastername", this._masterName);
modelClientValidationRule.ValidationParameters.Add("mastervalue", this._masterValue);
modelClientValidationRule.ValidationParameters.Add("dependantname", this._dependantName);
yield return modelClientValidationRule;
}
private static object _getValue(object objectInstance, string propertyName)
{
...
}
}
I have next javascript (please neglect returning false in mustexitif method - it's just for test purposes)
(function () {
jQuery.validator.addMethod('mustexistif', function (value, element, params) {
var masterName = params['mastername'];
var masterValue = params['mastervalue'];
var dependantName = params['dependantname'];
return false;
});
var setValidationValues = function (options, ruleName, value) {
options.rules[ruleName] = value;
if (options.message) {
options.messages[ruleName] = options.message;
}
};
var $Unob = $.validator.unobtrusive;
$Unob.adapters.add("mustexistif", ["mastername", "mastervalue", "dependantname"], function (options) {
var value = {
mastername: options.params.mastername,
mastervalue: options.params.mastervalue,
dependantname: options.params.dependantname
};
setValidationValues(options, "mustexistif", value);
});
})();
It works as expected when I decorate LastName property of RegisterModel class with MustExistIf annotation (like in provided code).
But what I really want is to decorate complex Address property of RegisterModel with MustExistIf annotation. Problem is that when I do that no unobrusive adapter gets registered (javascript doing that IS NOT triggered).
So, there is difference when I decoreate simple and complex properties. My solution does not allow me to decorate properties of Address class (FYI, I tried that and then also validation is working fine). Is there a way to accomplish what I intended? Am I missing something? Woud solution be to validate on model level? But then is it possible to do client side validation?
Maybe you can use Remote Validation.
http://msdn.microsoft.com/en-us/library/gg508808%28v=vs.98%29.aspx
Related
I need to have different validation for the same object. So I thought to use Metadatatype to define the different rules the code is below:
public class ValidateObjectAttribute : ValidationAttribute
{
private readonly Type _validationMetaDataType;
public ValidateObjectAttribute(Type validationMetaDataType)
{
_validationMetaDataType = validationMetaDataType;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var modelType = value.GetType();
AssociatedMetadataTypeTypeDescriptionProvider associatedMetadataTypeTypeDescriptionProvider = null;
if (_validationMetaDataType != null)
{
associatedMetadataTypeTypeDescriptionProvider = new AssociatedMetadataTypeTypeDescriptionProvider(modelType, _validationMetaDataType);
TypeDescriptor.AddProvider(associatedMetadataTypeTypeDescriptionProvider, modelType);
}
var validationctx = new ValidationContext(value);
var results = new List<ValidationResult>();
Validator.TryValidateObject(value, validationctx, results, true);
if (associatedMetadataTypeTypeDescriptionProvider != null)
{
TypeDescriptor.RemoveProvider(associatedMetadataTypeTypeDescriptionProvider, modelType);
TypeDescriptor.Refresh(value);
}
if (results.Count == 0) return ValidationResult.Success;
return new ValidationResult($"Validation fail for prop: {validationContext.DisplayName}");
}
}
public class BarMetaData1
{
[Required]
public string BarField1;
[Required]
public string BarField2;
}
public class FooMetaData1
{
[Required]
public string FooField1;
[Required]
public string FooField2;
[Required, ValidateObject(typeof(BarMetaData1))]
public Bar FooObject1;
}
public class FooMetaData2
{
public string FooField1;
[Required]
public string FooField2;
[Required, ValidateObject(typeof(BarMetaData1))]
public Bar FooObject1;
}
public class BaseValidation
{
public bool IsValid(Type validationMetaDataType)
{
var modelType = this.GetType();
AssociatedMetadataTypeTypeDescriptionProvider associatedMetadataTypeTypeDescriptionProvider = null;
if (validationMetaDataType != null)
{
associatedMetadataTypeTypeDescriptionProvider = new AssociatedMetadataTypeTypeDescriptionProvider(modelType, validationMetaDataType);
TypeDescriptor.AddProvider(associatedMetadataTypeTypeDescriptionProvider, modelType);
TypeDescriptor.Refresh(this);
}
var validationctx = new ValidationContext(this);
var results = new List<ValidationResult>();
Validator.TryValidateObject(this, validationctx, results, true);
if (associatedMetadataTypeTypeDescriptionProvider != null)
{
TypeDescriptor.RemoveProvider(associatedMetadataTypeTypeDescriptionProvider, modelType);
}
return results.Count == 0;
}
}
public class Foo : BaseValidation
{
public string FooField1 { get; set; }
public string FooField2 { get; set; }
public Bar FooObject1 { get; set; }
}
public class Bar
{
public string BarField1 { get; set; }
public string BarField2 { get; set; }
}
the validation is call in this way:
public void Index()
{
Foo fooInstance = new Foo()
{
FooObject1 = new Bar()
};
fooInstance.IsValid(typeof(FooMetaData2));
fooInstance.IsValid(typeof(FooMetaData1));
Foo fooInstance2 = new Foo()
{
FooObject1 = new Bar()
};
fooInstance2.IsValid(typeof(FooMetaData2));
}
What is the issue:
first validation [fooInstance.IsValid(typeof(FooMetaData2))] is right (2 mandatory fields missing),
second validation call [fooInstance.IsValid(typeof(FooMetaData1))] the result is wrong (3 mandatory field missing but the code notify me only 2), it seems that the code apply the validation describe in FooMetaData2 class instead of FooMetaData1
Someone can explain to me why?
thanks
I solved using fluentValidator plugin
Based on the following link: Multi Language - Data Annotations
Make a series of classes to translate the texts of the Data Annotation.
Everything works fine on the server side, but client side validation does not work.
If i use: [System.ComponentModel.DataAnnotations.Required]
public string Name { get; set; }
Validation on the client side works correctly, but if I use:
[Infrastructure.Required]//My custom class
public string Name { get; set; }
It works only on the server side.
This is the class that I am currently using:
namespace project.Infrastructure
{
public class RequiredAttribute : System.ComponentModel.DataAnnotations.RequiredAttribute
{
private string _displayName;
public RequiredAttribute()
{
ErrorMessageResourceName = "Validation_Required";
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
_displayName = validationContext.DisplayName;
return base.IsValid(value, validationContext);
}
public override string FormatErrorMessage(string name)
{
var msg = WebsiteTranslations.GetTranslationErrorMessage(Settings.LanguageId, "Required", WebsiteTranslations.GetTranslation(name, 1, Settings.LanguageId));
return string.Format(msg, _displayName);
}
public System.Collections.IEnumerable GetClientValidationRules(System.Web.Mvc.ModelMetadata metadata, ControllerContext context)
{
return new[] { new ModelClientValidationRequiredRule((ErrorMessage)) };
}
}
}
I get the answer from this post: validation-type-names-in-unobtrusive
The GetClientValidationRules Method is like this:
public IEnumerable GetClientValidationRules(System.Web.Mvc.ModelMetadata metadata, ControllerContext context)
{
var clientValidationRule = new ModelClientValidationRule()
{
ErrorMessage = FormatErrorMessage(ErrorMessage),
ValidationType = "required"
};
yield return new[] { clientValidationRule };
}
And in the Application_Start inside Global.asax:
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredAttribute), typeof(RequiredAttributeAdapter));
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(StringLengthAttribute), typeof(StringLengthAttributeAdapter));
I am trying to create a custom validation attribute to only require a field depending on the result of another.
The problem I am having is that the IsValid block is never called. The data seems to be getting into the fields and I have been able to check this with a breakpoint.
I tried putting a TryValidateModel(this) in the OnPostAsync and this worked through the breakpoint but I could see that another error occurred.
The requested operation is invalid for DynamicMethod
Here is my code below. Any help would be appreciated.
public class PageOneModel : PageModel
{
[BindProperty]
public bool CompanyHouseToggle { get; set; }
[BindProperty]
[StringLength(60, MinimumLength = 3)]
[RequiredIf("CompanyHouseToggle", desiredvalue: "true")]
public string CompanyNumber { get; set; }
[BindProperty]
[StringLength(60, MinimumLength = 3)]
public string OrganisationName { get; set; }
[BindProperty]
[RegularExpression(pattern: "(GB)?([0-9]{9}([0-9]{3})?|[A-Z]{2}[0-9]{3})", ErrorMessage = "This VAT number is not recognised")]
public string VatNumber { get; set; }
public void OnGet()
{
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
RedirectToPage("2");
}
}
public class RequiredIfAttribute : System.ComponentModel.DataAnnotations.ValidationAttribute
{
private String PropertyName { get; set; }
private Object DesiredValue { get; set; }
public RequiredIfAttribute(String propertyName, Object desiredvalue)
{
this.PropertyName = propertyName;
this.DesiredValue = desiredvalue;
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
var property = context.ObjectType.GetProperty(PropertyName);
if (property == null)
throw new ArgumentException("Property with this name not found");
// Just for testing purposes.
return new ValidationResult(ErrorMessage);
}
}
I suggest you to inherit from the ReuiredAttribute. It fully works for me.
public class RequiredUnlessDeletingAttribute : RequiredAttribute
{
string DeletingProperty;
/// <summary>
/// Check if the object is going to be deleted skip the validation.
/// </summary>
/// <param name="deletingProperty">The boolean property`s name which shows the object will be deleted.</param>
public RequiredUnlessDeletingAttribute(string deletingProperty = "MustBeDeleted") =>
DeletingProperty = deletingProperty;
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var property = validationContext.ObjectType.GetProperty(deletingProperty);
if ((bool)property.GetValue(validationContext.ObjectInstance))
return ValidationResult.Success;
return base.IsValid(value, validationContext);
}
}
Check the full implementation here
I am creating a select list which is populated with enum values:
<%= Html.DropDownListFor(model => model.OrderStatus, new SelectList(Enum.GetValues(typeof(OrderStatus))))%>
I am now performing client-side validation to ensure that OrderStatus is set properly:
[DisplayName("Order Status"), EnsureOrderStatus("ID")]
public OrderStatus OrderStatus { get; set; }
public class EnsureOrderStatus : ValidationAttribute, IClientValidatable
{
private readonly string OrderIDPropertyName;
public EnsureOrderStatus(string orderIDPropertyName)
{
OrderIDPropertyName = orderIDPropertyName;
}
protected override ValidationResult IsValid (object value, ValidationContext validationContext)
{
ValidationResult validationResult = ValidationResult.Success;
var propertyTestedInfo = validationContext.ObjectType.GetProperty(OrderIDPropertyName);
if (propertyTestedInfo == null)
{
validationResult = new ValidationResult(string.Format("Unknown property {0}", OrderIDPropertyName));
}
else
{
int orderID = (int)propertyTestedInfo.GetValue(validationContext.ObjectInstance, null);
OrderStatus orderStatus = (OrderStatus)value;
if (orderID == 0 && orderStatus != OrderStatus.Future)
{
validationResult = new ValidationResult(string.Format("Order must have have an Order Status of Future when being created."));
}
}
return validationResult;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = ErrorMessage,
ValidationType = "ensureorderstatus"
};
rule.ValidationParameters["orderid"] = OrderIDPropertyName;
yield return rule;
}
}
The value of 'value' inside of IsValid is always the first enum value of OrderStatus. I was wondering if there was other steps which need to be taken to ensure that the selected value of a DropDownList element updates properly.
UPDATED CODE:
//OrderDetailsModel:
[DisplayName("Order Status"), EnsureOrderStatus("ID")]
public OrderStatus OrderStatus { get; set; }
public List<OrderStatusModel> OrderStatusModels = new List<OrderStatusModel>();
public OrderDetailsModel()
{
OrderStatusModels.Add(new OrderStatusModel { EnumStatus = OrderStatus.Active, StringStatus = "Active" });
OrderStatusModels.Add(new OrderStatusModel { EnumStatus = OrderStatus.Completed, StringStatus = "Completed" });
OrderStatusModels.Add(new OrderStatusModel { EnumStatus = OrderStatus.Future, StringStatus = "Future" });
}
<%= Html.DropDownListFor(model => model.OrderStatus, new SelectList(Model.OrderStatusModels, "StringStatus", "EnumStatus"))%>
I would rethink this.. personally. Trying to hack an enum into a SelectList will create more work than is required.
Simply, wrap this all in a ViewModel.. consider this:
public enum OrderStatus {
NOT_SHIPPED,
SHIPPED
}
public class ViewModel {
public OrderStatus SelectedStatus { get; set; }
public List<StatusViewModel> Models = new List<StatusViewModel>();
}
public class StatusViewModel {
public string StringStatus { get; set; }
public OrderStatus EnumStatus { get; set; }
}
Setup (perhaps in the controller.. or somewhere else) involves this:
ViewModel model = new ViewModel();
model.Models.Add(new StatusViewModel() { EnumStatus = OrderStatus.NOT_SHIPPED, StringStatus = "Not shipped" });
model.Models.Add(new StatusViewModel() { EnumStatus = OrderStatus.SHIPPED, StringStatus = "Shipped" });
return View(model);
..and your view is simply this:
#model Models.ViewModel
#Html.DropDownListFor(x => x.SelectedStatus, new SelectList(Model.Models, "StringStatus", "EnumStatus"))
Then, when your model comes in after being posted, the SelectedStatus is strongly typed OrderStatus enum:
public ActionResult Index(ViewModel model) {
// model.SelectedStatus is an OrderStatus
}
What is the opposite/negate of [Compare(" ")] data annotation" in ASP.NET?
i.e: two properties must hold different values.
public string UserName { get; set; }
[Something["UserName"]]
public string Password { get; set; }
You can use the [NotEqualTo] data annotation operator included in MVC Foolproof Validation. I used it right now and it works great!
MVC Foolproof is an open source library created by #nick-riggs and has a lot of available validators. Besides doing server side validation it also does client side unobtrusive validation.
Full list of built in validators you get out of the box:
Included Operator Validators
[Is]
[EqualTo]
[NotEqualTo]
[GreaterThan]
[LessThan]
[GreaterThanOrEqualTo]
[LessThanOrEqualTo]
Included Required Validators
[RequiredIf]
[RequiredIfNot]
[RequiredIfTrue]
[RequiredIfFalse]
[RequiredIfEmpty]
[RequiredIfNotEmpty]
[RequiredIfRegExMatch]
[RequiredIfNotRegExMatch]
This is the implementation (server side) of the link that #Sverker84 referred to.
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class UnlikeAttribute : ValidationAttribute
{
private const string DefaultErrorMessage = "The value of {0} cannot be the same as the value of the {1}.";
public string OtherProperty { get; private set; }
public UnlikeAttribute(string otherProperty)
: base(DefaultErrorMessage)
{
if (string.IsNullOrEmpty(otherProperty))
{
throw new ArgumentNullException("otherProperty");
}
OtherProperty = otherProperty;
}
public override string FormatErrorMessage(string name)
{
return string.Format(ErrorMessageString, name, OtherProperty);
}
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
if (value != null)
{
var otherProperty = validationContext.ObjectInstance.GetType()
.GetProperty(OtherProperty);
var otherPropertyValue = otherProperty
.GetValue(validationContext.ObjectInstance, null);
if (value.Equals(otherPropertyValue))
{
return new ValidationResult(
FormatErrorMessage(validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
}
Usage:
public string UserName { get; set; }
[Unlike("UserName")]
public string AlternateId { get; set; }
Details about this implementation, and how to implement it client-side can be found here:
http://www.devtrends.co.uk/blog/the-complete-guide-to-validation-in-asp.net-mvc-3-part-2
http://www.macaalay.com/2014/02/25/unobtrusive-client-and-server-side-not-equal-to-validation-in-mvc-using-custom-data-annotations/
The complete code for both server side and client side validation is as follows:
[AttributeUsage(AttributeTargets.Property)]
public class UnlikeAttribute : ValidationAttribute, IClientModelValidator
{
private string DependentProperty { get; }
public UnlikeAttribute(string dependentProperty)
{
if (string.IsNullOrEmpty(dependentProperty))
{
throw new ArgumentNullException(nameof(dependentProperty));
}
DependentProperty = dependentProperty;
}
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
if (value != null)
{
var otherProperty = validationContext.ObjectInstance.GetType().GetProperty(DependentProperty);
var otherPropertyValue = otherProperty.GetValue(validationContext.ObjectInstance, null);
if (value.Equals(otherPropertyValue))
{
return new ValidationResult(ErrorMessage);
}
}
return ValidationResult.Success;
}
public void AddValidation(ClientModelValidationContext context)
{
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-unlike", ErrorMessage);
// Added the following code to account for the scenario where the object is deeper in the model's object hierarchy
var idAttribute = context.Attributes["id"];
var lastIndex = idAttribute.LastIndexOf('_');
var prefix = lastIndex > 0 ? idAttribute.Substring(0, lastIndex + 1) : string.Empty;
MergeAttribute(context.Attributes, "data-val-unlike-property", $"{prefix}{DependentProperty}");
}
private void MergeAttribute(IDictionary<string, string> attributes,
string key,
string value)
{
if (attributes.ContainsKey(key))
{
return;
}
attributes.Add(key, value);
}
}
Then include the following in JavaScript:
$.validator.addMethod('unlike',
function (value, element, params) {
var propertyValue = $(params[0]).val();
var dependentPropertyValue = $(params[1]).val();
return propertyValue !== dependentPropertyValue;
});
$.validator.unobtrusive.adapters.add('unlike',
['property'],
function (options) {
var element = $(options.form).find('#' + options.params['property'])[0];
options.rules['unlike'] = [element, options.element];
options.messages['unlike'] = options.message;
});
Usage is as follows:
public int FromId { get; set; }
[Unlike(nameof(FromId), ErrorMessage = "From ID and To ID cannot be the same")]
public int ToId { get; set; }
Use this in your get/set logic:
stringA.Equals(stringB) == false
In addition to solution given by #Eitan K, If you want to use other property's display name instead of other property's name, use this snippet:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class UnlikeAttribute : ValidationAttribute
{
private const string DefaultErrorMessage = "The value of {0} cannot be the same as the value of the {1}.";
public string OtherPropertyDisplayName { get; private set; }
public string OtherProperty { get; private set; }
public UnlikeAttribute(string otherProperty)
: base(DefaultErrorMessage)
{
if (string.IsNullOrEmpty(otherProperty))
{
throw new ArgumentNullException("otherProperty");
}
OtherProperty = otherProperty;
}
public override string FormatErrorMessage(string name)
{
return string.Format(ErrorMessageString, name, OtherPropertyDisplayName);
}
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
if (value != null)
{
var otherProperty = validationContext.ObjectInstance.GetType()
.GetProperty(OtherProperty);
var otherPropertyValue = otherProperty
.GetValue(validationContext.ObjectInstance, null);
if (value.Equals(otherPropertyValue))
{
OtherPropertyDisplayName = otherProperty.GetCustomAttribute<DisplayAttribute>().Name;
return new ValidationResult(
FormatErrorMessage(validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
}