use .NET MVC and code-first EF to implement of requested functionality. Business objects are relatively complex and I use System.ComponentModel.DataAnnotations.IValidatableObject to validate business object.
Now I'm trying to find the way, how to show validation result from business object, using MVC ValidationSummary without using data annotations. For example (very simplified):
Business Object:
public class MyBusinessObject : BaseEntity, IValidatableObject
{
public virtual IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
return Validate();
}
public IEnumerable<ValidationResult> Validate()
{
List<ValidationResult> results = new List<ValidationResult>();
if (DealType == DealTypes.NotSet)
{
results.Add(new ValidationResult("BO.DealType.NotSet", new[] { "DealType" }));
}
return results.Count > 0 ? results.AsEnumerable() : null;
}
}
Now in my MVC controller I have something like this:
public class MyController : Controller
{
[HttpPost]
public ActionResult New(MyModel myModel)
{
MyBusinessObject bo = GetBoFromModel(myModel);
IEnumerable<ValidationResult> result = bo.Validate();
if(result == null)
{
//Save bo, using my services layer
//return RedirectResult to success page
}
return View(myModel);
}
}
In view, I have Html.ValidationSummary();.
How I can pass IEnumerable<ValidationResult> to the ValidationSummary?
I tried to find an answer by googling, but all examples I found describes how to show validation summary using data annotations in Model and not in Business object.
Thanks
Add property, say BusinessError, in the model
in the View do the following
#Html.ValidationMessageFor(model => model.BusinessError)
Then in your controller whenever you have error do the following
ModelState.AddModelError("BussinessError", your error)
I would have a look at FluentValidation. It's a framework for validation without data annoations. I've used it with great success in some projects for complex validation, and it is also usable outside of the MVC-project.
Here is the sample code from their page:
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.Company).NotNull();
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;
Entity Framework should throw a DbEntityValidationException if there are validation errors. You can then use the exception to add the errors to the ModelState.
try
{
SaveChanges();
}
catch (DbEntityValidationException ex)
{
AddDbErrorsToModelState(ex);
}
return View(myModel);
protected void AddDbErrorsToModelState(DbEntityValidationException ex)
{
foreach (var validationErrors in ex.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
ModelState.AddModelError(validationError.PropertyName, validationError.ErrorMessage);
}
}
}
One of the ways to pass the contents of IEnumerate and keep taking advantage of Html.ValidationSummary is by updating ModelState.
You can find a good discussion on how to update the ModelState here.
Related
I have a scenario where I need to validate a view model differently under different contexts. For example, certain fields are required if you are going to, say, Post a message but they are not needed if you just want to save a Draft. Yet, there are still required fields for a Draft. Therefore, I have inputs that require validation based on the scenario you are saving under.
I'm using IValidatableObject on supple input models since the static attributes don't seem to allow this. I see there is the option to pass in data to the Validate method using the ValidationContext.Items property. I can read that in the validation, but after searching through the source code for MVC, it doesn't look like there's a way to actually set that before you would do a TryUpdate, etc, to set the scenario you're validating under.
Am I missing something or is there another method I'm not seeing?
public IEnumerable<System.ComponentModel.DataAnnotations.ValidationResult> Validate(ValidationContext validationContext)
{
ValidationLevel validationLevel;
object validationLevelObject;
if (validationContext.Items.TryGetValue("$" + nameof(ValidationLevel), out validationLevelObject))
{
validationLevel = (ValidationLevel)validationLevelObject;
}
else
{
validationLevel = ValidationLevel.Full;
}
...
In your model/class that implemented the IValidatableObject, try doing something like this:
...
public List<ValidationResult> ValidationResults { get; } = new List<ValidationResult>();
public bool TryValidate(out List<ValidationResult> vResults)
{
vResults = ValidationResults;
var context = new ValidationContext(this);
Validate(context);
var fieldValidations = new List<ValidationResult>();
var isValid = Validator.TryValidateObject(context.ObjectInstance, context, fieldValidations,
validateAllProperties: true);
//Add any attribute validation errors to ValidationResults
if (!isValid)
{
foreach (var validationResult in fieldValidations)
{
ValidationResults.Add(validationResult);
}
}
//Add your custom validations
if (!IsDraft() && Message.IsStringBlank())
{
ValidationResults.Add(new ValidationResult("Message cannot empty");
}
isValid = !ValidationResults.Any();
return isValid;
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (_isClassValidate) return new List<ValidationResult>();
_isClassValidate = true;
return ValidationResults;
}
I have seen many examples of using FluentValidation here but none seem to fit my need. I have an existing server side implementation and based on answers here, I am convinced I have to change my implementation to get it work client side. One reason is because I can't set a ValidationType which seems required for the client side. I am having trouble converting the code so I can use it client side as well. I am submitting a list of File objects and want client side validation that the file extensions are either .pdf or .doc.
Global - Many examples here show a much more complicated Configure
protected void Application_Start()
{
FluentValidationModelValidatorProvider.Configure();
}
Model - I've simplified my model to show that I have at least one property and a collection
[Validator(typeof(MyCustomValidator))]
public class MyCustomModel
{
public DateTime SubmitDate { get; set; }
public List<HttpPostedFileBase> MyFiles { get; set; }
}
Model Validator - I have a separate validator for the collection
public class MyCustomModelValidator : AbstractValidator<MyCustomModel>
{
public MyCustomModelValidator()
{
RuleFor(x => x.SubmitDate)
.NotEmpty()
.WithMessage("Date Required");
RuleFor(x => x.MyFiles)
.SetCollectionValidator(new MyFileValidator())
.Where(x => x != null);
}
}
Collection Validator - This should check a file for a valid extension
public class MyFileValidator : AbstractValidator<HttpPostedFileBase>
{
public MyFileValidator()
{
RuleFor(x => x)
.Must(x => x.IsValidFileType())
.WithMessage("Invalid File Type")
}
}
public static bool IsValidFileType(this HttpPostedFileBase file)
{
var extensions = { ".pdf", ".doc" };
return extensions.Contains(Path.GetExtension(file.FileName.ToLower()));
}
Controller - Just showing the basics
[HttpGet]
public ActionResult Index(DefaultParameters parameters)
{
var model = new MyCustomModel();
return this.View(model);
}
[HttpPost]
public ActionResult Submit(MyCustomModel model)
{
if (!ModelState.IsValid)
{
return this.View("Index", model);
}
}
View - I am allowing 5 uploads per submission
#Html.TextBoxFor(m => m.SubmitDate)
#Html.ValidationMessageFor(m => m.TextBoxFor
#for (int i = 0; i < 5; i++)
{
#Html.TextBoxFor(m => m.FileSubmissions[i], new { type = "file" })
#Html.ValidationMessageFor(m => m.FileSubmissions[i])
}
Im not sure why you would change it to clientside? Not all validations should run clientside. Maybe you can get it working using the regex validation method instead of the Must method which is performed clientside according to the docs.
On POST , if validation failed and before sending back the ViewModel to the same View with Model State errors, do you rebuild ViewModel for all SelectLists, ReadOnly fields etc?
right now I have separate methods for Fill First Time(for GET Edit-Method) / Rebuild ViewModels from domain objects, what is the best practice so I can be DRY and also not have to change two methods any time I add a new readonly property to ViewModel?
My Solution: Followed this Pattern
Followed pattern suggested here: https://stackoverflow.com/a/2775656/57132
In IModelBuilder Implementation
Build(..)
{
var viewModel = new ViewModel();
// and Fill all Non-ReadOnly fields
...
...
call CompleteViewModel(viewModel)
}
CompleteViewModel(ViewModel viewModel)
{
//Fill all ReadOnly & SelectLists
...
}
The reason I went with this solution is because I don't want to store stuff on server to retrieve across the HTTP Requests
I don't rebuild it, because I don't stay at POST. I follow POST-REDIRECT-GET pattern, so if I post to /User/Edit/1 using POST HTTP method, I get redirected to /User/Edit/1 uasing GET.
ModelState is transferred to TempData to follow Post-Redirect-Get and be availabe at GET call. View model is built in one place, at GET call. Example:
[HttpPost]
[ExportModelStateToTempData]
public ActionResult Edit(int id, SomeVM postedModel)
{
if (ModelState.IsValid) {
//do something with postedModel and then go back to list
return RedirectToAction(ControllerActions.List);
}
//return back to edit, because there was an error
return RedirectToAction(ControllerActions.Edit, new { id });
}
[ImportModelStateFromTempData]
public ActionResult Edit(int id)
{
var model = //create model here
return View(ControllerActions.Edit, model);
}
This is code for attributes importing/exporting ModelState:
public abstract class ModelStateTempDataTransferAttribute : ActionFilterAttribute
{
protected static readonly string Key = typeof(ModelStateTempDataTransferAttribute).FullName;
}
public class ExportModelStateToTempDataAttribute : ModelStateTempDataTransferAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
//Only export when ModelState is not valid
if (!filterContext.Controller.ViewData.ModelState.IsValid)
{
//Export if we are redirecting
if ((filterContext.Result is RedirectResult) || (filterContext.Result is RedirectToRouteResult))
{
filterContext.Controller.TempData[Key] = filterContext.Controller.ViewData.ModelState;
}
}
base.OnActionExecuted(filterContext);
}
}
public class ImportModelStateFromTempDataAttribute : ModelStateTempDataTransferAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
ModelStateDictionary modelState = filterContext.Controller.TempData[Key] as ModelStateDictionary;
if (modelState != null)
{
//Only Import if we are viewing
if (filterContext.Result is ViewResult)
{
filterContext.Controller.ViewData.ModelState.Merge(modelState);
}
else
{
//Otherwise remove it.
filterContext.Controller.TempData.Remove(Key);
}
}
base.OnActionExecuted(filterContext);
}
}
The simplest solution would be to pass in you viewModel to the method and account for null
private MyViewModel BuildViewModel(MyViewModel model = null)
{
model = model ?? new MyViewModel();
model.ReadOnlyList = new .....
.
.
return model;
}
for Create:
var model = BuildViewModel();
for rebuild:
model = buildViewModel(model);
I like #LukLed's answer above - it looks very interesting. If you want another option, here's what I currently do.
In my service layer, I have a method to build my view model. I call that on GET and return the the view model to the view. On POST, I build the model from the incoming ID and then TryUpdateModel(model). From there, you can do whatever you like (save, check model state, etc.). With this method, you only have 1 build method and only have to update it once if your model changes (i.e. add/remove properties in the future, etc.).
[HttpGet]
public ActionResult AssessFocuses(int apaID)
{
var model = this.apaService.BuildAssessFocusesViewModel(apaID);
return this.View(model);
}
[HttpPost]
public ActionResult AssessFocuses(int apaID, string button)
{
var model = this.apaService.BuildAssessFocusesViewModel(apaID);
this.TryUpdateModel(model);
switch (button)
{
case ButtonSubmitValues.Back:
case ButtonSubmitValues.Next:
case ButtonSubmitValues.Save:
case ButtonSubmitValues.SaveAndClose:
{
try
{
this.apaService.SaveFocusResults(model);
}
catch (ModelStateException<AssessFocusesViewModel> mse)
{
mse.ApplyTo(this.ModelState);
}
if (!this.ModelState.IsValid)
{
this.ShowErrorMessage(Resources.ErrorMsg_WEB_ValidationSummaryTitle);
return this.View(model);
}
break;
}
default:
throw new InvalidOperationException(string.Format(Resources.ErrorMsg_WEB_InvalidButton, button));
}
switch (button)
{
case ButtonSubmitValues.Back:
return this.RedirectToActionFor<APAController>(c => c.EnterRecommendationsPartner(model.ApaID));
case ButtonSubmitValues.Next:
return this.RedirectToActionFor<APAController>(c => c.AssessCompetenciesPartner(model.ApaID));
case ButtonSubmitValues.Save:
this.ShowSuccessMessage(Resources.Msg_WEB_NotifyBarSuccessGeneral);
return this.RedirectToActionFor<APAController>(c => c.AssessFocuses(model.ApaID));
case ButtonSubmitValues.SaveAndClose:
default:
return this.RedirectToActionFor<UtilityController>(c => c.CloseWindow());
}
}
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.
I'm displaying errors on my form with the use of
<%= Html.ValidationSummary("Please review the errors below") %>
My domain object inherits from a base class and I am finding that the base class data annotation properties are being displayed at the bottom of the list. This goes against the order in which they appear in my form.
Is there any way of specifying what order the errors should be displayed?
Example:
public class ClassA { [Required]public string AProperty; }
public class ClassB : ClassA { [Required]public string BProperty; }
My form (strongly typed view of ClassB):
AProperty: <%= Html.TextBoxFor(m => m.AProperty) %>
BProperty: <%= Html.TextBoxFor(m => m.BProperty) %>
Validation errors appear as:
The BProperty is required.
The AProperty is required.
I've written an extension for this:
public static void OrderByKeys(this ModelStateDictionary modelStateDictionary, IEnumerable<string> keys)
{
ModelStateDictionary result = new ModelStateDictionary();
foreach (string key in keys)
{
if (modelStateDictionary.ContainsKey(key) && !result.ContainsKey(key))
{
result.Add(key, modelStateDictionary[key]);
}
}
foreach (string key in modelStateDictionary.Keys)
{
if (!result.ContainsKey(key))
{
result.Add(key, modelStateDictionary[key]);
}
}
modelStateDictionary.Clear();
modelStateDictionary.Merge(result);
}
Which you can use by:
ModelState.OrderByKeys(new[] { "AProperty", "BProperty" });
Nope. Reflection is used to get all the DataAnnotations and they always appear in the order the properties would appear with a call to typeof(MagicSocks).GetTYpe().GetProperties(). In your case I'm pretty sure derived class properties will always appear before base type properties.
You have to write your own helper and our own attributes to display the validation errors in the order you choose.
i am not sure my answer is right or wrong, you can try this way.
public ActionResult yourAction(your params)
{
if (!ModelState.IsValid)
{
var errs = from er in tmpErrs
orderby er.Key
select er;
ModelState.Clear();
foreach (var err in errs)
{
ModelState.Add(err);
}
}
// your code
}
Try this filter attribute which orders the model state according to the request's form keys.
using System.Linq;
using System.Web.Mvc;
namespace
{
public class OrderedModelStateAttribute : FilterAttribute, IActionFilter
{
public void OnActionExecuted(ActionExecutedContext filterContext)
{
var modelState = filterContext.Controller.ViewData.ModelState;
var orderedModelState = new ModelStateDictionary();
foreach (var key in filterContext.HttpContext.Request.Form.Keys.Cast<string>()
.Where(
key =>
modelState.ContainsKey(key) && !orderedModelState.ContainsKey(key)))
{
orderedModelState.Add(key, modelState[key]);
}
foreach (var key in modelState.Keys.Where(key => !orderedModelState.ContainsKey(key)))
{
orderedModelState.Add(key, modelState[key]);
}
modelState.Clear();
modelState.Merge(orderedModelState);
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
}
}
}
Use the following code to add the filter to all Actions:
filters.Add(new OrderedModelStateAttribute());
I had this same problem and it wasn't feasible to create a new view model - also if you have custom model binders, these will appear at the end of the validation summary regardless. I expanded on Aphize answer, rather than pass it a list of property names you can pass it the form keys - this will ensure the order is the same as they appear in the form e.g.
ModelState.OrderByKeys(Request.Form.AllKeys);
This worked for me and I created an attribute to do this.. ( I had to do some tweaking to deal with the custom binders but here it is without that):
public class ForceValidationErrorOrderAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var allFormKeys = filterContext.HttpContext.Request.Form.AllKeys;
var modelStateDictionary = filterContext.Controller.ViewData.ModelState;
ModelStateDictionary result = new ModelStateDictionary();
foreach (string key in allFormKeys)
{
if (modelStateDictionary.ContainsKey(key) && !result.ContainsKey(key))
{
result.Add(key, modelStateDictionary[key]);
}
}
foreach (string key in modelStateDictionary.Keys)
{
if (!result.ContainsKey(key))
{
result.Add(key, modelStateDictionary[key]);
}
}
modelStateDictionary.Clear();
modelStateDictionary.Merge(result);
}
}
And then on the controller action method:
[ForceValidationErrorOrder]
public ActionResult Apply(ApplicationViewModel viewModel)