I use the built in model validation which works great for specifying required fields and such but on my model I have specified key constraints so the combination of two columns are unique.
How should I validate for this so it doesn't throw exception if user tries to add duplicate?
Here is my model:
public class EmailFilter
{
public int ID { get; set; }
[Required]
[StringLength(100)]
[Index("IX_FilterAndEmail", 2, IsUnique = true)]
public string Email { get; set; }
//This is an enum
[Required]
[Index("IX_FilterAndEmail", 1, IsUnique = true)]
public EmailFilterType Filter { get; set; }
}
And my create controller method. I tried to add an error but I am not doing it right. It stops the exception happening but it just returns to the listview. I'm not sure what the right way to validate here is.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ID,Email,Filter")] EmailFilter emailFilter)
{
if (ModelState.IsValid)
{
if(db.EmailFilters.Any(x => x.Filter == emailFilter.Filter && x.Email == emailFilter.Email))
{
// This doesn't seem to do anything and returns to listview not create view
// "EFValidationSummary" is the id of my validation summary razor control
ModelState.AddModelError("EFValidationSummary", "This filter already exists");
return View(emailFilter);
}
else
{
db.EmailFilters.Add(emailFilter);
db.SaveChanges();
}
return RedirectToAction("Index");
}
return View(emailFilter);
}
How do I properly trigger an error and send back to the create page with the validation error displayed?
The first parameter of AddModelError() is the name of the property in your model. In your case, the error message would be displayed in the placeholder generated by
#Html.ValidationMessageFor(m => EFValidationSummary)
but your model does not contain a property named EFValidationSummary. In order to display validation messages in the placeholder generated by #Html.ValidationSummary(), you need to provide an empty string for the first parameter
ModelState.AddModelError("", "This filter already exists");
Related
I'm developing an Asp.Net Core 2.1 App using Razor Pages.I've come down with an odd behavior.The problem is when I submit a form,the client side validation passes with all required properties filled out,but then the validation fails with the ModelState.IsValid check,and the reason is that the ModelState contains the required string properties twice,one with the value entered and one with null value,So the validation fails!
{[BankName, Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary+ModelStateNode]}
{[BankAccount.BankName, Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary+ModelStateNode]}
See,BankAccount is the model class,and BankName is a required property.I don't know why the property appears twice in ModelState dictionary,one with the model name(with the data entered) and one without the modelname(with null value)
Any idea why this is happening?
public class BankAccount
{
[DisplayName("")]
public int BankAccountId { get; set; }
[MaxLength(20, ErrorMessage = "")]
[Required(ErrorMessage = "")]
[DisplayName("")]
public string BankName { get; set; }
...
Here' the code OnPost() where the validation fails:
public async Task<IActionResult> OnPostAsync()
{
// TODO: Not ideal! But solves the problem of returning invalid model state.
ModelState.Remove("BankName");
if (!ModelState.IsValid)
{
return RedirectToPage();
}
_context.BankAccounts.Add(BankAccount);
await _context.SaveChangesAsync();
return RedirectToPage();
}
After searching a lot,I found a workaround,which isn't very ideal.That's to remove the additional property that has oddly been inserted in ModelState dictionary. I mean this line:
ModelState.Remove("BankName");
But that's not the right way.I'd like to figure out why it's happening?!
Here are two properties defined on the PageModel:
[BindProperty]
public BankAccount BankAccount { get; set; }
[BindProperty]
public BankAccount BankAccountEdit { get; set; }
One is used to insert new BankAccount and the other one is used to edit existing ones by clicking on a button from the table.
I figured out the the issue.The problem was that I have two properties of the same type(BankAccount class) in my page model,one for inserting new entity and the other one for editing existing entity all on the same page.
So to validate each form separately OnPost(),I used the following code:
public async Task<IActionResult> OnPostAsync()
{
var validateBankAccount = ModelState.GetFieldValidationState("BankAccount");
if (validateBankAccount ==
Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid)
{
return RedirectToPage();
}
_context.BankAccounts.Add(BankAccount);
await _context.SaveChangesAsync();
return RedirectToPage();
}
I'm new at ASP.NET MVC web framework. My database is compound of a single model class("Movie"). I need to validate user's from entering existing data, for instance, a database row could be:
Title - "Indiana Jones and the lost Arc"
Price - $10.00
If another user tries to insert into the database the same data above, provide an error message and prevent from submitting the form collection.
First of all, I think that your question need a "program as answer" but I'll try to suggest you from where to start for working with validation. Suppose you have this model:
class Movie {
public Guid Id { get; set; }
[Required(ErrorMessage="Title is required.")]
[Remote("UniqueTitle", "Validation")]
public String Title { get; set; }
[Required(ErrorMessage="Price is required.")]
public float Price { get; set; }
}
You can decorate it for "simple validation" using Data Annotation. I've used a specific attribute, called Remote.
This attribute allow you to define a custom, server-side, logic to validate the model.
Now, you can create a validation controller where check that provided value is not already in use:
class ValidationController : Controller {
private IDbContext db = ...;
public ActionResult UniqueTitle(String title) {
var item = db.Movies.FirstOrDefault(m => m.Title.Equals(title));
return Json(item == null, JsonRequestBehavior.AllowGet);
}
}
Now you are ready to validate your model.
I hope this can help.
We're always told that a Controller should be skinny and that validation should be done in the Model, not the Controller. But consider the following example.
Here's is a simple Model and Controller for handling the POST from an edit screen, on which we can edit a Person object.
public class PersonEditModel
{
[Required(ErrorMessage = "No ID Passed")]
public int ID { get; set; }
[Required(ErrorMessage = "First name Required")]
[StringLength(50,ErrorMessage = "Must be under 50 characters")]
public string FirstName { get; set; }
[Required(ErrorMessage = "Last name Required")]
[StringLength(50,ErrorMessage = "Must be under 50 characters")]
public string LastName { get; set; }
}
public class PersonController : Controller
{
// [HttpGet]View, [HttpGet]Edit Controller methods omitted for brevity
[HttpPost]
public ActionResult Edit(PersonEditModel model)
{
// save changes to the record
return RedirectToAction("View", "Person", new { ID = model.ID});
}
}
The Model performs two kinds of validation here. It validates FirstName and LastName, but it also validates the private key (ID) used to access the record we wish to change. Should this validation be done in the Model also?
What if we then want to expand validation (as we should) to include a check to see if this record exists?
Normally, I would validate this in the controller:
[HttpPost]
public ActionResult Edit(PersonEditModel model)
{
using(DatabaseContext db = new DatabaseContext())
{
var _person = db.Persons.Where(x => x.ID == model.ID);
if(_person == null)
{
ModelState.AddError("This person does not exist!");
// not sure how we got here, malicious post maybe. Who knows.
// so since the ID is invalid, we return the user to the Person List
return RedirectToAction("List", Person");
}
// save changes
}
// if we got here, everything likely worked out fine
return RedirectToAction("View", "Person", new { ID = model.ID});
}
Is this bad practice? Should I be checking if the record exists in some kind of complex custom validation method in the model? Should I put it somewhere else entirely?
UPDATE
On a related note. Should a ViewModel contain the methods to populate the data?
Which of these is better practice - this
public class PersonViewModel
{
public Person person { get; set; }
public PersonViewModel(int ID){
using(DatabaseContext db = new DatabaseContext())
{
this.person = db.Persons.Where(x => x.ID == ID);
}
}
}
[HttpPost]
public ActionResult View(int ID)
{
return View("View", new PersonViewModel(ID));
}
Or this?
public class PersonViewModel
{
public Person person { get; set; }
}
[HttpPost]
public ActionResult View(int ID)
{
PersonViewModel model = new PersonViewModel();
using(DatabaseContext db = new DatabaseContext())
{
model.person = db.Persons.Where(x => x.ID == ID);
}
return View("View", model);
}
I generally prefer FluentValidation for all purposes. It also has a Nuget to install it out-of the box in VS.
Sample Validation Code from here:
using FluentValidation;
public class CustomerValidator: AbstractValidator<Customer> {
public CustomerValidator() {
RuleFor(customer => customer.Surname).NotEmpty();
RuleFor(customer => customer.Forename).NotEmpty().WithMessage("Please specify a first name");
RuleFor(customer => customer.Discount).NotEqual(0).When(customer => customer.HasDiscount);
RuleFor(customer => customer.Address).Length(20, 250);
RuleFor(customer => customer.Postcode).Must(BeAValidPostcode).WithMessage("Please specify a valid postcode");
}
private bool BeAValidPostcode(string postcode) {
// custom postcode validating logic goes here
}
}
Customer customer = new Customer();
CustomerValidator validator = new CustomerValidator();
ValidationResult results = validator.Validate(customer);
bool validationSucceeded = results.IsValid;
IList<ValidationFailure> failures = results.Errors;
See?? It is very easy to validate any kind of models with Fluent Validation with clean methods. You can consider going through FluentValidation Documentation.
Where to validate?
Suppose you have a model as below:
public class Category
{
public int ID { get; set; }
public string Name { get; set; }
virtual public ICollection<Image> Images { get; set; }
}
then, you will define another validator model in a similar class library or preferably a new class library that handles validation for all the models in the project.
public class CategoryValidator : AbstractValidator<Category>
{
public CategoryValidator()
{
RuleFor(x => x.Name).NotEmpty().WithMessage("Category name is required.");
}
}
So, you can do it in a separate validator model keeping your methods and domain models as clean as possible.
When we talk about Model, it includes your DAL and your business layer. For small apps or demos it is not unusual to see that kind of code in a controller, but normally you should give that role to the business or data layer :
[HttpPost]
public ActionResult Edit(PersonEditModel model)
{
// Validation round one, using attributes defined on your properties
// The model binder checks for you if required fields are submitted, with correct length
if(ModelState.IsValid)
{
// Validation round two, we push our model to the business layer
var errorMessage = this.personService.Update(model);
// some error has returned from the business layer
if(!string.IsNullOrEmpty(errorMessage))
{
// Error is added to be displayed to the user
ModelState.AddModelError(errorMessage);
}
else
{
// Update successfull
return RedirectToAction("View", "Person", new { ID = model.ID});
}
}
// Back to our form with current model values, as they're still in the ModelState
return View();
}
Here the goal is to free the controller from business logic validation and usage of the data context. It pushes submitted data and is notified if errors occurred. I used a string variable, but you can implement error management as you like. Evolving your business rules won't impact your controller at all.
There's absolutely nothing wrong with this. Your controllers are responsible for directing the flow of control when it comes to which view to be shown to your users. Part of doing that is ensuring that a view is getting a model that is in a usable state.
The controller doesn't care what the model is, or what the model contains, but it does care that it's valid. It's why ModelState.IsValid is so important, because the controller doesn't have to know how that validation is performed or what directly makes a model valid. Typically, any validation that needs to take place after ModelState.IsValid, can be pushed to another layer of your application, which again enforces separation-of-concerns.
Problem
I have a list of fields that the user can edit. When the model is submitted I want to check if this items are valid. I can't use data notations because each field has a different validation process that I will not know until runtime. If the validation fails I use the ModelState.AddModelError(string key, string error) where the key is the name of the html element you want to add the error message to. Since there are a list of fields the name that Razor generates for the html item is like Fields[0].DisplayName. My question is there a method or a way to get the key of the generated html name from the view model?
Attempted Solution
I tried the toString() method for the key with no luck. I also looked through the HtmlHelper class but I didn't see any helpful methods.
Code Snippet
View Model
public class CreateFieldsModel
{
public TemplateCreateFieldsModel()
{
FreeFields = new List<FieldModel>();
}
[HiddenInput(DisplayValue=false)]
public int ID { get; set; }
public IList<TemplateFieldModel> FreeFields { get; set; }
public class TemplateFieldModel
{
[Display(Name="Dispay Name")]
public string DisplayName { get; set; }
[Required]
[Display(Name="Field")]
public int FieldTypeID { get; set; }
}
}
Controller
public ActionResult CreateFields(CreateFieldsModel model)
{
if (!ModelState.IsValid)
{
//Where do I get the key from the view model?
ModelState.AddModelError(model.FreeFields[0], "Test Error");
return View(model);
}
}
After digging around in the source code I have found the solution. There is a class called ExpressionHelper that is used to generate the html name for the field when EditorFor() is called. The ExpressionHelper class has a method called GetExpressionText() that returns a string that is the name of that html element. Here is how to use it ...
for (int i = 0; i < model.FreeFields.Count(); i++)
{
//Generate the expression for the item
Expression<Func<CreateFieldsModel, string>> expression = x => x.FreeFields[i].Value;
//Get the name of our html input item
string key = ExpressionHelper.GetExpressionText(expression);
//Add an error message to that item
ModelState.AddModelError(key, "Error!");
}
if (!ModelState.IsValid)
{
return View(model);
}
You have to frame the key(name of the input element) inside the controller based upon how you are rendering the fields in the form.
For ex. if the validation of the second item in the FreeFields collection of CreateFieldsModel fails you can frame the name of the input element i.e. key as FreeFields[1].DisplayName where the validation error is going to be mapped.
As far as I know you can't easily get that from controller.
I want to simply validate a single property of that model
public ActionResult Rate([Bind(Exclude="Score")]RatingModel model)
{
if(ModelState.IsValid)
{
//here model is validated without check Score property validations
model.Score = ParseScore( Request.Form("score"));
// Now i have updated Score property manualy and now i want to validate Score property
}
}
after assign Score manually, Mvc framework does not check validation on model. Now i want to validate Score property with all validation attributes which currently exist on model.
// How to do that easily ? Mvc Framework support this scenario ?
Here is my model
public class RatingModel
{
[Range(0,5),Required]
public int Score { get; set; }
}
I have found right solution. I simply call TryValidateModel and it validate properties include Score property.
public ActionResult Rate([Bind(Exclude="Score")]RatingModel model)
{
model.Score = ParseScore( Request.Form("score"));
if(TryValidateModel(model))
{
///validated with all validations
}
}
You're using MVC3. Any particular reason why you aren't setting some of the most basic validation rules in the Model?
You can set some validation rules directly in the model. For example, if you want to validate an email field, you can set the rules and even the error messages in the model itself.
[Required(ErrorMessage = "You must type in something in the field.")]
[RegularExpression(".+\\#.+\\..+", ErrorMessage = "You must type in a valid email address.")]
[Display(Name = "Email:")]
public string Email { get; set; }
Read more here:
http://www.asp.net/mvc/tutorials/validation-with-the-data-annotation-validators-cs
You need to check if the ModelState is valid in the Controller Action:
public ActionResult Action(RatingModel viewModel)
{
if (ModelState.IsValid)
{
//Model is validated
}
else
{
return View(viewModel);
}
}