I'm struggling to find a clean solution to the following issue. As an example I have the following model:
public class Person
{
[Required]
public string Name { get; set; }
[Required]
public string Surname { get; set; }
[Required]
public string FullName { get; set; }
}
In my action method I have the following:
[HttpPost]
public ActionResult Create(Person person)
{
person.FullName = String.Format("{0} {1}", person.Name, person.Surname);
if (ModelState.IsValid)
{
// Save to DB
return RedirectToAction("Index");
}
return View(person);
}
The problem is that ModelState will never be valid as FullName is validated before it is assigned in code. The same issue occurs if I user UpdateModel/TryUpdateModel.
How are you guys handling scenarios like this? I could remove the required validation on FullName and ensure I always assign it in code, but that just feels nasty. Any ideas?
I think a pretty slick way to solve this is with a custom model binder for the Person type.
The model binder:
public class PersonModelBinder : DefaultModelBinder
{
protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var person = (Person)bindingContext.Model;
person.FullName = string.Format("{0} {1}", person.Name, person.Surname);
}
}
Then register the binder for the Person type in Global.asax.cs:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
ModelBinders.Binders.Add(typeof(Models.Person), new PersonModelBinder());
RegisterRoutes(RouteTable.Routes);
}
Now every time Person is binded, the FullName will be calculated – no need to repeat the code in every controller. Furthermore this occurs before model validation, so it will pass.
I think it would be better not to have to set this in your controller. In your model, you could have the FullName property accessor do the work:
public string Name { get; set; }
public string Surname {get; set; }
public string FullName
{
get { return string.Format("{0} {1}", Name, Surname); }
}
Hi Bradley, I should've used a different example than a Person class with FullName property I totally agree
with you that given the FullName example I would typically implement it as you stated. Unfortunately the actual model and property I’m using in my project is a lot more complex. The property in question is derived not only from concatenating other properties
of the class, but includes data from the DB and the final value need to persist to the DB for various other reasons. So unfortunately a simple getter property will not suffice.
I think a pretty slick way to solve this is with a custom model binder for the Person type.
Nick, this worked beautifully, thank you very much. There was only one small change I had to make, seems like validation occurs during DefaultModelBinder.OnModelUpdated event, so I had to add base.OnModelUpdated(controllerContext, bindingContext) at the
end otherwise validation was skipped altogether. The final model binder class now looks like:
public class PersonModelBinder : DefaultModelBinder
{
protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var person = (Person)bindingContext.Model;
person.FullName = string.Format("{0} {1}", person.Name, person.Surname);
base.OnModelUpdated(controllerContext, bindingContext);
}
}
Beyers
Member
5 Points
7 Posts
MVC 2 Validation Question
Mar 11, 2010 04:02 PM|LINK
Hi all,
I'm struggling to find a clean solution to the following issue. As an example I have the following model:
public class Person { [Required] public string Name { get; set; } [Required] public string Surname { get; set; } [Required] public string FullName { get; set; } }In my action method I have the following:
[HttpPost] public ActionResult Create(Person person) { person.FullName = String.Format("{0} {1}", person.Name, person.Surname); if (ModelState.IsValid) { // Save to DB return RedirectToAction("Index"); } return View(person); }The problem is that ModelState will never be valid as FullName is validated before it is assigned in code. The same issue occurs if I user UpdateModel/TryUpdateModel.
How are you guys handling scenarios like this? I could remove the required validation on FullName and ensure I always assign it in code, but that just feels nasty. Any ideas?
mvc - 2 RC2 validation
Nick Riggs
Member
502 Points
81 Posts
Re: MVC 2 Validation Question
Mar 11, 2010 05:16 PM|LINK
I think a pretty slick way to solve this is with a custom model binder for the Person type.
The model binder:
public class PersonModelBinder : DefaultModelBinder { protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext) { var person = (Person)bindingContext.Model; person.FullName = string.Format("{0} {1}", person.Name, person.Surname); } }Then register the binder for the Person type in Global.asax.cs:
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); ModelBinders.Binders.Add(typeof(Models.Person), new PersonModelBinder()); RegisterRoutes(RouteTable.Routes); }Now every time Person is binded, the FullName will be calculated – no need to repeat the code in every controller. Furthermore this occurs before model validation, so it will pass.
bradleylandi...
Member
233 Points
109 Posts
Re: MVC 2 Validation Question
Mar 11, 2010 05:44 PM|LINK
I think it would be better not to have to set this in your controller. In your model, you could have the FullName property accessor do the work:
public string Name { get; set; }
public string Surname {get; set; }
public string FullName
{
get { return string.Format("{0} {1}", Name, Surname); }
}
bradwils
Contributor
5779 Points
691 Posts
Microsoft
Re: MVC 2 Validation Question
Mar 11, 2010 10:14 PM|LINK
Not only that, but it would never fail anyway because [Required] only says "cannot be null". It'll always have a value. :)
I agree, not settable is the way to go.
Beyers
Member
5 Points
7 Posts
Re: MVC 2 Validation Question
Mar 12, 2010 06:07 AM|LINK
Hi Bradley, I should've used a different example than a Person class with FullName property
I totally agree
with you that given the FullName example I would typically implement it as you stated. Unfortunately the actual model and property I’m using in my project is a lot more complex. The property in question is derived not only from concatenating other properties
of the class, but includes data from the DB and the final value need to persist to the DB for various other reasons. So unfortunately a simple getter property will not suffice.
Beyers
Member
5 Points
7 Posts
Re: MVC 2 Validation Question
Mar 12, 2010 06:38 AM|LINK
Nick, this worked beautifully, thank you very much. There was only one small change I had to make, seems like validation occurs during DefaultModelBinder.OnModelUpdated event, so I had to add base.OnModelUpdated(controllerContext, bindingContext) at the end otherwise validation was skipped altogether. The final model binder class now looks like:
public class PersonModelBinder : DefaultModelBinder { protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext) { var person = (Person)bindingContext.Model; person.FullName = string.Format("{0} {1}", person.Name, person.Surname); base.OnModelUpdated(controllerContext, bindingContext); } }