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.
Related
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");
I'm developing a new MVC site for my company & kind of confused as how to create mapping from Domain/POCO objects to ViewModel classes [contains validation] & vice versa. Here's an sample example.
My domain class [just to keep it simple I'hv omitted other properties]:
public partial class Glossary
{
public int Id { get; set; }
public string GlossaryItem { get; set; }
public string Definition { get; set; }
}
my ViewModel class inside my MVC app's model folder [with corrosponding validation]:
public class GlossaryModel
{
[HiddenInput(DisplayValue = false)]
public int Id { get; set; }
[Required(ErrorMessage = "Please enter a GlossaryItem")]
public string GlossaryItem { get; set; }
[Required(ErrorMessage = "Please enter a Definition")]
public string Definition { get; set; }
}
my Automapper configuration for DTO to Domain Model:
protected override void Configure()
{
CreateMap<GlossaryModel, Glossary>();
//....... etc
}
My controller's action method for editing an item:
public class GlossaryController : Controller
{
IGlossaryRepository _glossaryRepository;
IMappingService _mappingService;
public GlossaryController(IGlossaryRepository glossaryRepository, IMappingService autoMapperMappingService)
{
_glossaryRepository = glossaryRepository;
_mappingService = autoMapperMappingService;
}
// .... etc
[HttpPost, ValidateAntiForgeryToken]
public virtual ActionResult Edit(GlossaryModel glossaryModel)
{
if (ModelState.IsValid)
{
var glossary = _mappingService.Map<GlossaryModel, Glossary>(glossaryModel);
if (glossaryModel.Id <= 0)
_glossaryRepository.Add(glossary);
else
_glossaryRepository.Edit(glossary);
_glossaryRepository.Save();
TempData["message"] = string.Format("{0} has been saved", glossaryModel.Definition);
return RedirectToAction("All");
}
return View(glossaryModel);
}
//....etc
}
And it's working fine, but my question is... Now say I need an action that will list down all glossary items like..
public ActionResult All()
{
var allItems = _glossaryRepository.Glossary;
if (allItems.Count() == 0) return View(new List<GlossaryModel>());
// **The below line is the reverse mapping one**
var allItemsModel = _mappingService.Map<IEnumerable<Glossary>, IEnumerable<GlossaryModel>>(allItems);
return View(allItemsModel);
}
But now I need automapper to convert from Domain objects to DTO [from List(Glossary) to List(GlossaryModel)], just opposite of the Edit method, to push the data to the view. So do I again need to map the opposite binding in the automapper config...!! like
protected override void Configure()
{
CreateMap<GlossaryModel, Glossary>(); // Added before for DTO to Domain object
CreateMap<Glossary, GlossaryModel>();// Added for Domain object to DTO
//....... etc
}
Is it a good design to bind both ways? or there's better solution I'm missing, Please help
Thanks,
Sanjay
Jimmy Bogard also asked the same question. But there was enough demand for it that direct support has been added for simple cases like you've listed. In fact, in this answer Jimmy also suggested that there's nothing wrong with it if it works for you. A simple example is:
protected override void Configure()
{
CreateMap<GlossaryModel, Glossary>()
.ReverseMap();
//....... etc
}
Note that ReverseMap doesn't work for more complex mappings. See this answer for more details.
Automapper was build to Domain to ViewModel (Domain to DTO in the manner in which you've described it) mapping
Summed up well by #Marius' answer here What is wrong with two-way mapping?
In some medium sized projects I've used two way mapping and for larger projects I use Domain To View Model mapping and then used a CQRS system for sending the ViewModel values to the underlying persistence store.
When it comes down to it, it is up to you how you choose to use Automapper and what Architectural decisions make sense to you.
The world will not stop rotating if you do 2 way mapping.
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);
}
}
I'm trying to add a form to allow users to comment on posts on my blogging application. So far, I've added a form to the post details view and I can submit comments, adding them to my database correctly. However, I have a problem with displaying validation errors to the user. The comment form is contained within a partial view and is rendered using Html.RenderAction inside the post details view. I'd like to stress that I don't want to use AJAX for this as I'd like to approach this from a progressive enhancement point-of-view.
Here's the relevant posting action:
[HttpPost, Authorize]
public ActionResult AddComment(CommentViewModel newComment)
{
if (ModelState.IsValid)
{
Comment comment = new Comment(_userRepository.GetByUsername(User.Identity.Name));
Mapper.Map(newComment, comment);
_commentRepository.Add(comment);
_postsRepository.CommentAdded(comment.Article);
return RedirectToAction("Index", new { id = newComment.PostID });
}
// What do I do here?
}
I've tried several ways of returning views here but my issue is further complicated by some controller parameter validation that I have going on in the parent action:
//
// GET: /Posts/5/this-is-a-slug
public ActionResult Index(int id, string slug)
{
PostViewModel viewModel = new PostViewModel();
var model = _postsRepository.GetByID(id);
if (model != null)
{
if (slug == null || slug.CompareTo(model.Slug) != 0)
{
return RedirectToActionPermanent("Index", new { id, slug = model.Slug });
}
else
{
_postsRepository.PostVisited(model);
Mapper.Map(model, viewModel);
viewModel.AuthorName = _userRepository.GetById(model.AuthorID);
}
}
return View(viewModel);
}
This action basically mimics how SO's URLs work. If a post ID is supplied, the post is fetched from the database along with a slug which is created when the post is created. If the slug in the URL doesn't match the one in the database, it redirects to include the slug. This is working nicely but it does mean I'm having issues trying to populate my parent viewmodel, which is the following:
public class PostViewModel
{
public int PostID { get; set; }
public string Title { get; set; }
public string Body { get; set; }
public string Slug { get; set; }
public DateTime DatePublished { get; set; }
public int NumberOfComments { get; set; }
public int AuthorID { get; set; }
public string AuthorName { get; set; }
public List<CommentViewModel> Comments { get; set; }
public CommentViewModel NewComment { get; set; }
}
What I was hoping would work is to populate PostViewModel.NewComment, test to see if it has data and then using it to display any model errors. Unfortunately, I'm lost as to how to accomplish that. This question helped me shape my approach, but it didn't quite answer my problem.
Could someone give me a gentle push in the right direction? If my approach seems unreasonable, I'd love to find out why and what a potential fix would be.
Many thanks in advance.
Forgot to fill in my answer here. For anyone that happens to stumble on this, the answer was to use TempData to store the ModelState errors and then repopulating ModelState in the relevant controller action.
Firstly, I declared a key in the controller which would be used to reference the data inside TempData. I decided to base this on the CommentViewModel type as both actions depend on it.
public class PostsController : Controller
{
private static readonly string commentFormModelStateKey = typeof(CommentViewModel).FullName;
// Rest of class.
}
In this first action, the code checks to see if TempData contains data assigned to the key. If it does, it's copied into ModelState.
// GET: /posts/comment
[ChildActionOnly]
public PartialViewResult Comment(PostViewModel viewModel)
{
viewModel.NewComment = new CommentViewModel(viewModel.PostID, viewModel.Slug);
if (TempData.ContainsKey(commentFormModelStateKey))
{
ModelStateDictionary commentModelState = TempData[commentFormModelStateKey] as ModelStateDictionary;
foreach (KeyValuePair<string, ModelState> valuePair in commentModelState)
ModelState.Add(valuePair.Key, valuePair.Value);
}
return PartialView(viewModel.NewComment);
}
This action determines if the ModelState is valid before adding a comment to the database. If the ModelState is not valid, it is copied to TempData, which makes it available to the first action.
// POST: /posts/comment
[HttpPost, Authorize]
public ActionResult Comment(CommentViewModel newComment)
{
if (!ModelState.IsValid)
{
TempData.Add(commentFormModelStateKey, ModelState);
return Redirect(Url.ShowPost(newComment.PostID, newComment.Slug));
}
// Code to add a comment goes here.
}
I have a ViewModel like so:
public class ProductEditModel
{
public string Name { get; set; }
public int CategoryId { get; set; }
public SelectList Categories { get; set; }
public ProductEditModel()
{
var categories = Database.GetCategories(); // made-up method
Categories = new SelectList(categories, "Key", "Value");
}
}
Then I have two controller methods that uses this model:
public ActionResult Create()
{
var model = new ProductEditModel();
return View(model);
}
[HttpPost]
public ActionResult Create(ProductEditModel model)
{
if (ModelState.IsValid)
{
// convert the model to the actual entity
var product = Mapper.Map(model, new Product());
Database.Save(product);
return View("Success");
}
else
{
return View(model); // this is where it fails
}
}
The first time the user goes to the Create view, they are presented with a list of categories. However, if they fail validation, the View is sent back to them, except this time the Categories property is null. This is understandable because the ModelBinder does not persist Categories if it wasn't in the POST request. My question is, what's the best way of keeping Categories persisted? I can do something like this:
[HttpPost]
public ActionResult Create(ProductEditModel model)
{
if (ModelState.IsValid)
{
// convert the model to the actual entity
var product = Mapper.Map(model, new Product());
Database.Save(product);
return View("Success");
}
else
{
// manually populate Categories again if validation failed
model.Categories = new SelectList(categories, "Key", "Value");
return View(model); // this is where it fails
}
}
But this is an ugly solution. How else can I persist it? I can't use a hidden field because it's a collection.
I would use the repository to fetch whatever data is needed and don't think it's an ugly solution:
[HttpPost]
public ActionResult Create(ProductEditModel model)
{
if (!ModelState.IsValid)
{
// manually populate Categories again if validation failed
model.Categories = Repository.GetCategories();
return View(model);
}
// convert the model to the actual entity
var product = Mapper.Map(model, new Product());
Database.Save(product);
// I would recommend you to redirect here
return RedirectToAction("Success");
}
To further refactor this I would recommend you watching the excellent Putting Your Controllers on a Diet video presentation by Jimmy Bogard.
I typically implement my lists (for drop downs) as a readonly property. When the View gets the value the property is self contained on what it needs to return the values.
public SelectList Categories
{
get
{
var categories = Database.GetCategories(); // made-up method
return new SelectList(categories, "Key", "Value");
}
}
If necessary you can grab the currently selected item (i.e. validation failed) from the property containing the id that was posted and bound to the instance of your class.
In my case I have a BaseModel class where I keep all those property list as class attributes.
Like in the following sample:
public IEnumerable<SelectListItem> CountryList
{
get
{
return GetCountryList().Select(
t => new SelectListItem { Text = t.Name, Value = Convert.ToString(t.CountryID) });
}
}
GetCountryList() is a function that ask a Singleton for data. This would only happen once in the app lifecycle
Another way for doing this, and if those lists are pretty big, would be to have a static utility class with the lookup table that returns the SelectListItem.
If you need to access a list that change from time to time then simply dont use a Singleton class.