Overflow and Bilal, Thanks for answering my question.
@Bilal: I use the same model for Save and Submit and does not want any attributes on Model, rather need something at a controller/action level.
In a search to find a better answer I have come up with something like this. I read this from another article, but lost the link. As soon as I get it I will update the same.
Add a new action filter attribute
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class IgnoreValidationAttribute : FilterAttribute, IAuthorizationFilter
{
// TODO: Try to put it on another more appropriate method such as OnActionExcecuting.
// Looks like - This is the earliest method we can interpret before an action. I really dont like this!
public void OnAuthorization(AuthorizationContext filterContext)
{
//TODO: filterContext != null && filterContext.httpContext != null
var itemKey = this.CreateKey(filterContext.ActionDescriptor);
if (!filterContext.HttpContext.Items.Contains(itemKey))
{
filterContext.HttpContext.Items.Add(itemKey, true);
}
}
private string CreateKey(ActionDescriptor actionDescriptor)
{
var action = actionDescriptor.ActionName.ToLower();
var controller = actionDescriptor.ControllerDescriptor.ControllerName.ToLower();
return string.Format("IgnoreValidation_{0}_{1}", controller, action);
}
}
Override the DataAnnotationModelMetadata
public class IgnoreValidationModelMetaData : DataAnnotationsModelMetadata
{
public IgnoreValidationModelMetaData(DataAnnotationsModelMetadataProvider provider, Type containerType,
Func<object> modelAccessor, Type modelType, string propertyName,
DisplayColumnAttribute displayColumnAttribute) :
base(provider, containerType, modelAccessor, modelType, propertyName, displayColumnAttribute)
{
}
public override IEnumerable<ModelValidator> GetValidators(ControllerContext context)
{
var itemKey = this.CreateKey(context.RouteData);
if (context.HttpContext.Items[itemKey] != null && bool.Parse(context.HttpContext.Items[itemKey].ToString()) == true)
{
return Enumerable.Empty<ModelValidator>();
}
return base.GetValidators(context);
}
private string CreateKey(RouteData routeData)
{
var action = (routeData.Values["action"] ?? null).ToString().ToLower();
var controller = (routeData.Values["controller"] ?? null).ToString().ToLower();
return string.Format("IgnoreValidation_{0}_{1}", controller, action);
}
}
Now tell the provider to use our custom data annotation metadata and empty the validation if IgnoreValidationAttribute is present in an action method
public class IgnoreValidationModelMetaDataProvider : DataAnnotationsModelMetadataProvider
{
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes,
Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
var displayColumnAttribute = new List<Attribute>(attributes).OfType<DisplayColumnAttribute>().FirstOrDefault();
var baseMetaData = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
// is there any other good strategy to copy the properties?
return new IgnoreValidationModelMetaData(this, containerType, modelAccessor, modelType, propertyName, displayColumnAttribute)
{
TemplateHint = baseMetaData.TemplateHint,
HideSurroundingHtml = baseMetaData.HideSurroundingHtml,
DataTypeName = baseMetaData.DataTypeName,
IsReadOnly = baseMetaData.IsReadOnly,
NullDisplayText = baseMetaData.NullDisplayText,
DisplayFormatString = baseMetaData.DisplayFormatString,
ConvertEmptyStringToNull = baseMetaData.ConvertEmptyStringToNull,
EditFormatString = baseMetaData.EditFormatString,
ShowForDisplay = baseMetaData.ShowForDisplay,
ShowForEdit = baseMetaData.ShowForEdit,
Description = baseMetaData.Description,
ShortDisplayName = baseMetaData.ShortDisplayName,
Watermark = baseMetaData.Watermark,
Order = baseMetaData.Order,
DisplayName = baseMetaData.DisplayName,
IsRequired = baseMetaData.IsRequired
};
}
}
Usage
[HttpPost]
[IgnoreValidation]
public ActionResult SaveDraft(MyModel myModel)
{
if (ModelState.IsValid)
{
// Should always reach here
}
.......
}
[HttpPost]
public ActionResult Submit(MyModel myModel)
{
if (ModelState.IsValid)
{
}
}
Please don't forget to call this in your Application_Start for the wireup 'ModelMetadataProviders.Current = new IgnoreValidationModelMetaDataProvider();
There are couple of concerns though.
Is there an early place we could manipulate the HttpContext than OnAuthorization() ?. I dont like the idea of overriding this to do something not related to authorization. Please note OnActionExecuting() will be too late in the MVC pipeline to do this(I tried this and is not working).
Is there a better way to do this than adding a key to HttpContext and using it later?