Is it possible to place conditions on a ViewModel where data equals a specific value in one field within the entity framework, the required element or entire object is removed from the ViewModel before using TryValidateModel on the ViewModel?
I would like to remove HomeValue and PurchasePrice from the Model validation where OwnOrRent (model.OwnOrRent) is not equal to 1 or 9.
Model.cs
public class Address
{
public int Id { get; set; }
[DisplayName("House Name or Number")]
[StringLength(50)]
public string HouseNameOrNumber { get; set; }
[DisplayName("Post Town")]
[StringLength(50)]
public string PostTown { get; set; }
[Required(ErrorMessage = "Own or Rent is Required")]
[DisplayName("Own or Rent")]
[StringLength(50)]
public string OwnOrRent { get; set; }
[Required(ErrorMessage = "Mortgage/Rent Amount is Required")]
[DisplayName("Mortgage/Rent Amount")]
[StringLength(50)]
public string MortgageRent { get; set; }
[Required(ErrorMessage = "Home Value is Required")]
[DisplayName("Home Value")]
[StringLength(50)]
public string HomeValue { get; set; }
[Required(ErrorMessage = "Purchase Price is Required")]
[DisplayName("Purchase Price")]
[StringLength(50)]
public string PurchasePrice { get; set; }
}
HomeController.cs
public ActionResult Application(int id)
{
var viewAddressModel = new Address();
using (
var dbEntities = new DbEntities(new EntityConnection(_df.DbEntities)))
{
var model = dbEntities.Applications.Single(e => e.Id == id);
viewAddressModel.Id = Id;
viewAddressModel.HouseNameOrNumber = model.HouseNameOrNumber;
viewAddressModel.PostTown = model.PostTown;
viewAddressModel.OwnOrRent = GetStatus(model.OwnOrRent);
viewAddressModel.MortgageRent = model.MortgageRent.ToString();
viewAddressModel.HomeValue = model.HomeValue;
viewAddressModel.PurchasePrice = model.PurchasePrice;
if (model.OwnOrRent != "1" || model.OwnOrRent != "9")
{
ModelState.Remove("HomeValue");
ModelState.Remove("PurchasePrice");
}
if (!TryValidateModel(viewAddressModel))
{
return PartialView("Address", viewAddressModel);
}
}
var vm = new ApplicationViewModel { Item = CreateApp(id) };
return PartialView("Application", vm);
}
As you can see I have tried to use ModelState.Remove but this has no effect.
Any assistance with this would be much appreciated?
Based on your comments you want to populate a model from the database, then validate it (because its old data which may not be valid), but not display errors for HomeValue or PurchasePrice based on the value of OwnOrRent, in which case you need to call TryValidateModel first, then remove ModelState errors
var viewAddressModel = new Address();
.... // set values
if (!TryValidateModel(viewAddressModel))
{
if (model.OwnOrRent != "1" || model.OwnOrRent != "9")
{
if (ModelState.ContainsKey("HomeValue"))
{
ModelState["HomeValue"].Errors.Clear();
}
if (ModelState.ContainsKey("PurchasePrice"))
{
ModelState["PurchasePrice"].Errors.Clear();
}
}
}
You can now use if (ModelState.IsValid) to check if there are any other validation errors and return the appropriate view
Side note: I just used your if condition relating to the OwnOrRent value, but I suspect what you really want is
if (!(model.OwnOrRent == "1" || model.OwnOrRent == "9"))
There is a thread about the different options to do conditional validation:
ASP.NET MVC Conditional validation
Related
I have a table used to track a patient appointments in a clinic.
Check In and out are used to track his presence.
I need to be able to create an appointment only if he had checked out
public class Appointment
{
[Key]
public virtual int ID { get; set; }
[Required]
[Display(Name = "Case Number")]
public virtual string MRN { get; set; }
[Required]
[Display(Name = "Department")]
public virtual int DepID { get; set; }
[Required]
[Display(Name = "Check IN")]
public virtual DateTime Check_IN { get; set; }
[Display(Name = "Check Out")]
public virtual DateTime? Check_OUT { get; set; }
}
I created this method inside the controller to check whether or not he checked out.
private bool CheckedOut (string MRN)
{
return _context.Appointments.Any(a => a.MRN == MRN && !a.Check_Out.HasValue);
}
And my Create action method.
public async Task<IActionResult> Create(string MRN, Appointment appointment)
{
ViewData["MRN"] = MRN;
if (CheckedOut(appointment.MRN))
{
_context.Add(appointment);
await _context.SaveChangesAsync();
return RedirectToAction("Index", "Patients");
}
return View();
}
But the appointment is still created even if the patient has one which he hadn't checked out from.
Any ideas?
Does it create an appointment if the User has checked out?
Because to me it looks like you are checking if any open appointments exist and returning true on that.
should be:
return !_context.Appointments.Any(a => a.MRN == MRN && !a.Check_Out.HasValue);
the problem has to be you LINQ query.
i would suggest that you extract variables and set a breakpoint to see what is going on:
private bool CheckedOut (string MRN)
{
var mrn = MRN;
var hasCheckedOut = a.Check_Out.HasValue;
return _context.Appointments.Any(a => a.MRN == mrn && !hasCheckedOut);
}
This is in ASP.NET Core MVC 2.1.
So what I want to do is validate a list of objects I read from a file.
The controller successfully reads the file and gets the data. I am using a DTO version of the model, heard it is better to do like that.
The main entity model.
public class Person
{
[Key]
public int ID { get; set; }
[Required(ErrorMessage = "Osoba obavezno mora imati ime")]
public string FirstName { get; set; }
[Required(ErrorMessage = "Osoba obavezno mora imati prezime")]
public string LastName { get; set; }
[Required(ErrorMessage = "Grad obavezno mora imati ime")]
public string CityName { get; set; }
[Required(ErrorMessage = "Osoba obavezno mora imati postanski broj")]
[StringLength(5, MinimumLength = 5, ErrorMessage = "Poštanski broja mora sadrži točno 5 znamenki.")]
[RegularExpression("^[0-9]*$", ErrorMessage = "Poštanski broj smije sadržavati samo znamenke.")]
public string PostalCode { get; set; }
[Required(ErrorMessage = "Osoba obavezno mora imati mobilni broj")]
[StringLength(11, MinimumLength = 11, ErrorMessage = "Mobilni broja mora sadrži točno 11 znamenki.")]
[RegularExpression("^[0-9]*$", ErrorMessage = "Mobilni broj smije sadržavati samo znamenke.")]
public string MobileNumber { get; set; }
public string FullName => $"{FirstName} {LastName}";
}
The DTO entity model.
public class PersonModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string CityName { get; set; }
public string PostalCode { get; set; }
public string MobileNumber { get; set; }
}
The method used in the controller.
public IActionResult LoadData()
{
PeopleService peopleService = new PeopleService();
var path = "pathToFile";
var resultData = peopleService.ReadCSVFile(path);
return PartialView("_LoadDataTable", resultData);
}
So now my question is what is the best way to validate the data from the file?
I experimented using the entity model it worked, but I seems like a bad idea because you have to call the database every time. I want to validate it client side first I guess.
Also, I need to get the error message back so that I can display it on view.
You can use TryValidateModel to valid the object from file. Document
You need to convert the string to a specific object fisrt, then call TryValidateModel.
public IActionResult LoadData()
{
//read the content from file and convert it to Person
string readText = "";
FileStream fileStream = new FileStream(#"path", FileMode.Open);
using (StreamReader reader = new StreamReader(fileStream))
{
readText += reader.ReadToEnd();
}
var person = JsonConvert.DeserializeObject<Person>(readText);
var valid=TryValidateModel(person);
List<string> listError = new List<string>();
foreach(var value in ModelState.Values)
{
if (value.Errors.Count > 0)
listError.Add(value.Errors[0].ErrorMessage);
}
ViewBag.errors = listError;
//...
}
View
#foreach(var e in (List<string>)ViewBag.errors)
{
<span class="text-danger">#e</span>
}
Another way is to use ValidationSummary in the view.
#Html.ValidationSummary()
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:
This my class Obj and Action. But ModelState.isValid checks the whole array obj, but I need a separate object in each cycle to check whether it pass validation.
public class Obj
{
[HiddenInput(DisplayValue = false)]
public string Id { get; set; }
[Required(ErrorMessage = "The field is required")]
public string Name { get; set; }
[Required(ErrorMessage = "The field is required.")]
[Range(1000, 2019, ErrorMessage = "Year of publication must be between 1000 and 2017.")]
[Display(Name = "Year of publication")]
public int Year { get; set; }
[DataType(DataType.MultilineText)]
public string Desc { get; set; }
}
public ActionResult Create(Obj[] obj)
{
foreach (var b in obj)
{
if (ModalState.isValid)
{
//...
}
}
return View();
}
To validate manually with the data annotations you can create your own ValidationContext for the object in question and then run TryValidateObject for each of the objects.
var validationResults = new List<ValidationResult>();
foreach (var b in obj)
{
var context = new ValidationContext(b);
var isValid = Validator.TryValidateObject(b,context,validationResults);
'do whatever.....
}
Try to use these methods: TryValidateModel or ValidateModel
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();
}