I use Visual Studio 2013, Update 5
with the ASP.NET MVC Project Template I created a simple Web to get deeper into MVC after taking a few steps.
I added a Model for which i creaded a standard Edit View and Controller.
In my Model I use "Required" and MinLength Attributes.
The MinLength Attibutes raised Validation Messages but if the Fields are kept empty the "Required" Attributes doesn't work.
What works is, if I just put one Character in the field, and leave the field (so that the MinLenth Validation Fails) and afterwards Clear the complete field.
Only in this Case, the Required Attribute seems to do anythings.
(Bug or Feature?! :-) )
Here is the Model I use with the Edit View completely generated by "new View" Template of Visual Studio
public class Apotheke
{
[DisplayName("Apotheken Nr.")]
[DisplayFormat(DataFormatString = "{0:D4}")]
[Range(1, 9999)]
[Required(AllowEmptyStrings = false)]
public int ApothekenNr { get; set; }
[DisplayName("Name der Apotheke")]
[MinLength(3)]
[Required(AllowEmptyStrings = true)]
public string ApoName { get; set; }
[MinLength(3)]
[Required(AllowEmptyStrings = false)]
public string Straße { get; set; }
[MinLength(5)]
[MaxLength(5)]
[Required(AllowEmptyStrings = false)]
public string PLZ { get; set; }
[MinLength(3)]
[Required(AllowEmptyStrings = false)]
public string Ort { get; set; }
[DisplayName("Inhaber Vorname")]
[MinLength(3)]
[Required(AllowEmptyStrings = false)]
public string Vorname { get; set; }
[DisplayName("Inhaber Nachname")]
[MinLength(3)]
[Required(AllowEmptyStrings = false)]
public string Nachname { get; set; }
[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
[MinLength(10)]
[MaxLength(10)]
[Required(AllowEmptyStrings = false)]
public DateTime Eintritt { get; set; }
}
AllowEmptyStrings = false raises a validation error when user enters blank spaces. It is working fine in below framework. Please try validating object in controller to make sure your annotations are correct. Check ModelState errors.
To test your issue I am using below packages on .NET 4.5 (VS 2013, MVC 5):
"EntityFramework" version="6.1.3"
"jQuery" version="2.1.4"
"jQuery.Validation" version="1.11.1"
public ActionResult Edit(string id)
{
...
returningModel.PLZ = " ";
//returningModel.PLZ = null;
bool b = TryValidateModel(returningModel);
var modelStateErrors = ModelState.Values.SelectMany(m => m.Errors);
return View(returningModel);
}
Related
I've got a view which needs several models to work correctly. So, I created a model which is a collection of multiple (sub) models. This is the model.
public class PolicyDetail
{
public Policy Policy { get; set; }
public IEnumerable<Insured> Insureds { get; set; }
public IEnumerable<Risk> Risks { get; set; }
public IEnumerable<Construction> Constructions { get; set; }
}
And here's an example of what one of the sub models look like, which is an actual entity from the database:
public class Policy
{
[Key]
public int PolicyID { get; set; }
[DisplayName("Policy Number")]
public Guid PolicyNumber { get; set; }
[Required(ErrorMessage = "Please enter a valid Effective Date.")]
[DataType(DataType.DateTime)]
[DisplayName("Effective Date")]
[DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}", ApplyFormatInEditMode = true)]
public DateTime EffDate { get; set; }
[Required(ErrorMessage = "Please enter a valid Expiration Date.")]
[DataType(DataType.DateTime)]
[DisplayName("Expiration Date")]
[DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}", ApplyFormatInEditMode = true)]
public DateTime ExpDate { get; set; }
public Boolean IsActive { get; set; }
}
This was all working well, right up until I tried to submit a form with errors in it to test the validation. I should have seen this coming (maybe?) but because the actual model doesn't have any validation tags on it, it always passes the if (ModelState.IsValid) check. Is there some way to enforce, or inherit, all of the Data Annotations from the sub classes?
Or, am I going about this all wrong, using a model which is a collection of other models? The thing is, I want to be able to edit/add multiple db entities from the same view.
EDIT:
This article by Josh Carroll looks to be EXACTLY what I need. But when I implement it, I get a Null Object error. Here's what I'm doing:
public class PolicyDetail
{
[Required, ValidateObject]
public Policy Policy { get; set; }
public IEnumerable<Insured> Insureds { get; set; }
public IEnumerable<Risk> Risks { get; set; }
public IEnumerable<Construction> Constructions { get; set; }
}
Then in the override method he provides:
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var results = new List<ValidationResult>();
var context = new ValidationContext(value, null, null);
Validator.TryValidateObject(value, context, results, true);
if (results.Count != 0)
{
var compositeResults = new CompositeValidationResult(String.Format("Validation for {0} failed!", validationContext.DisplayName));
results.ForEach(compositeResults.AddResult);
return compositeResults;
}
return ValidationResult.Success;
}
}
the parameter "value" comes in null, so it errors on this line:
Validator.TryValidateObject(value, context, results, true);
Am I missing something? Doing something wrong?
You can manually call the validations on the sub-models using this: https://msdn.microsoft.com/en-us/library/dd411772.aspx
var context = new ValidationContext(model.Policy, serviceProvider: null, items: null);
var validationResults = new List<ValidationResult>();
bool isValid = Validator.TryValidateObject(model.Policy, context, validationResults, true);
You can then use the ModelState.AddModelError to build the response from that.
Definitely not the most elegant possible solution, but might be easier than rewriting what you have.
I'm having a problem I can't seem to figure out here and I've done a fair amount of searching:
In my model (a list of objects) I have the following output:
<input data-val="true" data-val-number="Please enter a number." data-val-required="The ModelID field is required." id="Students_19__ModelID" name="Students[19].ModelID" type="hidden" value="62">
If you look closely here this value is 62 (I've bound a second object just to check myself and I see that it is also 62:
<input data-val="true" data-val-number="Please enter a number." id="Students_19__storage_ID" name="Students[19].storage.ID" type="hidden" value="62">
In the same view I'm seeing this:
#Html.LabelFor(_ => Model.Students[i].Show, "ID: " + Model.Students[i].storage.ID)
producing:
ID: 63
In summary:
#Html.HiddenFor(_ => Model.Students[i].storage.ID)
#Html.LabelFor(_ => Model.Students[i].Show, "ID: " + Model.Students[i].storage.ID)
OR
#Html.HiddenFor(m => m.Students[i].storage.ID)
#Html.LabelFor(m => m.Students[i].Show, "ID: " + Model.Students[i].storage.ID)
produces->
<input data-val="true" data-val-number="Please enter a number." id="Students_19__storage_ID" name="Students[19].storage.ID" type="hidden" value="62">
<label for="Students_19__Show">ID: 63</label>
Obviously 62 != 63 and this is throwing off the model in the controllers. Does anyone have an idea of what could be causing this? I feel like something is being converted poorly but I'm out of ideas at this point. I've seen this behavior before and shuffling around the loading order of the hidden fields has fixed it but this obviously isn't a good solution.
EDIT: Per comments:
The structure of the object is pretty tame: (this is the "storage" variable)
public class EducationGoalModel
{
public int? ID { get; set; }
public decimal Amount { get; set; }
public string Name { get; set; }
public string Category { get; set; }
//Education
public int StudentAge { get; set; }
public int StudentBegin { get; set; }
public int StudentYears { get; set; }
[DisplayFormat(DataFormatString = "{0:P2}", ApplyFormatInEditMode = true)]
[Percentage] //Custom annotation, nothing silly here
[Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(Resources.Errors))] //Error is not triggered otherwise the ID wouldn't work in the "workaround"
public decimal Inflation { get; set; }
// This is just used by another module that needs annotations waaaay down the line, these values are unused.
public decimal TargetAmount { get { return Calculations.FutureValue(this.YearOfCompletion - DateTime.Now.Year, this.Inflation, this.Amount); } }
public byte RiskToleranceLevel { get; private set; }
public int YearOfCompletion { get; set; }
public bool Hide { get; set; }
public EducationGoalModel()
{
this.Category = FinanceGoalTargetCategory.EDUCATIONAL;
}
public EducationGoalModel(EducationGoal goal)
{
//The goal is just the entity I'm using, this is a light wrapper around it.
ID = goal.ID;
Amount = goal.Amount;
Name = goal.Name;
Category = goal.Category;
StudentAge = goal.StudentAge;
StudentBegin = goal.StudentBegin;
StudentYears = goal.StudentYears;
Inflation = goal.Inflation;
Hide = goal.Hide;
}
public void CopyTo(EducationGoal goal)
{ //SNIP.... we don't really care about this method, it isn't called
}
Here's the view Model:
public class EducationGoalInputModel : InputModelBase
{
public CurrencySliderModel AmountSlider { get; set; }
public EducationGoalModel storage { get; set; }
public int ModelID { get; set; }
public int StudentAge { get; set; }
public int StudentBegin { get; set; }
public int StudentYears { get; set; }
[MaxLength(20, ErrorMessageResourceName = "FinancialGoalsNameLength", ErrorMessageResourceType = typeof(Resources.Errors))]
public string Name { get; set; }
[DisplayFormat(DataFormatString = "{0:P2}", ApplyFormatInEditMode = true)]
[Percentage]
[Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(Resources.Errors))]
public decimal Inflation { get; set; }
... plain constructor (no args), a second to construct and establish the "storage" object which is the one used for the above output.
Here's the base class, nothing interesting here really:
public abstract class InputModelBase { //a simple class that has
public abstract void UpdateAmounts(int age);
//and a couple other utility methods for the user interface (we support IE8 so a lot of UI information is stored)
}
EDIT: Per Hacks: because this is MVC (this works fine, but is terrible for maintenance and indicates a bigger problem)
<input type="hidden" name="#String.Format("Students[{0}].storage.ID", i)" id="#String.Format("Students_{0}__storage_ID", i)" value="#Model.Students[i].storage.ID" />
This outputs the correct values of value="63" using the example above.
EDIT #2
Based on feedback from the comments below *(thank you!) it looks like the ModelState doesn't agree with the Model. There's nothing obvious modifying the ModelState in the View that I can find but I've inherited this project and there's probably some extension method doing something that I need to track down. The values in this particular test 5. The ModelState shows the wrong value and thus the binding is wrong. This has not fixed the issue but is basically an accepted answer as I now know where to look.
Thanks for your help.
I have a class that is called MyMethodQuery that contains a property of entry of my web method
[DataContract()]
public class MyMethodQuery
{
[DataMember(IsRequired = true)]
[StringLength(100, MinimumLength = 10)]
public string Id{ get; set; }
}
My Web Method :
public string MyMethod(MyMethodQuery MyMethodQuery)
{
return "it's ok !";
}
I want to throw an exception if I leave the empty property. But it doesn't work in spite of the attribute StringLength.
I test my method with SoapUI.
These attributes are not supposed to work in this context "out of the box". Validator class is the entry point to the world of validation, and you will have to invoke it manually.
See this answer for an example.
[DataContract()]
public class MyMethodQuery: IValidatableObject
{
[DataMember(IsRequired = true, EmitDefaultValue = false)]
[StringLength(500, MinimumLength = 5)]
public string Id { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Id.Length < 1)
{
yield return new ValidationResult("error");
}
}
}
But i have not error return if Id is empty
I'm using Fluent Validation v5.5 with ASP.NET v5.2.2 and I'm getting some inconsistent results with the validation.
My view model is:
public class QuoteViewModel
{
[Display(Name = #"Day")]
public int DateOfBirthDay { get; set; }
[Display(Name = #"Month")]
public int DateOfBirthMonth { get; set; }
[Display(Name = #"Year")]
public int DateOfBirthYear { get; set; }
[Display(Name = #"Gender")]
public Gender? Gender { get; set; }
[Display(Name = #"State")]
public int StateId { get; set; }
}
My controller method is:
public ActionResult Quote(QuoteViewModel viewModel)
{
var _validator = new QuoteValidator();
var results = _validator.Validate(viewModel);
if (!ModelState.IsValid)
{
return Json(false);
}
return Json(true);
}
My validator is:
public class QuoteValidator : AbstractValidator<QuoteViewModel>
{
public QuoteValidator()
{
RuleFor(x => x.Gender).NotEmpty();
RuleFor(x => x.StateId).NotEmpty();
RuleFor(x => x.DateOfBirthDay).NotEmpty().InclusiveBetween(1, 31);
RuleFor(x => x.DateOfBirthMonth).NotEmpty().InclusiveBetween(1, 12);
RuleFor(x => x.DateOfBirthYear).NotEmpty().LessThanOrEqualTo(DateTime.UtcNow.Year);
}
}
I'm running a test that posts all blank value form fields. Thus the view model fields retain default values after the view model object is created.
For comparison, in the controller I'm running the validation explicitly and the results aren't consistent with the validation result in ModelState.
ModelState is showing 4 errors, all triggered by NotEmpty rules. NotEmpty on the nullable enum Gender doesn't seem to trigger.
The explicit validation is returning 7 out of 8 errors, the LessThanOrEqualTo rule won't fire since the DateOfBirthYear defaults to zero.
My pain point is I can't figure out why ModelState is missing the NotEmpty error on the nullable enum Gender.
The only way I've been able to trigger that error is to post just the Gender value.
Please help.
EDIT:
After stepping through some code, it appears that the issue is related to the Fluent Validation RequiredFluentValidationPropertyValidator. The Gender field is a nullable value type which is bound to null. The following snippet from RequiredFluentValidationPropertyValidator prevents validation:
ShouldValidate = isNonNullableValueType && nullWasSpecified;
!ModelState.IsValid doesn't use your validation result it uses defaulf MVC validation (that can be added through DataAnnotations). You have to check !results.IsValid instead which contains the validation result of your QuoteValidator.
If you want to use default ModelState.IsValid you have to mark your model with validator attribute:
[Validator(typeof(QuoteValidator))]
public class QuoteViewModel
{
[Display(Name = #"Day")]
public int DateOfBirthDay { get; set; }
[Display(Name = #"Month")]
public int DateOfBirthMonth { get; set; }
[Display(Name = #"Year")]
public int DateOfBirthYear { get; set; }
[Display(Name = #"Gender")]
public Gender? Gender { get; set; }
[Display(Name = #"State")]
public int StateId { get; set; }
}
And add the following line to your Application_Start method:
protected void Application_Start() {
FluentValidationModelValidatorProvider.Configure();
}
I am trying to develop an application using MVC 5 and EF 6 code first approach.
I am new at software devolpment.
I would like to add this data annotation:
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
to a property named Birthdate from a ViewModels
An Example to illustrate:
public class StudentRegisterViewModel
{
public string LastName { get; set; }
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }
}
The problem here is that the field EnrollmentDate does not recognize DisplayFormat annotations.
Can anyone help me, to handle it, please?
The [DisplayFormat] attribute is only used by DisplayFor and EditorFor. And not by the other helpers like TextBoxFor.
To format it further, you can create a css class and use jQuery to apply it:
$("EnrollmentDate").addClass("class-name")
To apply the format specified in [DisplayFormat] you need to access the value via Html.DisplayFor(). For example, in a view that would be:
#Html.DisplayFor(vm => vm.EnrollmentDate)
Write your own ValidationAttribute. To do this you override the IsValid() method with your own logic.
Add more validation if you wish:
public class MyAmazingValidationClass : ValidationAttribute
{
protected override bool IsValid(object value)
{
DateTime date;
bool parsed = DateTime.TryParse((string)value, out date);
//or maybe :
bool parsed = DateTime.ParseExact((string)value),"dd/MM/yyyy")
if(!parsed)
return false;
return true;
}
}
Then go to your awesome class and decorate it:
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[MyAmazingValidationClass(ErrorMessage="You made a big mistake right now!")]
public DateTime EnrollmentDate { get; set; }