Hi folks, I've got an old MVC 1 application we're trying to update to MVC 3. We're hitting an issue with the TryUpdateModel method using a FormCollection. I have a bunch of validation code on my various data object's properties and previously (in MVC 1) only
the ones listed in the input FormCollection were being updated by the TryUpdateModel, but now (in MVC 3) with the same FormCollection every property's validation code is being hit.
Is this something that's changed, fundamentally?
I know that it's better, now, to use strongly-typed form post objects (in my latest project I do this) but this is a huge overhaul of my application and though I would like to do this later, I don't have time to run through the whole set of changes now.
An example of my data objects properties are:- ID, Priority, RequiresReview, DueDate, Country, Description, ItemNumber - my FormCollection contains just Priority, RequiresReview, DueDate but the ItemNumber and Country validation code is now being called
when I do the TryUpdateModel.
Please remember to mark replies as answers if you find them useful =8)
Yea, IIRC, that was a breaking change -- the signature to TryUpdateModel now accepts an IValueProvider. Fortunately, the FormCollection has a ToValueProvider API (or something like that) that you can pass instead of the FormCollection itself.
I've set up a simple test example and that doesn't work - it could be due to the way I'm implementing the validation. This was from a book on MVC1 and so may be different to MVC 3 methods (or other methods). Here's my example data object (I inject sample data in the constructor) - on the TryUpdateModel above (obj is this data type) the property set methods are only called when updated, but the this[string propName] is called for every property in MVC 3, but only for the ones in the collection in MVC 1:-
public class TestUpdateDataObject : IDataErrorInfo {
public TestUpdateDataObject()
{
_textValue1 = "text 1";
_textValue2 = "text 2";
_int1 = 10;
_int2 = 20;
}
private string _textValue1;
private string _textValue2;
private int _int1;
private int _int2;
public string TextValue1
{
get
{
return _textValue1;
}
set
{
if (_textValue1.Trim() != value.ToString().Trim())
_textValue1 = value.ToString().Trim();
}
}
public string TextValue2
{
get
{
return _textValue2;
}
set
{
if (_textValue2.Trim() != value.ToString().Trim())
_textValue2 = value.ToString().Trim();
}
}
public int Int1
{
get
{
return _int1;
}
set
{
if (_int1 != value)
_int1 = value;
}
}
public int Int2
{
get
{
return _int2;
}
set
{
if (_int2 != value)
_int2 = value;
}
}
public string this[string propName]
{
get
{
var x = propName;
return null;
}
}
public string Error { get { return null; } }
}
Please remember to mark replies as answers if you find them useful =8)
Actually, why do you need to use TryUpdateModel? Do you need to be in control of the object creation? That's the main reason to use UpdateModel/TryUpdateModel. Also by passing the FormCollection's value provider, you're narrowing what potential inputs from
the http request to use for model binding, meaning no query string or route values will be used. Maybe that's ok, maybe that's not.
Maybe just try accepting the model as the paramater to your action method and avoid calling TryUpdateModel.
(if I understand your query correctly) That's purely an example object that can be used to recreate the problem - my actual objects map to a complex set of database tables, hence why I am using TryUpdateModel to take input from my update HTML forms to update
the backend database data.
Please remember to mark replies as answers if you find them useful =8)
Change in property validation behavior for classes that implement IDataErrorInfo
Every property for model objects that use IDataErrorInfo to perform validation is validated, regardless of whether a new value was set. In ASP.NET MVC 1.0, only properties that had new values set would be validated. In ASP.NET MVC 2, the Error property of IDataErrorInfo
is called only if all the property validators were successful.
The question is, how can I overcome this without a complete rewrite of my objects - that ToValueProvider method doesn't seem to make any difference, and by the looks of things it shouldn't as it's the TryUpdateModel method that's firing every property, regardless.
Can't understand why they're doing this as most of the time they'll just be revalidating data that hasn't changed and has already been validated. I suppose, on the other hand, individual property validation could depend on other properties so there's the
other side of the "validate all" argument, but I reckon it will result in a lot of unneccessary code calls, which could result in speed, and possibly database call, overheads.
Please remember to mark replies as answers if you find them useful =8)
Sorry -- I was focused on the TryUpdateModel & FormCollection change when you were having a validation issue. Sorry for misunderstanding.
As for the validation interface, you don't have to validate everything. Just validate the stuff you want to -- they pass in the property name and you can just return null for the ones you don't want to validate.
Another idea is to implement IValidateableObject -- this is the replacement for IDataErrorInfo in MVC3. Not sure this will solve your problem tho.
Ah, IValidateable may be the answer, unless it, too, implements validation in the same way - which (thinking twice about it) it may well do. I'll have a look into it and report back - thanks.
Please remember to mark replies as answers if you find them useful =8)
"And whoever is removed away from the Fire and admitted to Paradise, he indeed is successful." (The Holy Quran)
Excellent Windows VPS Hosting Imran Baloch MVP, MVB, MCP, MCTS, MCPD
Will that solution only work with strongly typed model objects? I am trying some test code:-
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel;
using System.Web.UI;
namespace TestMVC3.Models
{
public class TestErrorValidatorModel : IDataErrorInfo
{
public TestErrorValidatorModel()
{
Parent = new TestErrorValidatorParentModel();
}
public string TypeString { get; set; }
public string TestStringProperty1 { get; set; }
public string TestStringProperty2 { get; set; }
public TestErrorValidatorParentModel Parent { get; set; }
#region IDataErrorInfo Members
public string Error
{
get { return null; }
}
public string this[string columnName]
{
get
{
switch (columnName)
{
case "TypeString":
if (!(new List<string> { "A", "B", "C", "D" }).Contains(TypeString))
return "Invalid type";
break;
case "TestStringProperty1":
if (TypeString != "B")
return "Can only update property 1 for B types";
break;
case "TestStringProperty2":
return "Cannot update property 2";
break;
default:
break;
}
return null;
}
}
#endregion
}
public class TestErrorValidatorParentModel : IDataErrorInfo
{
public string CanUpdate { get; set; }
public string CannotUpdate { get; set; }
public string MustBeA { get; set; }
#region IDataErrorInfo Members
public string Error
{
get { return null; }
}
public string this[string columnName]
{
get
{
switch (columnName)
{
case "CannotUpdate":
return "You cannot update this parent property";
break;
case "MustBeA":
if (MustBeA != "A")
return "This must be set to A";
break;
default:
break;
}
return null;
}
}
#endregion
}
}
with controller code of
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using TestMVC3.Models;
using TestMVC3.HelperClasses;
namespace TestMVC3.Controllers
{
public class TestErrorValidatorController : Controller
{
//
// GET: /TestErrorValidator/
public ActionResult Update(string id)
{
ViewData["Fields"] = id;
return View();
}
[HttpPost]
[ValidateOnlyIncomingValuesAttribute]
public ActionResult Update(string id, FormCollection collection)
{
TestErrorValidatorModel model = new TestErrorValidatorModel();
TryUpdateModel(model);
if (ModelState.IsValid)
return RedirectToAction("Update", new { @id = id });
else
{
ViewData["Fields"] = id;
return View();
}
}
}
}
I have the id, in this case, so that I can easily control which field are shown for the update, to be able to test different circumstances more easily, but it also adds in realistic, to my application, routing data. Debugging the ValidateOnlyIncomingValuesAttribute code
from that example reveals the modelState contents to just be "id", so no keys are getting cleared.
Please remember to mark replies as answers if you find them useful =8)
Mad-Halfling
Participant
1438 Points
729 Posts
Changes in Update Model Using FormCollection From MVC 1 To MVC 3 - Now Updates All Properties?
Feb 28, 2012 03:28 PM|LINK
Hi folks, I've got an old MVC 1 application we're trying to update to MVC 3. We're hitting an issue with the TryUpdateModel method using a FormCollection. I have a bunch of validation code on my various data object's properties and previously (in MVC 1) only the ones listed in the input FormCollection were being updated by the TryUpdateModel, but now (in MVC 3) with the same FormCollection every property's validation code is being hit.
Is this something that's changed, fundamentally?
I know that it's better, now, to use strongly-typed form post objects (in my latest project I do this) but this is a huge overhaul of my application and though I would like to do this later, I don't have time to run through the whole set of changes now.
An example of my data objects properties are:- ID, Priority, RequiresReview, DueDate, Country, Description, ItemNumber - my FormCollection contains just Priority, RequiresReview, DueDate but the ItemNumber and Country validation code is now being called when I do the TryUpdateModel.
BrockAllen
All-Star
27574 Points
4912 Posts
MVP
Re: Changes in Update Model Using FormCollection From MVC 1 To MVC 3 - Now Updates All Properties...
Feb 28, 2012 03:44 PM|LINK
Yea, IIRC, that was a breaking change -- the signature to TryUpdateModel now accepts an IValueProvider. Fortunately, the FormCollection has a ToValueProvider API (or something like that) that you can pass instead of the FormCollection itself.
DevelopMentor | http://www.develop.com
thinktecture | http://www.thinktecture.com/
Mad-Halfling
Participant
1438 Points
729 Posts
Re: Changes in Update Model Using FormCollection From MVC 1 To MVC 3 - Now Updates All Properties...
Feb 29, 2012 02:24 PM|LINK
Do you mean something like
?
I've set up a simple test example and that doesn't work - it could be due to the way I'm implementing the validation. This was from a book on MVC1 and so may be different to MVC 3 methods (or other methods). Here's my example data object (I inject sample data in the constructor) - on the TryUpdateModel above (obj is this data type) the property set methods are only called when updated, but the this[string propName] is called for every property in MVC 3, but only for the ones in the collection in MVC 1:-
BrockAllen
All-Star
27574 Points
4912 Posts
MVP
Re: Changes in Update Model Using FormCollection From MVC 1 To MVC 3 - Now Updates All Properties...
Feb 29, 2012 02:46 PM|LINK
I don't see anything alarming in your model.
Actually, why do you need to use TryUpdateModel? Do you need to be in control of the object creation? That's the main reason to use UpdateModel/TryUpdateModel. Also by passing the FormCollection's value provider, you're narrowing what potential inputs from the http request to use for model binding, meaning no query string or route values will be used. Maybe that's ok, maybe that's not.
Maybe just try accepting the model as the paramater to your action method and avoid calling TryUpdateModel.
DevelopMentor | http://www.develop.com
thinktecture | http://www.thinktecture.com/
Mad-Halfling
Participant
1438 Points
729 Posts
Re: Changes in Update Model Using FormCollection From MVC 1 To MVC 3 - Now Updates All Properties...
Mar 02, 2012 08:42 AM|LINK
(if I understand your query correctly) That's purely an example object that can be used to recreate the problem - my actual objects map to a complex set of database tables, hence why I am using TryUpdateModel to take input from my update HTML forms to update the backend database data.
Mad-Halfling
Participant
1438 Points
729 Posts
Re: Changes in Update Model Using FormCollection From MVC 1 To MVC 3 - Now Updates All Properties...
Mar 02, 2012 11:19 AM|LINK
Found it - it is a breaking change:-
Change in property validation behavior for classes that implement IDataErrorInfo
Every property for model objects that use IDataErrorInfo to perform validation is validated, regardless of whether a new value was set. In ASP.NET MVC 1.0, only properties that had new values set would be validated. In ASP.NET MVC 2, the Error property of IDataErrorInfo is called only if all the property validators were successful.
The question is, how can I overcome this without a complete rewrite of my objects - that ToValueProvider method doesn't seem to make any difference, and by the looks of things it shouldn't as it's the TryUpdateModel method that's firing every property, regardless. Can't understand why they're doing this as most of the time they'll just be revalidating data that hasn't changed and has already been validated. I suppose, on the other hand, individual property validation could depend on other properties so there's the other side of the "validate all" argument, but I reckon it will result in a lot of unneccessary code calls, which could result in speed, and possibly database call, overheads.
BrockAllen
All-Star
27574 Points
4912 Posts
MVP
Re: Changes in Update Model Using FormCollection From MVC 1 To MVC 3 - Now Updates All Properties...
Mar 02, 2012 02:09 PM|LINK
Sorry -- I was focused on the TryUpdateModel & FormCollection change when you were having a validation issue. Sorry for misunderstanding.
As for the validation interface, you don't have to validate everything. Just validate the stuff you want to -- they pass in the property name and you can just return null for the ones you don't want to validate.
Another idea is to implement IValidateableObject -- this is the replacement for IDataErrorInfo in MVC3. Not sure this will solve your problem tho.
DevelopMentor | http://www.develop.com
thinktecture | http://www.thinktecture.com/
Mad-Halfling
Participant
1438 Points
729 Posts
Re: Changes in Update Model Using FormCollection From MVC 1 To MVC 3 - Now Updates All Properties...
Mar 05, 2012 08:38 AM|LINK
Ah, IValidateable may be the answer, unless it, too, implements validation in the same way - which (thinking twice about it) it may well do. I'll have a look into it and report back - thanks.
imran_ku07
All-Star
45815 Points
7698 Posts
MVP
Re: Changes in Update Model Using FormCollection From MVC 1 To MVC 3 - Now Updates All Properties...
Mar 05, 2012 05:02 PM|LINK
Yes, See this. You may be quickly fix this using.
Excellent Windows VPS Hosting
Imran Baloch MVP, MVB, MCP, MCTS, MCPD
Mad-Halfling
Participant
1438 Points
729 Posts
Re: Changes in Update Model Using FormCollection From MVC 1 To MVC 3 - Now Updates All Properties...
Mar 07, 2012 03:39 PM|LINK
Will that solution only work with strongly typed model objects? I am trying some test code:-
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.ComponentModel; using System.Web.UI; namespace TestMVC3.Models { public class TestErrorValidatorModel : IDataErrorInfo { public TestErrorValidatorModel() { Parent = new TestErrorValidatorParentModel(); } public string TypeString { get; set; } public string TestStringProperty1 { get; set; } public string TestStringProperty2 { get; set; } public TestErrorValidatorParentModel Parent { get; set; } #region IDataErrorInfo Members public string Error { get { return null; } } public string this[string columnName] { get { switch (columnName) { case "TypeString": if (!(new List<string> { "A", "B", "C", "D" }).Contains(TypeString)) return "Invalid type"; break; case "TestStringProperty1": if (TypeString != "B") return "Can only update property 1 for B types"; break; case "TestStringProperty2": return "Cannot update property 2"; break; default: break; } return null; } } #endregion } public class TestErrorValidatorParentModel : IDataErrorInfo { public string CanUpdate { get; set; } public string CannotUpdate { get; set; } public string MustBeA { get; set; } #region IDataErrorInfo Members public string Error { get { return null; } } public string this[string columnName] { get { switch (columnName) { case "CannotUpdate": return "You cannot update this parent property"; break; case "MustBeA": if (MustBeA != "A") return "This must be set to A"; break; default: break; } return null; } } #endregion } }with controller code of
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using TestMVC3.Models; using TestMVC3.HelperClasses; namespace TestMVC3.Controllers { public class TestErrorValidatorController : Controller { // // GET: /TestErrorValidator/ public ActionResult Update(string id) { ViewData["Fields"] = id; return View(); } [HttpPost] [ValidateOnlyIncomingValuesAttribute] public ActionResult Update(string id, FormCollection collection) { TestErrorValidatorModel model = new TestErrorValidatorModel(); TryUpdateModel(model); if (ModelState.IsValid) return RedirectToAction("Update", new { @id = id }); else { ViewData["Fields"] = id; return View(); } } } }and a view of
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<dynamic>" %> <!DOCTYPE html> <html> <head runat="server"> <title>Update</title> </head> <body> <div> <%: Html.ActionLink("Home", "", "Home") %> </div> <div> <% using (Html.BeginForm()) { %> <div style="color: Red;"> <%: Html.ValidationSummary(false)%> </div> <% if (ViewData["Fields"] != null && ViewData["Fields"].ToString().Trim().Length > 0 && ViewData["Fields"].ToString().Contains("T")) { %> <div> <label>Type</label> <input id="TypeString" name="TypeString" type="text" /> </div> <% } %> <% if (ViewData["Fields"] != null && ViewData["Fields"].ToString().Trim().Length > 0 && ViewData["Fields"].ToString().Contains("1")) { %> <div> <label>Property 1</label> <input id="TestStringProperty1" name="TestStringProperty1" type="text" /> </div> <% } %> <% if (ViewData["Fields"] != null && ViewData["Fields"].ToString().Trim().Length > 0 && ViewData["Fields"].ToString().Contains("2")) { %> <div> <label>Property 2</label> <input id="TestStringProperty2" name="TestStringProperty2" type="text" /> </div> <% } %> <div> <label>Can Update</label> <%: Html.TextBox("Parent.CanUpdate") %> </div> <div> <label>Must Be A</label> <%: Html.TextBox("Parent.MustBeA") %> </div> <input type="submit" value="Save" /> <% } %> </div> </body> </html>I have the id, in this case, so that I can easily control which field are shown for the update, to be able to test different circumstances more easily, but it also adds in realistic, to my application, routing data. Debugging the ValidateOnlyIncomingValuesAttribute code from that example reveals the modelState contents to just be "id", so no keys are getting cleared.