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.
Related
In this example, form 1 and 2 use the same data model (User).
In form 1 all fields are mandatory.
In form 2, all fields are mandatory except the Name.
I would like to know how I can manually modify the validation of the Name field in this second form to suit this rule.
User.cs
public class User
{
[Required]
public string Name { get; set; }
[Required]
public string Cpf { get; set; }
[Required]
public string Rg { get; set; }
[Required]
public string Phone { get; set; }
}
Page.razor
//Form 1
<EditForm Model="user">
...
</EditForm>
//Form 2
<EditForm Model="user">
...
</EditForm>
#code {
User user = new User();
}
Well since the attributes are kinda coupled with your properties one way is to have an abstract User Class containing all the properties except Name.
Then extend this class with 2 child classes one with a Required Name the other with an optional Name.
Another way is to implement your own custom requiredIF attribute.
See this example and you can customize it adding some context to your form.
RequiredIf Conditional Validation Attribute
Solution 1: In Form 2 you can use OnSubmit instead of OnValidSubmit so the validation won't stop you. And in the method you passed to OnSubmit you can do the validation yourself.
FormExample:
#(canSendData ? "Sent" : "Not sent")
#if (User is not null)
{
<EditForm Model="User" OnSubmit="Submit">
<DataAnnotationsValidator />
<label>Phone</label>
<InputText #bind-Value="User.Phone" />
<ValidationMessage For="#(() => User.Phone)" />
<label>Name</label>
<InputText #bind-Value="User.Name" />
<ValidationMessage For="#(() => User.Name)" />
<button class="btn btn-success" type="submit">Save</button>
</EditForm>
}
#code {
private bool canSendData;
[Parameter]
public User User { get; set; } = null!;
private void Submit(EditContext editContext)
{
var phoneFieldIdentifier = editContext.Field("Phone");
var nameFieldIdentifier = editContext.Field("Name");
editContext.NotifyFieldChanged(phoneFieldIdentifier);
var validationMessagesCount = editContext.GetValidationMessages().Count();
if (validationMessagesCount == 0)
{// every field is valid
canSendData = true;
StateHasChanged();
}
else if (validationMessagesCount == editContext.GetValidationMessages(nameFieldIdentifier).Count())
{// every field is valid except the field for the `Name` property, but we dont care about it
canSendData = true;
StateHasChanged();
}
else
{// there is/are some invalid field/s that we care about
canSendData = false;
StateHasChanged();
}
}
}
I tried it and it works- it validates and even shows validation messages!
Some links that provided info: Binding a form (docs) and this answer.
I would say that this solution is easy and fast to implement, but it has a downside... Let's say you fill the name field, click Save, but some field was invalid so it didn't send the data and showed the validation message... but before you click Save again, you (for some reason) decide that you don't want the name field to be filled anymore, so you delete its content, now you click Save and the problem has arrived... The validation message for Name property shows. I don't know why but it does... On the other hand, even though the validation message shows the form will be saved. So it seems everything works "properly", BUT for some reason in this scenario the name field validation message is shown.
Solution 2: This another solution is more difficult to implement, but in my opinion it might be the most proper way how to do this- implementation of the custom validator. More here.
Bonus: While searching for info I came across this interesting blog post. It looks exactly like what you need but it says:
"If you use FluentValidation in a commercial project, please sponsor the project financially."
You need to separate out your edit context from your base record.
The user record:
public class User
{
public string Name { get; set; } = String.Empty;
public string Cpf { get; set; } = String.Empty;
public string Rg { get; set; } = String.Empty;
public string Phone { get; set; } = String.Empty;
}
And then two edit model classes that are used by the edit forms:
public class UserEditModel1
{
[Required]
public string Name { get; set; } = String.Empty;
[Required]
public string Cpf { get; set; } = String.Empty;
[Required]
public string Rg { get; set; } = String.Empty;
[Required]
public string Phone { get; set; } = String.Empty;
public UserEditModel1(User user)
{
this.Name = user.Name;
this.Cpf = user.Cpf;
this.Rg = user.Rg;
this.Phone = user.Phone;
}
public User User => new User
{
Name = this.Name,
Cpf = this.Cpf,
Rg = this.Rg,
Phone = this.Phone
};
}
public class UserEditModel2
{
public string Name { get; set; } = String.Empty;
[Required]
public string Cpf { get; set; } = String.Empty;
[Required]
public string Rg { get; set; } = String.Empty;
[Required]
public string Phone { get; set; } = String.Empty;
public UserEditModel2(User user)
{
this.Name = user.Name;
this.Cpf = user.Cpf;
this.Rg = user.Rg;
this.Phone = user.Phone;
}
public User User => new User
{
Name = this.Name,
Cpf = this.Cpf,
Rg = this.Rg,
Phone = this.Phone
};
}
Comment: My User class would be a record rather that a class which I use to check edit state.
I have the following class which is being used as an input model for an EditForm in a Blazor server side application.
public class KundeInput
{
[ValidateComplexType]
public List<AnsprechpartnerInput> Ansprechpartner { get; } = new List<AnsprechpartnerInput>();
public string? Kundennummer { get; }
[Required]
[MaxLength(60)]
public string Firma { get; set; } = String.Empty;
[MaxLength(60)]
public string? Name2 { get; set; }
[MaxLength(60)]
public string? Name3 { get; set; }
}
As you can see, my model contains a list of another model called AnsprechpartnerInput. Here is this model:
public class AnsprechpartnerInput
{
public string? Kundennummer { get; set; }
public int Nummer { get; } = -1;
[MaxLength(60)]
[Required]
public string Vorname { get; set; } = String.Empty;
[MaxLength(60)]
[Required]
public string Nachname { get; set; } = String.Empty;
[MaxLength(40)]
[Required]
public string? Bereich { get; set; }
/ * More properties */
}
The validation works fine. However, once I have multiple invalid AnsprechpartnerInput models in my list, the ValidationSummary becomes a mess. Because it displays e.g. 5 times field xyz is invalid.
I know I can set a custom message with the ErrorMessage property but I am not able to use other attributes from my model in this message.
What I want to achive is this:
[Required(ErrorMessage = $"Vorname of {Kundennummer} is required")]
public string Vorname { get; set; } = String.Empty;
I already tried to change the message with reflection but accoridng to Microsoft this way is not recommend or supported
https://github.com/dotnet/aspnetcore/issues/25611
Is there any way to get it to work? I thought of string replacement but I am not sure how I can figure out the right model for my ValidationMessage.
Also is there any way to validate the items of the list by one and get a boolean result? Let's say I want to achive this:
#foreach (var ansprechpartner in Input.Ansprechpartner)
{
if (Input.SelectedAnsprechpartner is null)
Input.SelectedAnsprechpartner = ansprechpartner;
<a #onclick="() => Input.SelectedAnsprechpartner = ansprechpartner"
class="#GetNavListClass(Input.SelectedAnsprechpartner == ansprechpartner)"
id="list-ansprechpartner-tab-#(ansprechpartner.Nummer)"
data-toggle="list"
href="#list-ansprechpartner-#(ansprechpartner.Nummer)"
role="tab"
aria-controls="#(ansprechpartner.Nummer)">
#((MarkupString)(ansprechpartner.Nummer < 0 ? "<span class=\"font-weight-bold\">NEU</span>" : $"({ansprechpartner.Nummer})")) #ansprechpartner.Vorname #ansprechpartner.Nachname
</a>
// When the model ansprechpartner is invalid, I want to display an icon
}
Thanks for any help!
PS: Blazor rocks!
You should use a custom validation attribute where you can explicitly add any error message you want
public class KundennummerValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var model = (AnsprechpartnerInput)validationContext.ObjectInstance;
if(string.IsNullOrEmpty((string)value))
{
return new ValidationResult($"Vorname of {model.Kundennummer} is required", new[] { "Kundennummer" });
}
return ValidationResult.Success;
}
}
then use
[KundennummerValidation]
public string Vorname { get; set; } = String.Empty;
result :
Validation summary:
I want to do a single OR multiple answer quiz using radiobuttonfor and checkboxFor, but I cannot make it work. The problem with all example I see is that the Question model also register the SelectedAnswer, but in my case I want each possible answer to be selectable since some questions will have multiples answers, and thus the isSelected property is directly inside the Answer model.
Therefore, for questions with single answers, when I try to create my model using RadioButtonFor(m => m[question].Answers[answerToDisplayId].IsSelected), every answer is in its own group and is not unchecked when I check another answer from that question (basically it behave like a checkBoxFor)
What I currently have: The question model
public enum questionfield
{
Chaser, Beater, Seeker, Contact, Process, Other
};
public enum QuestionDifficulty
{
Basic, Advanced
};
public enum AnswerType
{
SingleAnswer, MultipleAnswer
}
public class Question
{
public int Id { get; set; }
[Required(ErrorMessage = "Question name not valid")]
public string Name { get; set; }
[Required]
public QuestionField Field { get; set; }
[Required]
public QuestionDifficulty Difficulty { get; set; }
[Required]
public bool IsVideo { get; set; }
public string VideoURL { get; set; }
[Required]
public string QuestionText { get; set; }
[Required]
public AnswerType AnswerType { get; set; }
[Required]
public List<Answer> Answers { get; set; }
[Required]
public String AnswerExplanation { get; set; }
Answer model :
public class Answer
{
public int Id { get; set; }
public String Answertext { get; set; }
public Boolean IsTrue { get; set; }
public Boolean IsSelected { get; set; }
}
The view :
<div>
<!-- For each Question, display a new div with the Title, the question code, the question text, the video if there is one, then the possible answers depending on the type of answers-->
#using(Html.BeginForm("QuizzResult", "Home"))
{
for(int i = 0; i < Model.Count; i++)
{
<div class="QuizzQuestion">
<div class="QuizzQuestionTitle">#Model[i].Id : #Model[i].Name</div> #Html.HiddenFor(m => Model[i].Id)
<div class="QuizzQuestiontext">#Model[i].QuestionText</div>
#if(#Model[i].IsVideo)
{
<div class="QuizzQuestionVideoContainer">
<iframe class="QuizzQuestionVideo" id="ytplayer" type="text/html"
src="#Model[i].VideoURL"
frameborder="0"></iframe>
</div>
}
<div class="RadioButtonAnswers">
#if (#Model[i].AnswerType == QRefTrain3.Models.AnswerType.SingleAnswer)
{
for (int j = 0; j < Model[i].Answers.Count; j++)
{
#Model[i].Answers[j].Answertext #Html.RadioButtonFor(m => m[i].Answers[j].IsSelected, true)
#Html.HiddenFor(m => Model[i].Answers[j].IsTrue)
}
}
</div>
</div>
}
<input type="submit" value="Validate Answers"/>
}
</div>
The controller :
[HttpPost]
public ActionResult QuizzResult(List<Question> answers)
{
foreach(Question a in answers)
{
var b = Request.Form[a.Id.ToString()];
}
Result result = new Result();
foreach (Question q in answers)
{
result.QuestionsAskedIds.Add(q.Id);
if (Question.IsGoodAnswer(q))
{
result.GoodAnswersIds.Add(q.Id);
}
}
if (User.Identity.IsAuthenticated)
{
result.User = Dal.Instance.GetUserByName(HttpContext.User.Identity.Name);
Dal.Instance.CreateResult(result);
}
return View("QuizResult", result);
}
What would be the good way to do this? Thank you!
In case someone will see this :
The solution I found was to change the model : Instead of having one IsSelected parameter per answer, add a List selectedAnswers to your Question model. Then, in the view, add your radiobutton like this :
#Html.RadioButtonFor(m => m[i].SelectedAnswers, Model[i].Answers[j].Id)
You will store the id of each selected answer for this question ine the SelectedAnswers list. You can then create your results using this data.
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();
}
Now I was looking to make some validation for some checkbox fields in my model.
I want to create a unique rule that would require at least one checkbox to be true (or checked) in each category to make it valid. I have three different categories in this model.
I was told to approach this with enum as stated here.
I've looked into the situation and it seems a little over my head, because you basically utilize C# to customize your own rules.
Now these are the categories as mentioned in the hyperlink above:
//Disabilities
[Display(Name = "Learning Disabilities")]
public bool LD { get; set; }
[Display(Name = "Developmental Disabilities")]
public bool DD { get; set; }
[Display(Name = "AD/HD")]
public bool ADHD { get; set; }
[Display(Name = "Autism")]
public bool Autism { get; set; }
//Age Group
[Display(Name = "Child")]
public bool child { get; set; }
[Display(Name = "Youth")]
public bool youth { get; set; }
[Display(Name = "Adult")]
public bool adult { get; set; }
//Strategy Type
[Display(Name = "Academic")]
public bool academic { get; set; }
[Display(Name = "Behaviour")]
public bool behaviour { get; set; }
[Display(Name = "Communication")]
public bool communication { get; set; }
[Display(Name = "Social")]
public bool social { get; set; }
Now to approach this I was told to use enum:
public enum Age
{
[Display(Name="Child")
Child,
[Display(Name="Youth")
Youth,
[Display(Name="Adult")
Adult
}
^Do I throw this in the model still?
I know this goes into the model:
[Required]
public Age MyAge { get; set; }
After looking at several other examples I know that the above code is incomplete and I would also have to edit my view. As sad as it sounds, my education has not gone this far in programming so I apologize for my lack of understanding.
But if you could point me in the right direction so I can walk this golden brick road that would be much appreciated
Cheers.
Here is the small prototype I did for you with Enums and CheckBoxes and its validation.
Let your ENUM be -
public static class Data
{
public enum BloodGroup
{
[Description("A+")]
APositive,
[Description("B+")]
BPositive
}
}
Then construct your Enum model, which will hold the basic Checkbox properties -
public class EnumModel
{
public Data.BloodGroup BloodGroup { get; set; }
public bool IsSelected { get; set; }
}
Then construct Enum View Model based on Enum model, which basically have List of Enum Models -
public class EnumViewModel
{
public List<EnumModel> CheckBoxItems { get; set; }
}
Then your Controller Index Action, will construct EnumViewModel and will bind it to Index View -
public ActionResult Index()
{
EnumViewModel model = new EnumViewModel();
model.CheckBoxItems = new List<EnumModel>();
model.CheckBoxItems.Add(new EnumModel() { BloodGroup = Data.BloodGroup.APositive, IsSelected = false });
model.CheckBoxItems.Add(new EnumModel() { BloodGroup = Data.BloodGroup.BPositive, IsSelected = false });
return View(model);
}
Index View will display all the checkboxes and will make a POST to Submit action on click of submit button -
#model MVC.Controllers.EnumViewModel
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#Html.ValidationSummary();
#using (Html.BeginForm("Submit", "Enum", FormMethod.Post))
{
for (int i = 0; i < Model.CheckBoxItems.Count; i++)
{
#Html.LabelFor(m => m.CheckBoxItems[i].BloodGroup);
#Html.CheckBoxFor(m => m.CheckBoxItems[i].IsSelected);
#Html.HiddenFor(m => m.CheckBoxItems[i].BloodGroup);
}
<input type="submit" value="click"/>
}
In the Submit Action We check for the IsSelected properties of the Enum View Model, if there are none, then we return error to Index View.
public ActionResult Submit(EnumViewModel model)
{
if (!model.CheckBoxItems.Where(p => p.IsSelected).Any())
{
ModelState.AddModelError("CheckBoxList", "Please select atleast one!!!");
return View("Index",model);
}
return View();
}
Output -
On Load -
When we do not select anything and submit the form -