ASP.NET MVC ModelState is always valid with Fluent Validation - asp.net-mvc

I am trying to use fluent validation with ASP.NET MVC project. I am trying to validate my view model.
This is my viewmodel,
[Validator(typeof(ProductCreateValidator))]
public class ProductCreate
{
public string ProductCategory { get; set; }
public string ProductName { get; set; }
....
}
This is my validator class,
public class ProductCreateValidator : AbstractValidator<ProductCreate>
{
public ProductCreateValidator()
{
RuleFor(product => product.ProductCategory).NotNull();
RuleFor(product => product.ProductName).NotNull();
}
}
And in my controller, I am checking whether my ModelState is valid or not,
[HttpPost]
public ActionResult Create(ProductCreate model)
{
/* This is a method in viewmodel that fills dropdownlists from db */
model.FillDropDownLists();
/* Here this is always valid */
if (ModelState.IsValid)
{
SaveProduct(model);
return RedirectToAction("Index");
}
// If we got this far, something failed, redisplay form
return View(model);
}
This is what I have. My problem is ModelState.IsValid returns true when my viewmodel is completely empty. Do i need to manually configure Fluent validation so that model errors can be added to ModalState ?

As the documentation explains, make sure you have added the following line in your Application_Start in order to swap the data annotations model metadata provider and use fluent validation instead:
FluentValidationModelValidatorProvider.Configure();
Also the following comment in your action scares me:
/* This is a method in viewmodel that fills dropdownlists from db */
model.FillDropDownLists();
A View model shouldn't know what a database means. So having such methods in your view model is a very wrong approach.

Related

Asp MVC 5 - ModelState is Invalid?

I have the following view model:
public class CreateCaseViewModel
{
[Required]
public string Subject { get; set; }
[Required]
[DisplayName("Post Content")]
[UIHint("ForumEditor"), AllowHtml]
[DataType(DataType.MultilineText)]
public string PostContent { get; set; }
// some other dropdown properties
}
The following controller action:
[HttpPost]
[ValidateAntiForgeryToken]
[ValidateInput(false)]
public ActionResult Create(CreateCaseViewModel viewModel, FormCollection collection)
{
// Re-populate dropdowns
viewModel.Categories = _unitOfWork.CategoryRepository.GetCategories();
viewModel.Subject = collection["Subject"];
viewModel.PostContent = collection["Description"];
try
{
if (ModelState.IsValid)
{
// Do stuff
}
}
catch (DataException dex )
{
throw new ApplicationException("Something :", dex);
}
return View(viewModel);
}
I am manually assigning the value to PostContent from a value in FormCollection as you can see from code above. However I still keep getting modelstate is invalid - I'm returned back to the view with the validation error saying `The Post Content field is required'
Why is modelstate invalid?
When you submit the form the model binder will read the posted request data and map it to your method parameter. After that model validation framework will do the validation. It does not look at your FormCollection for doing this. So in your case, your model validation is failing because as per your view model it is expecting a value for PostContent property and it is not available there. Your action method code where you are setting the value of it gets executed later ( by this time model validation already occurred).
Your options are, either standardize the input element name with your view model property name (rename the PostContent to Description or vice versa)
public class CreateCaseViewModel
{
[Required]
public string Subject { get; set; }
[Required]
[DisplayName("Post Content")]
[UIHint("ForumEditor"), AllowHtml]
[DataType(DataType.MultilineText)]
public string Description { get; set; }
}
Now let the model binder maps the request body to your view model parameter. Remove the manual assignment from the FormCollection in your action method
Or you can probably create a new custom model binder which does the custom mapping for you (same as what you did in your action method).
I would go with option one. Let the default model binder takes care of it.
The model is validated before it is passed to your controller action. Modifying the model does not change that.
You need to call ModelState.Clear() followed by Controller.TryValidateModel(model) to re-validate the model and reset the IsValid property.

MVC: post, catching form values into property of page model

Alright...this may be a bit backwards but, I only need to do it in one spot.
I have a Model
public class LoginModel : xxx.Models.PageVars
{
public Item.LoginAttempt LoginAttempt { get; set; }
public LoginModel()
{
// does a bunch of stuff here...mainly to set the layout properties from PageVar
this.LoginAttempt = new Item.LoginAttempt();
}
}
Login Attempt is a simple obj (for now)
// login attempt
public class LoginAttempt
{
public string Email { get; set; }
public string Password { get; set; }
}
My controller
public ActionResult Login()
{
return View("Login", new Models.LoginModel());
}
[HttpPost]
public ActionResult LoginAttempt(LoginAttempt model)
{
return View("Login", model);
}
In my view
#model xxx.Models.LoginModel
Is there a way to use the property of the obj/model from LoginModel for the #model.
I can get the values from FormCollection or request but...that's not optimal.
thoughts???
tnx
The model for your GET should match the model for your POST. Otherwise, you're not playing on the same field. In order to allow the binding of data from a POST to a model, the HTML Helpers will generate a name that matches the access path of the property in the view's model. In other words, in your form, based on the model being LoginModel, your field names will be LoginAttempt.Email and LoginAttempt.Password. But, in the POST action, you're accepting just LoginAttempt, so the modelbinder is expecting to see data for Email and Password, which it won't find.
There's actually not even any need for this nested class. Just put your Email and Password fields directly on LoginModel and use that for both your view and your POST parameter. Then, you won't have any issues because everything will match up.
Why don't you have the form post controller action accept the parent model LoginModel instead of LoginAttempt? That way, the default MVC model binding should automatically parse the submitted values into the LoginModel and you'll have acces to LoginAttempt.
If it isn't then your form needs to use the prefix values in the names of the properties on the form. This is done automatically when you use TextboxFor, DropdownListFor etc.
In your example, the names of the form fields should start with LoginAttempt.Email etc
I've seen it work 2 ways. First way would be to rename your LoginAttempt model parameter to be
[HttpPost]
public ActionResult LoginAttempt(LoginAttempt loginModel)
{
return View("Login", model);
}
But i would use the Bind(Prefix) option
[HttpPost]
public ActionResult LoginAttempt([Bind(Prefix="LoginModel")] LoginAttempt model)
{
return View("Login", model);
}
you can't really return model of type LoginAttempt to the view though so you'd have to do even more work to get it to work if you're set on doing it this way. You should probably be redirecting to a different page instead of returning the Login view if it succeeds. Other wise return new LoginModel() {LoginAttempt = model}

MVC binding triggering validations errors

I'm trying to get my head around why (data annotation) validation errors are triggering when the page first loads, prior to any Submit/Posts. But more importantly how to fix this.
Reading SO and the interwebs, the reason seems to be model binding triggers validation errors for the view model properties, prior to the view model properties having values. I'm not sure if this is true and what is actually happening, but it sounds legit.
And I've read two workarounds, which sound a bit hacky:
1. Use the ModelState.Clear in the controller action method on the inital page load, OR
2. Initialiase the view model properties in an empty view model constructor. (Yet to confirm this technique)
Both these techniques sound like work-arounds. I'd rather understand what is happening and design my code appropriately.
Have others come across this issue? And if so, what are you doing?
Code as requested. Below you can see the Data Annotation validation on Property1 which is being triggered on initial requests, i.e. first page load.
Controller action method (refactored for simplicity):
public ActionResult Index([Bind(Include = Property1, Property2, Property3, vmclickedSearchButton)] IndexVM vm, string Submit)
{
bool searchButtonClicked = (Submit == "Search") ? true : false;
if (searchButtonClicked)
{
PopulateUIData(vm); // Fetch data from database and pass them to VM
if (ModelState.IsValid)
{
vm.clickedSearchButton = true; // Used in the vm to avoid logic execution duing initial requests
DoWork(vm);
}
}
return View(vm);
}
// Inital request
IndexVM newVM = new IndexVM();
PopulateUIData(newVM); // Fetch data from database and pass to VM
return View(newVM);
}
Design note:
Ideally I would like to sepate the rendering and submiting logic into separate action methods.
I.e. rendering within a [HttpGet]Index() action method, and submitting within a [HttpPost]Index() action method.
But since I'm using ForMethod.Get in the View as this method is used for searching functionality, I can only use a [HttpGet] Index action method.
View Model (refactored for simplicity):
public class IndexVM
{
// DropDownLists
public IEnumerable<SelectListItem> DDLForProperty1 { get; set; }
public IEnumerable<SelectListItem> DDLForProperty2 { get; set; }
public IEnumerable<SelectListItem> DDLForProperty3 { get; set; }
[Required]
public int? Property1 { get; set; }
public int? Property2 { get; set; }
public int? Property3 { get; set; }
public bool vmclickedSearchButton { get; set; }
}
Note:
The view model is very simple. It contains drop down lists, selected properties for the DDLs, and a validation rule on one of the properties.
Adding a constructor to the view model and initialising the property workaround:
public IndexVM()
{
this.Property1 = 0;
}
The problem is that you are sending an invalid model to your view.
The Property1 in your model is being required and is a nullable int. This does not explain why validation executes, but why the model is invalid.
Your action method is executing the validation during initial load. Model binding will execute validation regardless of http method (get or post).
Since you are requiring Property1 (int?) to NOT be null, by definition your model becomes invalid when it is instantiated. There are several ways to handle this (not sure which is most appropriate though)
Create separate methods for HttpGet and HttpPost in your controller. Do not implement binding for HttpGet.
Use a default value (as you have done already)
Modify the model so that Property1 is not nullable (i.e. int).

Fluent Validation in ASP.net MVC - Database Validations

I'm using the Fluent Validation framework in my ASP.net MVC 3 project. So far all of my validations have been very simple (make sure string is not empty, only a certain length, etc.) but now I need to verify that something exists in the database or not.
Should Fluent Validation be used in this case?
If the database validation should be done using Fluent Validation, then how do I handle dependencies? The validator classes are created automatically, and I would need to somehow pass it one of my repository instances in order to query my database.
An example of what I'm trying to validate might:
I have a dropdown list on my page with a list of selected items. I want to validate that the item they selected actually exists in the database before trying to save a new record.
Edit
Here is a code example of a regular validation in Fluent Validation framework:
[Validator(typeof(CreateProductViewModelValidator))]
public class CreateProductViewModel
{
public string Name { get; set; }
public decimal Price { get; set; }
}
public class CreateProductViewModelValidator : AbstractValidator<CreateProductViewModel>
{
public CreateProductViewModelValidator()
{
RuleFor(m => m.Name).NotEmpty();
}
}
Controller:
public ActionResult Create(CreateProductViewModel model)
{
if(!ModelState.IsValid)
{
return View(model);
}
var product = new Product { Name = model.Name, Price = model.Price };
repository.AddProduct(product);
return RedirectToAction("Index");
}
As you can see, I never create the Validator myself. This works because of the following line in Global.asax:
FluentValidation.Mvc.FluentValidationModelValidatorProvider.Configure();
The problem is that now I have a validator that needs to interact with my database using a repository, but since I'm not creating the validators I don't know how I would get that dependency passed in, other than hardcoding the concrete type.
Can't you just create your own validation method where in you would kick-off the database validation?
RuleFor(m => m.name)
.Must(BeInDatabase)
private static bool BeInDatabase(string name)
{
// Do database validation and return false if not valid
return false;
}
I'm using FluentValidation for DataBase validations. just pass the Validation class the session in the Ctor. and do the validation inside the action something like:
var validationResult = new ProdcutValidator(session).Validate(product);
Update: Based on your example I add my example...
public class CreateProductViewModel
{
public string Name { get; set; }
public decimal Price { get; set; }
}
public class CreateProductViewModelValidator : abstractValidator<CreateProductViewModel>
{
private readonly ISession _session;
public CreateProductViewModelValidator(ISession session)
{
_session = session;
RuleFor(m => m.Name).NotEmpty();
RuleFor(m => m.Code).Must(m, Code => _session<Product>.Get(Code) == null);
}
}
Controller:
public ActionResult Create(CreateProductViewModel model)
{
var validator = new CreateProductViewModelValidator();
var validationResult =validator.Validate(model);
if(!validationResult.IsValid)
{
// You will have to add the errors by hand to the ModelState's errors so the
// user will be able to know why the post didn't succeeded(It's better writing
// a global function(in your "base controller" That Derived From Controller)
// that migrate the validation result to the
// ModelState so you could use the ModelState Only.
return View(model);
}
var product = new Product { Name = model.Name, Price = model.Price };
repository.AddProduct(product);
return RedirectToAction("Index");
}
Second update:
If you insist using parameterless constructor you will have to use some Inversion Of control container, a static class that is something like the Factory of your objects.
use it like this:
public class CreateProductViewModelValidator : abstractValidator<CreateProductViewModel>
{
private readonly ISession _session;
public CreateProductViewModelValidator()
{
_session = IoC.Container.Reslove<ISession>();
RuleFor(m => m.Name).NotEmpty();
RuleFor(m => m.Code).Must(m, Code => _session<Product>.Get(Code) == null);
}
}
You can find many IoC containers, most famous are Windsor and Ninject,
You will need to register- instruct the container once to resolve all the ISession to return your's session object.
The other way this could work for you is using Constructor injection. While this method isn't as clear cut as using an IoC library, it may help if you have a static way of accessing or fetching your session.
public class CreateProductViewModelValidator
{
private ISession _session;
public CreateProductViewModelValidator()
:this(SessionFactory.GetCurrentSession()) //Or some other way of fetching the repository.
{
}
internal CreateProductViewModelValidator(ISession session)
{
this._session = session;
RuleFor(m => m.Name);//More validation here using ISession...
}
}
I have been spending quite a bit of time thinking about this exact same issue. I am using ninject to inject my repository into my web UI layer so that my web UI only accesses the database through an interface.
I am wanting to be able to validate things that access the database such as checking for duplicate names and hence my validation needs to access the injected repository. I think that the best way to do this is to just setup Fluent Validation via the manual method rather than the MVC integrated way. For Example:
Create your validation Class (can pass in repository Interface):
public class CategoryDataBaseValidation : AbstractValidator<CategoryViewModel>
{
private IRepository repository;
public CategoryDataBaseValidation (IRepository repoParam)
{
repository = repoParam;
RuleFor(Category => Category.Name).Must(NotHaveDuplicateName).WithMessage("Name already exists");
}
private bool NotHaveDuplicateName(string name)
{
List<Category> c = repository.Categories.ToList(); //Just showing that you can access DB here and do what you like.
return false;
}
}
}
Then in your controller you can just create an instance of above class and pass in the repository (that ninject would have injected in the controller constructor)
[HttpPost]
public ActionResult Create(CategoryViewModel _CategoryViewModel )
{
CategoryDataBaseValidation validator = new CategoryDataBaseValidation (repository);
ValidationResult results = validator.Validate(_CategoryViewModel );
if (results.IsValid == false)
{
foreach (var failure in results.Errors)
{
//output error
}
}
return View(category);
}
Both the above files can live in the Web UI project and you can then also just use the standard MVC DataAnnotations for client side validation.
Just thought that I would put this up for comment / help someone.

Model Validation problem in ASP.NEt MVC 2 RC 2

I have facing the following problem after the update.
I have a Model with Class level Validation plus property level validation in it. After updating to MVC 2 RC 2. The model validation fails on Model binding. What i actually understand that new mechanism trying to validate the model when you first request it or say on GET and it get null object exception during tryvalidatemodel Model binding call.
My Model is like this below
[Serializable]
[MetadataType(typeof(InterestClaimMetaData))] //metadata with all properties level validation
//these validations fails when you request a page.
[DateComparison("DateA", "DateB", eDateComparitor.GreaterThan,
ErrorMessage = "Date A must be greater than B Date")]
[MutuallyExclusive("A", "B", ErrorMessage = "Please select either A or B field")]
public class IE {
public int ID { get; set; }
public byte[] Updated { get; set; }
}
DataComparison and MutuallyExclusive overrides the validate function isvalid and check the validation but it fails trying to validate as first requested.
dont know how to stop this happening as it should not validate model on get request; just attach the properties.
Only models without these class level validation works.
Please advise.
Thanks
Seperate your action method in your controller in to two action methods. Mark one as GET and the other as POST. Then only apply the validation in the POST method. So, for example, if you currently have an method called Create that looks something like this...
public ActionResult Create(YourModel yourModel)
{
// Some code in here to validate stuff
// Some code in here to do stuff
return RedirectToAction("Index");
}
Split this out in to two methods like this...
[HttpGet]
public ActionResult Create()
{
return View();
}
[HttpPost]
public ActionResult Create(YourModel yourModel)
{
try
{
// Some code in here to validate stuff
// Some code in here to do stuff
return RedirectToAction("Index");
}
catch
{
return View();
}
}

Resources