i\'ve got a sign up wizard for new user registration. When I try to goto the 2nd page, I get validation errors because my User
object hasn\'t been fully populated,
In the action just remove the errors for the items not yet checked for. This then makes your model valid for the items already checked
foreach (var error in ModelState["Avatar"].Errors)
{
ModelState["Avatar"].Errors.Remove(error);
}
or
ModelState["Avatar"].Errors.Clear();
To ignore the properties from ModelState, here is the simplest code.
if (ModelState["PropertyName"] != null) ModelState["PropertyName"].Errors.Clear();
ViewModels that exactly match the data being posted back is generally the recommended technique because it is very predictable and you get all the benefits of strong typing, scaffolding, etc.. On the other hand, using BindAttribute may require you to take the properties that are not being posted back into account, and can result in silent failure at runtime when a property name is changed but the BindAttribute Include or Exclude strings are not. Avoiding the use of validation attributes has many drawbacks in MVC and would need to be replaced with some other validation technique like IValidatableObject or FluentValidation.
Despite all the benefits of ViewModels and the caveats that accompany the BindAttribute, it can still sometimes be preferable to use the BindAttribute and partially post to a model/viewmodel. This ActionFilterAttribute covers that exact case. It takes the code @awrigley cited a step further, but instead of clearing errors based on the ValueProvider, it clears errors based on the use of the BindAttribute (e.g. Include and Exclude). This attribute can be safely added to the GlobalFilterCollection because it won't change behavior of MVC validation when the BindAttribute has not been applied. Please note: I have not made heavy use of this but it works well for my basic cases.
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web.Mvc;
/// <summary>
/// When the BindAttribute is in use, validation errors only show for values that
/// are included or not excluded.
/// </summary>
public class ValidateBindableValuesOnlyAttributes : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var modelState = filterContext.Controller.ViewData.ModelState;
var includedProperties = filterContext.ActionDescriptor.GetParameters()
.SelectMany(o => o.BindingInfo.Include.Select(name => (string.IsNullOrWhiteSpace(o.BindingInfo.Prefix) ? "" : o.BindingInfo.Prefix + ".") + name));
var excludedProperties = filterContext.ActionDescriptor.GetParameters()
.SelectMany(o => o.BindingInfo.Exclude.Select(name => (string.IsNullOrWhiteSpace(o.BindingInfo.Prefix) ? "" : o.BindingInfo.Prefix + ".") + name));
var ignoreTheseProperties = new List<KeyValuePair<string, ModelState>>();
if (includedProperties.Any())
{
ignoreTheseProperties.AddRange(modelState.Where(k => !includedProperties.Any(name => Regex.IsMatch(k.Key, "^" + Regex.Escape(name) + @"(\.|\[|$)"))));
}
ignoreTheseProperties.AddRange(modelState.Where(k => excludedProperties.Any(name => Regex.IsMatch(k.Key, "^" + Regex.Escape(name) + @"(\.|\[|$)"))));
foreach (var item in ignoreTheseProperties)
{
item.Value.Errors.Clear();
}
}
}
public override void OnActionExecuting(ActionExecutingContext context)
{
var modelstate = context.ModelState;
var keys = modelstate.Keys.Where(x => ExculdeFeilds.Split(",").ToList().Contains(x));
foreach (var item in keys)
{
modelstate[item].ValidationState = ModelValidationState.Valid;
}
if (!modelstate.IsValid)
{
context.Result = new BadRequestObjectResult(context.ModelState);
}
}
What about IgnoreModelErrors custom class ?
http://mrbigglesworth79.blogspot.in/2011/12/partial-validation-with-data.html
Inherit from the class ActionFilterAttribute, and clear errors[based on matching names or regex patterns] in the OnActionExecuting as demonstrated in the above link. This will be cleaner.
I had a reference entity that wasn't supposed to be validated.
Removed it from validation at the beginning of the action:
[HttpPost]
public async Task<IActionResult> Post([FromBody] Contact contact)
{
var skipped = ModelState.Keys.Where(key => key.StartsWith(nameof(Contact.Portfolios)));
foreach (var key in skipped)
ModelState.Remove(key);
//ModelState doesn't include anything about Portfolios which we're not concerned with
if (!ModelState.IsValid)
return BadRequest(ModelState);
//Rest of action
}