I would like to use the built-in validation features as far as possible. I would also like to use the same model for CRUD methods.
However, as a drop down list cannot be done using the standard pattern, I have to validate it manually. In the post back method, I would like to just validate the drop down list and add this result to ModelState so that I don't have to validate all the other parameters which are done with Data Annotation. Is it possible to achieve this?
I may be mistaken about the drop down list, but from what I read, the Html object name for a drop down list cannot be the same as the property in the Model in order for the selected value to be set correctly. Is it still possible to use Data Annotation with this workaround?
Thanks.
You can use the addModelError
ModelState.AddModelError(key,message)
when you use that, it will invalidate the ModelState so isValid will return false.
Update
after seeing the comment to #Pieter's answer
If you want to exclude an element from affecting the isValid() result, you can use the ModelState.Remove(field) method before calling isValid().
Another option is to inherit IValidatableObject in your model. Implement its Validate method and you can leave all other validation in place and write whatever code you want in this method. Note: you return an empty IEnumerable<ValidationResult> to indicate there were no errors.
public class Class1 : IValidatableObject
{
public int val1 { get; set; }
public int val2 { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var errors = new List<ValidationResult>();
if (val1 < 0)
{
errors.Add(new ValidationResult("val1 can't be negative", new List<string> { "val2" }));
}
if (val2 < 0)
{
errors.Add(new ValidationResult("val2 can't be negative", new List<string> { "val2" }));
}
return errors;
}
}
EDIT: After re-reading the question I don't think this applicable to this case, but I'm leaving the answer here in case it helps someone else.
You cannot manually set the ModelState.IsValid property but you can add messages to the ModelState that will ensure that the IsValid is false.
ModelState.AddModelError();
yes, you can achieve this (also you will use the same model for CRUD methods) :
Example MODEL
public class User
{
public virtual int Id{ get; set; }
public virtual Role Role { get; set; }
}
public class Role
{
[Required(ErrorMessage = "Id Required.")]
public virtual int Id { get; set; }
[Required(ErrorMessage = "Name Required.")]
public virtual string Name { get; set; }
}
Example VIEW with validation on the dropdownlist
#Html.DropDownListFor(m => m.Role.Id, (SelectList)ViewBag.gRoles, "-- Select --")
#Html.ValidationMessageFor(m => m.Role.Id)
CONTROLLER: clearing the required (but not needed here) fields
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Creedit(User x)
{
x.Role = db.RoseSet.Find(x.Role.Id);
if (x.Role != null)
{
ModelState["Role.Name"].Errors.Clear();
}
if (ModelState.IsValid)
{
// proceed
}
else
{
// return validation error
}
}
Might be more recent methods, since this is an old post, but this might help future readers.
One can set a field to valid with this two methods:
ModelState.ClearValidationState("Password");
ModelState.MarkFieldValid("Password");
Need to use both because the second one without the first one it gives an error stating that the state is already marked.
To set a field to invalid, just use ModelState.AddModelError() method as already referred.
Related
I have a view that is using a model and I am using that information to create a form.
I have three steps of the form that are optional or may not be shown.
The problem is that these hidden sections get posted along with the form data and break the business logic. (I have no control over the business logic)
So is there a way to tell the framework not to pass certain sections or fields? Perhaps VIA a class or something?
I know I could use AJAX to send certain sections as they are needed, but the site spec is to have them hidden and displayed as needed.
Although you could do this client-side, it won't stop malicious over-posting/mass assignment.
I suggest reading 6 Ways To Avoid Mass Assignment in ASP.NET MVC.
Excerpts:
Specify Included Properties only:
[HttpPost]
public ViewResult Edit([Bind(Include = "FirstName")] User user)
{
// ...
}
Specify Excluded Properties only:
[HttpPost]
public ViewResult Edit([Bind(Exclude = "IsAdmin")] User user)
{
// ...
}
Use TryUpdateModel()
[HttpPost]
public ViewResult Edit()
{
var user = new User();
TryUpdateModel(user, includeProperties: new[] { "FirstName" });
// ...
}
Using an Interface
public interface IUserInputModel
{
string FirstName { get; set; }
}
public class User : IUserInputModel
{
public string FirstName { get; set; }
public bool IsAdmin { get; set; }
}
[HttpPost]
public ViewResult Edit()
{
var user = new User();
TryUpdateModel<IUserInputModel>(user);
// ...
}
Use the ReadOnlyAttribute
public class User
{
public string FirstName { get; set; }
[ReadOnly(true)]
public bool IsAdmin { get; set; }
}
Lastly, and the most recommended approach is to use a real ViewModel, instead a domain Model:
public class UserInputViewModel
{
public string FirstName { get; set; }
}
Show/Hide will not allow/disallow the value from being sent to the Controller.
Elements that are Disabled or just not editable will (99% of the time) be returned as null / minVal.
You can set the elements in the View as Disabled by using JQuery in the script:
$('#elementID').attr("disabled", true);
OR you could use a DOM command:
document.getElementById('elementID').disabled = "true";
So you can set the fields as both Disabled AND Hidden, so that it is neither displayed, nor populated. Then in your Controller you can just base the Business Logic on whether or not certain fields (preferable Mandatory fields, if you have any) are null.
You can check this in C# like this:
For a string:
if (string.IsNullOrWhiteSpace(Model.stringField))
{
ModelState.AddModelError("stringField", "This is an error.");
}
For a DateTime:
if (Model.dateTimeField == DateTime.MinValue)
{
ModelState.AddModelError("dateTimeField ", "This is an error.");
}
Just for interest sake, here is how you can Hide/Show elements on the View using JQuery:
$('#elementID').hide();
$('#elementID').show();
I have a list of Pair of radio buttons (Yes/No):
Q1.(Y)(N)
Q2.(Y)(N)
Q3.(Y)(N)
Q4.(Y)(N)
and I have one property in my model
public string MedicalExplanation { get; set; }
My goal is to make Explanation required if any of the radio button has been set to true.
My first try was to use [Required] but it does not handle conditions.
Then I decided to use third party tool like MVC Foolproof Validation
I used it like this:
[RequiredIf("Q1", true, ErrorMessage = "You must explain any \"Yes\" answers!")]
Now the problem is I don't know how to make it required if any of the other Q2, Q3, Q4 is checked.
Please advice
In your ViewModel, create a bool property like this:
public bool IsMedicalExplanationRequired
{
get
{
return Q1 || Q2 || Q3 || Q4;
}
}
Then, use your RequiredIf attribute like this:
[RequiredIf("IsMedicalExplanationRequired", true, ErrorMessage = "You must explain any \"Yes\" answers!")]
UPDATE:
If your Q1 - Q4 properties are of type bool?, just change the IsMedicalExplanationRequired property like below:
public bool IsMedicalExplanationRequired
{
get
{
return Q1.GetValueOrDefault() || Q2.GetValueOrDefault() || Q3.GetValueOrDefault() || Q4.GetValueOrDefault();
}
}
This is how I did it:
First I created a custom validation attribute which gets a string array of fields to check passed in:
public class ValidateAtLeastOneChecked : ValidationAttribute {
public string[] CheckBoxFields {get; set;}
public ValidateAtLeastOneChecked(string[] checkBoxFields) {
CheckBoxFields = checkBoxFields;
}
protected override ValidationResult IsValid(Object value, ValidationContext context) {
Object instance = context.ObjectInstance;
Type type = instance.GetType();
foreach(string s in CheckBoxFields) {
Object propertyValue = type.GetProperty(s).GetValue(instance, null);
if (bool.Parse(propertyValue.ToString())) {
return ValidationResult.Success;
}
}
return new ValidationResult(base.ErrorMessageString);
}
}
Then I use it like this (I am using resource files to localize my error messages):
[ValidateAtLeastOneChecked(new string[] { "Checkbox1", "Checkbox2", "Checkbox3", "Checkbox4" }, ErrorMessageResourceType=typeof(ErrorMessageResources),ErrorMessageResourceName="SelectAtLeastOneTopic")]
public bool Checkbox1{ get; set; }
public bool Checkbox2{ get; set; }
public bool Checkbox3{ get; set; }
public bool Checkbox4{ get; set; }
It is only actually setting the error on the first checkbox. If you are using the built in css highlighting to highlight fields in error you will need to modify this slightly to make it look right, but I felt this was a clean solution which was reusable and allowed me to take advantage of the support for resource files in validation attributes.
How do you validate a class using Validation attributes when validating strongly typed view models.
Suppose you have a view model like so:
[PropertiesMustMatch("Admin.Password", "Admin.ConfirmPassword")]
public class AdminsEditViewModel
{
public AdminsEditViewModel()
{
this.Admin = new Admin(); // this is an Admin class
}
public IEnumerable<SelectListItem> SelectAdminsInGroup { get; set; }
public IEnumerable<SelectListItem> SelectAdminsNotInGroup { get; set; }
public Admin Admin { get; set; }
}
I get null exception when on this line of PropertiesMustMatchAttribute
object originalValue = properties.Find(OriginalProperty, true /* ignoreCase */).GetValue(value);
since Password field is a property of Admin class and NOT AdminsEditViewModel. How do I make it so that it will go so many levels deep until it does find property of Admin in the ViewModel AdminsEditViewModel?
thanks
You need to modify the PropertiesMustMatchAttribute class to parse the property name and search deeply.
This attribute is not part of the framework; it's included in the default MVC template (in AccountModels.cs)
You can therefore modify it to suit your needs.
Specifically, you would call name.Split('.'), then loop through splitted names and get the property values.
It would look something like
object GetValue(object obj, string properties) {
foreach(strong prop in properties)
obj = TypeDescriptor.GetProperties(obj)
.Find(prop, ignoreCase: true)
.GetValue(obj);
}
return obj;
}
I have a view model with 2 properties that are optional - ie - not required. The view uses dropdownlistfor() to get values for these two fields, an includes an optionlabel of "" for the blank value.
When posted back to the create action the ModelState has an error for both of these fields saying "A value is required".
Anyone got any clue if this is a bug or a stupid user (ie, me) error?
Thanks
Udpate:
The View Model looks like this:
[DisplayName("Check Digit Type")]
public VMBarcodeMaskCheckDigitType BarcodeMaskCheckDigitType
{
get;
set;
}
[DisplayName("Mask Type")]
[Required(ErrorMessage="Mask type is required")]
public VMBarcodeMaskType BarcodeMaskType
{
get;
set;
}
[DisplayName("Product")]
public VMProduct Product
{
get;
set;
}
The binding in the controller is :
public ActionResult Create()
{
BarcodeMaskViewModel model = new BarcodeMaskViewModel(new VMBarcodeMask(), Domain.GetBarcodeMaskTypes(), Domain.GetBarcodeCheckDigitTypes(), Domain.GetProducts());
return View(model);
}
//
// POST: /Barcode/Create
[HttpPost]
public ActionResult Create(BarcodeMaskViewModel model)
{
try
{
if (ModelState.IsValid)
{
...
}
}
catch (Exception ex)
{
ModelState.AddModelError("*", ex);
}
return View(new BarcodeMaskViewModel(model.BarcodeMask, Domain.GetBarcodeMaskTypes(), Domain.GetBarcodeCheckDigitTypes(), Domain.GetProducts()));
}
I've had this problem too, and I discovered it was actually nothing to do with the optional fields.
It was because I had an auto-generating primary key column for the entity, called 'Id'. MVC2 automatically checked for a value for this, and obviously there wasn't one as it was being auto-generated.
There's an easy way to fix this is to rename the column to BarcodeId etc, rather than just Id. I gave a better explanation here: http://www.ediblecode.com/post/A-value-is-required-with-ASPNET-MVC-2.aspx
That explanation is all assuming you're using LINQ...
Just use Bind(Exclude="Id") before the first parameter of your Create action.
I think this a confirmed bug. See here: http://forums.asp.net/p/1529205/3699143.aspx
On the assumption that I have Entity with couple of fields. Some fields are required at some specific state but others only on further/other state.
public class Entity
{
//Required always
public SomeReference {}
//Required in specific situation/scenario
public OtherReference {}
}
How to achieve that scenario with some known validation framework or how to do it by my self?
For help:
Udi Dahan has some thoughts on this.
http://www.udidahan.com/2007/04/30/generic-validation/
I have a solution that I am using at the moment. I use Fluent validation and am still getting used to it. I can give you an example of a simple scenario I have. maybe it helps. I have a user class, with a address Object property. At some point, I want to only validate the User details(name, email, password, etc) and at another state I want to validate the user address(first line, postcode, etc).
Classes look like this:
public class User {
public virtual string Name { get; set; }
public virtual string Email { get; set; }
public virtual string Password { get; set; }
public virtual Address Address { get; set; }
}
public class Address {
public virtual string Address1 { get; set; }
public virtual string PostCode { get; set; }
}
I then have two (simplfied) validators, one for an address and one for a user:
public AddressValidator() {
RuleFor(address => address.Address1)
.NotNull()
.WithMessage("Please enter the first line of your address");
RuleFor(address => address.PostCode)
.NotNull()
.WithMessage("Please enter your postcode")
.Matches(UK_POSTCODE_REGEX)
.WithMessage("Please enter a valid post code!");
}
public UserValidator() {
RuleFor(user => user.FirstName)
.NotNull()
.WithMessage("Please provide a first name")
.Length(3, 50)
.WithMessage("First name too short");
RuleFor(user=> user.Password)
.Length(8, 50)
.WithMessage("Password is too short");
}
I then create a Model Validator, so for example, say we have a form where the user enters an address, we create a AddressModelValidator, and can re-use the validators we have written:
public AddressModelValidator() {
RuleFor(user => user.id)
.NotNull()
.WithMessage("An error has occured, please go back and try again");
RuleFor(user => user.Address).SetValidator(new AddressValidator());
}
So, with some thought, you can really create some nice models, and reduce your validation code duplication!
My preferernce is to localize common validation functions such as email and date validations into a ValidationService class that I can pass my object into. For the rest though I tend to put the validation into the class itself. If I am using LINQ to SQL then I can create a Validate() method on my object which LINQ to SQL will call prior to saving the object to the db like this:
public void Validate()
{
if(!IsValid)
throw new ValidationException("Rule violations prevent saving");
}
public bool IsValid
{
get { return GetRuleViolations().Count() == 0;}
}
public IEnumerable<RuleViolation> GetRuleViolations()
{
if(this.TermID == 0)
yield return new RuleViolation(HelpMessageService.GetHelpMessageBodyByID(1), "agreeWithTerms");
if(ValidationService.ValidateDate(this.Birthdate.ToString()))
yield return new RuleViolation(HelpMessageService.GetHelpMessageBodyByID(2), "birthDate");
if (!(Username.Length >= ConfigurationService.GetMinimumUsernameLength()) ||
!(Username.Length <= ConfigurationService.GetMaximumUsernameLength()))
yield return new RuleViolation(HelpMessageService.GetHelpMessageBodyByID(5), "username");
if(ValidationService.ValidateUsernameComplexity(Username))
yield return new RuleViolation(HelpMessageService.GetHelpMessageBodyByID(6), "username");
if (AccountID == 0 && ObjectFactory.GetInstance<IAccountRepository>().UsernameExists(this.Username))
yield return new RuleViolation(HelpMessageService.GetHelpMessageBodyByID(7), "username");
if (!ValidationService.ValidateEmail(Email))
yield return new RuleViolation(HelpMessageService.GetHelpMessageBodyByID(8), "email");
if (AccountID == 0 && ObjectFactory.GetInstance<IAccountRepository>().EmailExists(this.Email))
yield return new RuleViolation(HelpMessageService.GetHelpMessageBodyByID(9), "email");
yield break;
}
Read here for a full understanding of this: http://nerddinnerbook.s3.amazonaws.com/Part3.htm