ModelState.IsValid vs IValidateableObject in MVC3 - data-annotations

so according to Gu IValidatableObject.Validate() should get called when a controller validates it's model (i.e. before ModelState.IsValid) however simply making the model implement IValidatableObject doesn't seem to work, because Validate(..) doesn't get called.
Anyone know if there is something else I have to wire up to get this to work?
EDIT:
Here is the code as requested.
public class LoginModel : IValidatableObject
{
[Required]
[Description("Email Address")]
public string Email { get; set; }
[Required]
[Description("Password")]
[DataType(DataType.Password)]
public string Password { get; set; }
[DisplayName("Remember Me")]
public bool RememberMe { get; set; }
public int UserPk { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var result = DataContext.Fetch( db => {
var user = db.Users.FirstOrDefault(u => u.Email == Email);
if (user == null) return new ValidationResult("That email address doesn't exist.");
if (user.Password != User.CreateHash(Password, user.Salt)) return new ValidationResult("The password supplied is incorrect.");
UserPk = user.UserPk;
return null;
});
return new List<ValidationResult>(){ result };
}
}
The action. ( I don't do anything special in the Controller...)
[HttpPost]
public ActionResult Login(LoginModel model)
{
if (ModelState.IsValid)
{
FormsAuthentication.SetAuthCookie(model.Email, model.RememberMe);
return Redirect(Request.UrlReferrer.AbsolutePath);
}
if (ControllerContext.IsChildAction || Request.IsAjaxRequest())
return View("LoginForm", model);
return View(model);
}
I set a break point on the first line of LoginModel.Validate() and it doesn't seem to get hit.

There isn't anything more than that you just have to add it to the model you're validating. Here's an example of validation
public class User : IValidatableObject {
public Int32 UserID { get; set; }
public string Name { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
//do your validation
return new List<ValidationResult>();
}
}
And your controller would use this model
public ActionResult Edit(User user) {
if (ModelState.IsValid) {
}
}
Hope this helps. Other requirements are .net 4 and data annotations - which you obviously need jsut for ivalidatableobject. Post any issues and we'll see if we can't resolve them - like post your model and your controller...you might be missing something.

Validation using the DefaultModelBinder is a two stage process. First, Data Annotations are validated. Then (and only if the data annotations validation resulted in zero errors), IValidatableObject.Validate() is called. This all takes place automatically when your post action has a viewmodel parameter. ModelState.IsValid doesn't do anything as such. Rather it just reports whether any item in the ModelState collection has non-empty ModelErrorCollection.

Related

ASP.NET MVC Model Mapping from Session

I upload an email through Ajax, parse it and then return some email properties as a partial view. If the user submits the form, the fields go to the post, however at this point I need to also retrieve the original file to store it in the database (so I won't store anything the user uploads, only relevant files). To do this, I store the File Stream (as byte[]) of the email in session. As there might be multiple pages open at the same time I save the emails in session as a list.
I then use the Validate event of the model to extract the byte[] FileStream from Session.
public class EmailModel
{
[Required]
[Display(Name = "TO")]
public string To { get; set; }
[Display(Name = "FROM")]
[Required]
public string From { get; set; }
[Display(Name = "Date")]
public DateTime Data { get; set; }
[Display(Name = "Location of requester")]
public string Location { get; set; }
[Required]
[Display(Name = "Subject of the mail ")]
public string SubjectMail { get; set; }
[Required]
[Display(Name = "Description ")]
public string EmailBodyAsText { get; set; }
public string EmailTypeAsString { get; set; }
public byte[] FileStream { get; set; }
public int ID { get; set; }
public string KendoUniqueId { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
FileStream = ExtractEmailFromSession(KendoUniqueId, HttpContext.Current.Session);
yield return null;
}
private byte[] ExtractEmailFromSession(string emailId, HttpSessionState session)
{
if (!string.IsNullOrEmpty(emailId))
{
var emailList = (List<EmailInSession>)session["emailInSession"];
return emailList.FirstOrDefault(x => x.EmailUniqueId == emailId)?.File;
}
else return null;
}
}
I think this is a bit of smelly code as I retrieve the content from an event that is supposed to be used for validating the data, so I am trying to figure out the proper place to do this. I know I can use IModelBinder to create a custom binder but then I need to bind all the properties which seems overkill for my purpose. Ideally I want to use a custom attribute only for the FileStream property that would extract the data from session and return it to the model. Is there such a possibility?
In case someone else comes to this thread in the future with a similar problem, please have a look below. Please bear in mind I could not make this really generic as this resolves a very specific situation. I created a Model Binder that inherits the default model binder. This allowed me to use the base BindModel method that did most of the job for me (the usual mapping of properties from the context that MVC does for us). Then I went for the specific property that I wanted to populate and extracted the data from the session.
public class SessionModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
try
{
var model = (EmailModel)base.BindModel(controllerContext, bindingContext);
model.FileStream = ExtractEmailFromSession(model.KendoUniqueId, HttpContext.Current.Session);
return model;
}
catch (Exception ex)
{
bindingContext.ModelState.AddModelError("","No data");
return null;
}
}
private byte[] ExtractEmailFromSession(string emailId, HttpSessionState session)
{
if (!string.IsNullOrEmpty(emailId))
{
var emailList = (List<EmailInSession>)session["emailInSession"];
return emailList?.FirstOrDefault(x => x.EmailUniqueId == emailId)?.File;
}
else return null;
}
}
Besides this, the new model binder needed to be registered. This is the place where you need to decide for which models your model binder applies.
public class SessionModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(Type modelType)
{
if (modelType == typeof(EmailModel))
return new SessionModelBinder();
else return null;
}
}
Lastly, I needed to register my new binder provider so MVC knows about it. This is done by adding the line below in global asax (please note you need to put it in the chain at first position - 0, otherwise default model binder would kick in and yours will never be reached):
ModelBinderProviders.BinderProviders.Insert(0, new SessionModelBinderProvider());

Model Validation not working with all properties

I have the following ViewModel:
public class MyViewModel:IValidatableObject
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime? Birthday { get; set; }
public int Status { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (string.IsNullOrWhiteSpace(Name))
yield return new ValidationResult("Please fill the name", new string[] { "Name" });
if (Birthday.HasValue == false)
yield return new ValidationResult("Please fill the birthday", new string[] { "Birthday" });
if(Status <= 0)
yield return new ValidationResult("Please fill the status", new string[] { "Status" });
}
}
Controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Id,Name,Birthday,Status")] MyViewModel myViewModel)
{
if (ModelState.IsValid)
{
db.MyViewModels.Add(myViewModel);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(myViewModel);
}
I would like to display all the validation messages at the same time, however it shows first status and then the other two properties.
This is due to the order in which validation happens. First the ModelBinder does it's job, and if that passes, since you've created a self validating viewmodel by implementing IValidatableObject, the Validate method is called. In the first screenshot the modelbinding process is failing so Validate is never called. In the second screenshot, modelbinding succeeds, but Validate() fails.
You can solve this by using DataAnnotations instead of implementing IValidatableObject like so:
public class MyViewModel:IValidatableObject
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public DateTime Birthday { get; set; }
[Required, Range(0, Int32.MaxValue)]
public int Status { get; set; }
}

ModelState, TempData, ViewModel issue - new viewmodel value automatically gets overridden

This problem is best explained with code:
public async Task<ActionResult> Index()
{
if (TempData.ContainsKey("ModelState"))
{
ModelState.Merge((ModelStateDictionary)TempData["ModelState"]);
var viewModelA= new ViewModelA
{
FirstName = "John"
}
return View(viewModelA);
}
}
[HttpPost]
public async Task<ActionResult> IndexPostActionMethod(ViewModelB model)
{
if (ModelState.IsValid)
{
var result = await DoSomeStuff();
if (result.Succeeded)
{
DoSomeOtherStuff();
}
else
{
AddErrors(result);
}
}
TempData["ModelState"] = ModelState;
return RedirectToAction("Index");
}
The problem here is that when I post to IndexPostActionMethod and DoSomeStuff() returns false for some reason, the form/page is rediplayed by redirecting to Index and saving the ModelState in TempData during that redirect and then once the form/page is redisplayed, the value from ModelState (or where does it get the value from?) gets set as the value for a #Html.TextBoxFor(m => m.FirstName) and not the value that I set here: var viewModelA = new ViewModelA { FirstName = "John"}. Why can't I override the value and where does it get it from? It's like the framework is automatically updating viewModelA with whatever is in the ModelStatDictionary eventhough I create a new ViewModelA with a new value.
Don't do this. The way you should be handling a GET-POST-Redirect cycle is:
public async Task<ActionResult> Index()
{
var model = new ViewModel
{
FirstName = "John"
}
return View(model);
}
[HttpPost]
public async Task<ActionResult> Index(ViewModel model)
{
if (ModelState.IsValid)
{
var result = await DoSomeStuff();
if (result.Succeeded)
{
DoSomeOtherStuff();
}
else
{
AddErrors(result);
}
}
return View(model);
}
ModelState rules all. Regardless of what you pass to the view explicitly, if the value is present in ModelState it will take precedence. Using TempData to pass the ModelState to another view is such a code smell, "smell" doesn't even really cover it.
If your only goal is to use one view model for the GET action and another, different, view model for the POST action, then you should really just stop and ask yourself seriously why you need to do that. In all the years I've worked with MVC, I've never had to do that, never needed to do that, and I can't imagine a scenario where I would need to do that.
UPDATE
So, based on the scenario you laid out in the comments, I would design it like this:
public class IndexViewModel
{
public SomeTabViewModel SomeTab { get; set; }
public AnotherTabViewModel AnotherTab { get; set; }
public FooTabViewModle FooTab { get; set; }
}
public class SomeTabViewModel
{
[Required]
public string SomeRequiredField { get; set; }
}
public class AnotherTabViewModel
{
[Required]
public string AnotherRequiredField { get; set; }
}
public class FooTabViewModel
{
[Required]
public string FooRequiredField { get; set; }
}
Basically, you break up the individual POST pieces into view models that altogether make up one big view model. Then, model validation only follows actual internal view model instances of the container. So, for example if you postback to FooTab.FooRequiredField only, then only FooTab is instantiated. The other two view model properties will remain null and will not be validated internally (i.e. the fact that they have required fields doesn't matter).

TryValidate and manually set values in cotroller (ASP MVC 3)

I have a "New user" form both for admins and for regular users. Both form use the RegisterModel
public class RegisterModel
{
[Required]
public string Name { get; set; }
public string Email { get; set; }
[Required]
public string Password { get; set; }
}
The difference is that on my front end "New user" page I want users to provide their own password. But in back end, I want the system to generate the password.
Since I use the same RegisterModel for both forms, I get a validateion error in the back end saying Password is required..
I thought, I could solve this by adding this to my controller:
[HttpPost]
public ActionResult New(RegisterModel model)
{
model.Password = Membership.GeneratePassword(6, 1);
if (TryValidateModel(model))
{
// Do stuff
}
return View(model);
}
But I still get the error message Password is required.. Why is this the issue when I do call TryValidate in my controller?
What would be best practice for this issue, create a separate RegisterModelBackEnd or are there any other solutions to this?
When updating model manually, you do not need to use it as parameter in Action. Also, use this overload that lets you specify only the properties on which binding will occur.
protected internal bool TryUpdateModel<TModel>(
TModel model,
string[] includeProperties
)
where TModel : class
So, the working code will be
[HttpPost]
public ActionResult New()
{
RegisterModel model = new RegisterModel();
model.Password = Membership.GeneratePassword(6, 1);
if (TryValidateModel(model, new string[] {"Name", "Email"}))
{
// Do stuff
}
return View(model);
}
You can make this even simpler, using BindAttribute
[HttpPost]
public ActionResult New([Bind(Exlude="Password")]RegisterModel model)
{
if(ModelState.IsValid)
{
model.Password = Membership.GeneratePassword(6, 1);
// Do Stuff
}
return View(model);
}
And finally simplest and the best way
Define separate view models

Help improving (refactoring) my code. Automapper - EF - asp.net mvc-3

I have this 4 models - 2 Domain Models and 2 DTOs
public class Project
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Task> Tasks { get; set; }
}
public class Task
{
public int ID { get; set; }
public virtual int ProjectID { get; set; }
public string Name { get; set; }
public virtual Project Project { get; set; }
}
public class ProjectDTO
{
[Required]
public string Name { get; set; }
public List<TaskDTO> Tasks { get; set; }
}
public class TaskDTO
{
[Required]
public string Name { get; set; }
public int ID { get; set; }
public bool MarkRemove { get; set; }
}
Heres my automapper configuration
Mapper.CreateMap<Project, ProjectDTO>();
Mapper.CreateMap<ProjectDTO, Project>().ForMember(p =>p.ID, opt=>opt.Ignore()).ForMember(p=>p.Tasks, opt=>opt.Ignore());
Mapper.CreateMap<Task, TaskDTO>();
Mapper.CreateMap<TaskDTO, Task>().ForMember(task=>task.ProjectID, opt=>opt.Ignore()).ForMember(task=>task.Project, opt=>opt.Ignore());
Heres my HttpPost Edit action
[HttpPost]
public ActionResult Edit(int id, ProjectDTO p)
{
if (ModelState.IsValid)
{
var dbProject = db.Projects.Where(pr => pr.ID == id).Single();
Mapper.Map(p, dbProject);
foreach (var task in p.Tasks)
{
Task dbTask;
try
{
dbTask = dbProject.Tasks.Where(t => t.ID == task.ID).Single();
}
catch
{
dbTask = new Task();
Mapper.Map(task, dbTask);
dbProject.Tasks.Add(dbTask);
}
if (task.MarkRemove)
{
db.Tasks.Remove(dbTask);
}
else {
Mapper.Map(task, dbTask);
}
}
db.Entry(dbProject).State = EntityState.Modified;
db.SaveChanges();
TempData["Success"] = "Modelo Valido";
return RedirectToAction("Index");
}
return View(p);
}
Im not completely happy with this but I dont think there is a much cleaner approach to handling this somewhat complex scenario....
now that it is working I would like to at least refactor this to use repository pattern or something in a way that the controller action is not that convoluted.. this will eventually be production code :s
can anyone give me some advice on how to refactor this?
please help.
I would use a service layer, like this:
public interface IProjectsService
{
void RemoveTasks(int projectId, IEnumerable<int> taskIdsToRemove);
}
and then the controller would depend on this service layer:
public class ProjectsController : Controller
{
private readonly IProjectsService _service;
public ProjectsController(IProjectsService service)
{
_service = service;
}
public ActionResult Edit(int id)
{
// TODO: Add methods to your service layer
// allowing to retrieve projects, then map
// the resulting project into a view model
throw new NotImplementedException();
}
[HttpPost]
public ActionResult Edit(int id, ProjectDTO p)
{
if (!ModelState.IsValid)
{
return View(p);
}
var taskIdsToRemove = p.Tasks.Where(x => x.MarkRemove).Select(x => x.ID);
_service.RemoveTasks(id, taskIdsToRemove);
TempData["Success"] = "Modelo Valido";
return RedirectToAction("Index");
}
}
This way the controller logic is more weakly coupled to the way we do data access. That's an implementation detail that a controller should never have to worry about.
As a further improvement to the RemoveTasks method you could make it return a boolean indicate the success or failure of the operation along with an error message so that the Edit action could redisplay the view and show the error in case something goes wrong.
Now as far as this service layer is concerned, the RemoveTasks method is a business operation that could be built upon multiple CRUD operations with some repository. So this service layer would itself depend on a repository. It is only this repository that will have to know about EF or whatever you are using to do your data access.
So basically everytime I see someone asking a question about ASP.NET MVC and EF at the same time, for me, those are two completely different questions. ASP.NET MVC should not know anything about EF. EF should be buried away behind an abstraction of a repository.
I realize this question has been inactive for a while, but I had the same problem, and though I would post my solution for anyone else struggling with the nested model not being saved during edit.
[HttpPost]
public ActionResult Edit(ProjectDTO p)
{
if (ModelState.IsValid)
{
// *****MODIFIED CODE HERE********
for (int i = 0; i < p.Tasks.Count; i++)
{
db.Entry(p.Tasks[i]).State = EntityState.Modified;
}
// *************************************
db.Entry(dbProject).State = EntityState.Modified;
db.SaveChanges();
TempData["Success"] = "Modelo Valido";
return RedirectToAction("Index");
}
return View(p);
}
Basically, you want to set State of each nested model to Modified as well as the root model.

Resources