Partial Validation with Data Annotations in ASP.NET MVC
Introduction
This article is a follow-up to Andy West's blog post about performing a conditional validation when using .NET data annotations on a model in MVC.
Now I am not going to go into the arguments about the use of DTOs vs 'real' model objects; or using separate vs shared model objects across different views. As many others have noted (from what I've seen on the Web), if you are working with 'real' model objects using data annotations, there is a clear need to be able to exclude certain validations depending on the specific scenario.
The Scenario
Let's look at a simple product/category model:
// C#
public class Category
{
[Required]
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Description { get; set; }
}
public class Product
{
[Required]
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Description { get; set; }
[Required]
public Category Category { get; set; }
[Required]
public decimal Price { get; set; }
}
' Visual Basic
Public Class Category
<Required()>
Public Property Id As Integer
<Required()>
Public Property Name As String
<Required()>
Public Property Description As String
End Class
Public Class Product
<Required()>
Public Property Id As Integer
<Required()>
Public Property Name As String
<Required()>
Public Property Description As String
<Required()>
Public Property Category As Category
<Required()>
Public Property Price As Decimal
End Class
As you can see, this is a very simple model where all properties on the two classes are decorated with the Required
attribute.
Now let's take a simple action to create a new product:
// C#
[HttpPost]
public ActionResult Create(Product product)
{
if (ModelState.IsValid)
{
// Do something here, probably put the product in some database.
return View("SuccessPage");
}
else
{
// Do something else here, probably return to the view showing the errors.
return View();
}
}
' Visual Basic
<HttpPost()>
Public Function Create(ByVal product As Product) As ActionResult
If ModelState.IsValid
' Do something here, probably put the product in some database.
Return View("SuccessPage")
Else
' Do something else here, probably return to the view showing the errors.
Return View
End If
End Function
Now our data annotations specify that a Product
must have a Category
that, in turn, must have values for its Id
, Name
, and Description
properties. However, when we post back to the above action, do we really need to specify the name and description for the product's category? The answer is probably not. After all it is likely that at the time of product creation, the category already exists in our data store and that the user picked the category from a drop-down list (or similar) of current categories. In that case we are not really interested in the category's name and description. We are only really interested in the category's ID, so we can assigned it to the product and thus satisfy any data integrity constraints (e.g. database foreign keys) we have on our data.
However if we just post back the category ID, the model-state validation will fail because of the Required
attributes on the Name
and Description
properties. However, we do not want to get rid of these attributes because elsewhere on the system, on the category creation view for example, we want to make sure the user specifies a name and description for any new categories they create.
So, what are we to do?
The Solution
This is where the IgnoreModelErrors
attribute comes in. It allows us to specify a comma-separated string of model-state keys for which we wish to ignore any validation errors. So, in our example, we could decorate our action like this:
// C#
[HttpPost]
[IgnoreModelErrors("Category.Name, Category.Description")]
public ActionResult Create(Product product)
{
// Code omitted for brevity.
}
' Visual Basic
<HttpPost()>
<IgnoreModelErrors("Category.Name, Category.Description")>
Public Function Create(ByVal product As Product) As ActionResult
' Code omitted for brevity.
End Function
Additional Options
The IgnoreModelErrors
attribute has a couple of additional options worth mentioning:
Firstly, the attribute supports the '*' wildcard when specifying model-state keys. So if, for example, we used "Category.*"
, validation errors for any sub-property of the Category
property will be ignored. However, if instead we used "*.Description"
, validation errors for the Description
sub-property of any property will be ignored.
Secondly, the attribute also supports collections: For example, if the Product
contained a property Categories
which returned a IList<Category>
, we could use "Categories[0].Description"
to specify validation errors for the Description
property of the first Category
object in the list. We can use 1, 2, 3 etc. as the indexer to specify the second, third, fourth etc. Category
as required. Omitting the indexer, e.g.: "Categories[].Description
specifies all validation errors for the Description
property of any Category
object in the list.
The Code
The code for the IgnoreModelErrors
attribute is shown below:
// C#
public class IgnoreModelErrorsAttribute : ActionFilterAttribute
{
private string keysString;
public IgnoreModelErrorsAttribute(string keys)
: base()
{
this.keysString = keys;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
ModelStateDictionary modelState = filterContext.Controller.ViewData.ModelState;
string[] keyPatterns = keysString.Split(new char[] { ',' },
StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < keyPatterns.Length; i++)
{
string keyPattern = keyPatterns[i]
.Trim()
.Replace(@".", @"\.")
.Replace(@"[", @"\[")
.Replace(@"]", @"\]")
.Replace(@"\[\]", @"\[[0-9]+\]")
.Replace(@"*", @"[A-Za-z0-9]+");
IEnumerable<string> matchingKeys = _
modelState.Keys.Where(x => Regex.IsMatch(x, keyPattern));
foreach (string matchingKey in matchingKeys)
modelState[matchingKey].Errors.Clear();
}
}
}
' Visual Basic
Public Class IgnoreModelErrorsAttribute
Inherits ActionFilterAttribute
Private keysString As String
Public Sub New(ByVal keys As String)
MyBase.New()
Me.keysString = keys
End Sub
Public Overrides Sub OnActionExecuting(ByVal filterContext As ActionExecutingContext)
Dim modelState As ModelStateDictionary = filterContext.Controller.ViewData.ModelState
Dim keyPatterns As String() = _
keysString.Split(New Char() {","}, StringSplitOptions.RemoveEmptyEntries)
For i As Integer = 0 To keyPatterns.Length - 1 Step 1
Dim keyPattern As String = keyPatterns(i) _
.Replace(".", "\.") _
.Replace("[", "\[") _
.Replace("]", "\]") _
.Replace("\[\]", "\[[0-9]+\]") _
.Replace("*", "[A-Za-z0-9]+")
Dim matchingKeys As IEnumerable(Of String) = _
modelState.Keys.Where(Function(x) Regex.IsMatch(x, keyPattern))
For Each matchingKey As String In matchingKeys
modelState(matchingKey).Errors.Clear()
Next
Next
End Sub
End Class
As you can see the code is very straightforward. Firstly we split the comma-separated string into its component keys. We then transform each key into a regular expression which we then use to query the model-state for any keys which match. For any matches which are found, we clear any validation errors which may have been raised.
Summary
The IgnoreModelErrors
attribute provides another alternative, and more declarative, method for performing partial or selective validation when posting model data back to an action in MVC. At present it provides only a basic syntax for matching keys in the model-state dictionary, but it could easily be expanded upon to handle more complex queries.
发表评论
Really informative blog.Much thanks again. Fantastic.
you continue to care for to stay it sensible. I can not wait to read
It as hard to find well-informed people for this topic, but you seem like you know what you are talking about! Thanks
Thanks so much for the blog.Really thank you! Keep writing.
Very good written. Keep up the author held the level.
Thanks for the blog.Really thank you! Cool.
This site definitely has all the information I wanted about this
I value the blog post.Much thanks again. Want more.
I truly appreciate this article post.Really looking forward to read more. Really Cool.
naturally like your web-site however you have to check the spelling
Somewhere in the Internet I have already read almost the same selection of information, but anyway thanks!!
Just that is necessary. I know, that together we can come to a right answer.
you may have an important blog here! would you prefer to make some invite posts on my blog?
the home of some of my teammates saw us.
Thanks again for the blog post.Really looking forward to read more. Really Cool.
you will have a great blog right here! would you like to make some invite posts on my blog?
It as difficult to find knowledgeable people in this particular topic, however, you sound like you know what you are talking about! Thanks
JAPAN JERSEY ??????30????????????????5??????????????? | ????????
Very informative article.Really thank you! Really Cool.
pretty beneficial material, overall I think this is really worth a bookmark, thanks
Muchos Gracias for your article post.Much thanks again. Want more.
You ave made some good points there. I checked on the net to find out more about the issue and found most individuals will go along with your views on this web site.
Just Browsing While I was browsing today I saw a excellent post about
Major thanks for the article.Much thanks again. Much obliged.
Weird , this post turns up with a dark color to it, what shade is the primary color on your web site?
This actually answered my problem, thanks!
Im obliged for the post.Much thanks again. Really Great.
the time to read or take a look at the content material or websites we ave linked to below the
Pretty! This was an extremely wonderful post. Thank you for supplying this info.
I went over this web site and I conceive you have a lot of superb info, saved to my bookmarks (:.
This is really interesting, You are a very skilled blogger. I ave joined your feed and look forward to seeking more of your fantastic post. Also, I ave shared your website in my social networks!
the home as value, homeowners are obligated to spend banks the real difference.
Very neat blog article.Thanks Again. Cool.
While I was surfing yesterday I saw a excellent post concerning
Your style is really unique in comparison to other people I ave read stuff from. Thanks for posting when you ave got the opportunity, Guess I will just bookmark this page.
Some truly choice blog posts on this site, saved to fav.
Your style is so unique in comparison to other people I have read stuff from. I appreciate you for posting when you have the opportunity, Guess I all just book mark this site.
Major thanks for the post.Thanks Again. Awesome. here
You have mentioned very interesting details ! ps nice web site. I didn at attend the funeral, but I sent a nice letter saying that I approved of it. by Mark Twain.
this topic. You understand a whole lot its almost tough to argue with you
It as actually a wonderful and handy section of data. Now i am satisfied that you choose to discussed this useful details about. Remember to stop us educated like this. Many thanks for revealing.
We stumbled over here by a different website and thought I should check things out. I like what I see so now i am following you. Look forward to checking out your web page for a second time.
Wow, great post.Much thanks again. Much obliged.
Im no expert, but I think you just crafted an excellent point. You naturally comprehend what youre talking about, and I can actually get behind that. Thanks for being so upfront and so honest.
Regards for all your efforts that you have put in this. Very interesting information. Aim at heaven and you will get earth thrown in. Aim at earth and you get neither. by Clive Staples Lewis.
This particular blog is without a doubt cool additionally diverting. I have discovered a lot of handy stuff out of it. I ad love to come back over and over again. Cheers!
Say, you got a nice article post. Great.
You are my breathing in, I own few web logs and rarely run out from to brand.
It as really a cool and useful piece of information. I am glad that you shared this helpful info with us. Please keep us informed like this. Thanks for sharing.
I'аve learn a few excellent stuff here. Certainly value bookmarking for revisiting. I surprise how so much attempt you set to make this sort of excellent informative website.