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.
发表评论
qC8grC It as really a great and useful piece of information. I am glad that you shared this helpful info with us. Please keep us informed like this. Thank you for sharing.
JohmYO Thanks a lot for this kind of details I had been exploring all Yahoo to locate it!
8nMYcA Outstanding post, I think website owners should learn a lot from this website its rattling user friendly. So much good info on here .
gMn7Rm You made some first rate factors there. I regarded on the internet for the issue and found most individuals will go along with along with your website.
uRE3Zg Thanks for the article post.Really looking forward to read more. Great.
Thanks for sharing, this is a fantastic blog. Much obliged.
vfyh6G I was able to find good info from your content.
visit always a major fan of linking to bloggers that I enjoy but really don at get a great deal of link really like from
My brother suggested I might like this websiteHe was once totally rightThis post truly made my dayYou can not imagine simply how a lot time I had spent for this information! Thanks!
Merely wanna remark that you have a very decent web site , I enjoy the pattern it actually stands out.
This is one awesome blog.Really looking forward to read more. Great.
Nice blog here! Also your site loads up fast! What host are you using? Can I get your affiliate link to your host? I wish my website loaded up as fast as yours lol
I truly appreciate this blog article.Much thanks again. Much obliged.
recommend to my friends. I am confident they will be benefited from this website.
The Silent Shard This may possibly be pretty valuable for a few of one as employment I plan to will not only with my website but
What aаАабТа Going down i am new to this, I stumbled upon this I avаА аЂа found
these camera look like it was used in star trek movies.
Usually I do not read article on blogs, however I would like to say that this write-up very pressured me to try and do so! Your writing taste has been amazed me. Thanks, quite great post.
This is really interesting, You are a very skilled blogger. I ave joined your feed and look forward to seeking more of your great post. Also, I have shared your site in my social networks!
Thanks for sharing, this is a fantastic blog post.Much thanks again. Awesome.
You have observed very interesting points ! ps nice internet site. Tis a sharp medicine, but it will cure all that ails you. last words before his beheadding by Sir Walter Raleigh.
This is really interesting, You are a very skilled blogger. I have joined your feed and look forward to seeking more of your magnificent post. Also, I have shared your web site in my social networks!
Then you all know which is right for you.
This blog is no doubt awesome as well as informative. I have chosen many helpful tips out of it. I ad love to visit it again soon. Thanks a bunch!
I simply could not go away your website prior to suggesting that I extremely enjoyed the usual info a person provide for your visitors? Is gonna be again steadily to check out new posts.
Im grateful for the article post.Really looking forward to read more. Will read on
This actually answered my predicament, thank you! jordans free shipping
Very good blog.Really thank you! Really Cool.
Thank you for your blog article.Really thank you! Fantastic.
Wow, marvelous blog layout! How long have you ever been running a blog for? you made running a blog look easy. The whole glance of your website is fantastic, as well as the content!
Wonderful blog! I found it while browsing on Yahoo News. Do you have any tips on how to get listed in Yahoo News? I ave been trying for a while but I never seem to get there! Many thanks
Major thanks for the post.Thanks Again. Awesome. here
Really enjoyed this article. Really Great.
pretty beneficial stuff, overall I think this is worthy of a bookmark, thanks
Wow, awesome blog layout! How long have you been blogging for? you make blogging look easy. The overall look of your website is magnificent, let alone the content!
You certainly put a fresh spin on a subject that has been discussed for years.
There is definately a great deal to know about this subject. I love all the points you ave made.
Thanks for the meal!! But yeah, thanks for spending
The account aided me a acceptable deal. I had been a
Really informative blog.Much thanks again. Really Cool. free sex gifs
RW8WEu Thanks so much for the post. Keep writing.
Wow, awesome blog layout! How long have you been blogging for? you make blogging look easy. The overall look of your site is great, let alone the content!
This unique blog is really educating and also amusing. I have discovered a bunch of handy things out of this blog. I ad love to go back over and over again. Thanks!
This page really has all of the info I needed concerning this subject and didn at know who to ask.
Thank you ever so for you article post.Thanks Again. Will read on
The best richness is the richness of the soul.
This blog is without a doubt educating and besides amusing. I have found a bunch of handy stuff out of this source. I ad love to come back again soon. Thanks a lot!
Really informative article post.Really thank you! Much obliged.
Really informative post.Thanks Again. Really Cool.
Wow, great blog.Much thanks again. Cool.