I'm creating my own ModelValidator inheritor. I'm essentially performing a "Required" check on properties based upon a property of the container. This worked out beautifully in the override of Validate(object container). However, GetClientValidationRules
gives me no such "container" parameter/feature. How can I determine the container from inside the GetClientValidationRules override? The callstack shows that GetClientValidationRules is called as part of my call: Html.ValidationMessageFor(f=>f.Field). I need
"f.IsRequired" to determine whether or not to send the client-side rule. Or can the rule be disabled on the client side with some other parameter for ValidationMessageFor?
At the point which client validation is being set up, there may not be a container; in fact, we may not even know what the intended data-bound container type will be, or even if the outbound model will be the same as the inbound model.
GetClientValidationRules is only called from one place in the codebase, and at that place we do have access to htmlHelper.ViewData.Model. It wouldn't hurt to pass that object in even if it is not valid in all instances.
Here is some code that I'm using to do this currently that illustrates how useful it would be to have the container passed into the GetClientValidationRules method. Obvserve that if the container were passed into that method the entire inheritor of the DataAnnotationsModelMetadataProvider
could go away and the Validate and GetClientValidationRules methods would then be perfectly symmetrical.
public interface IDynamicMetadata
{
IEnumerable<ModelValidationResult> Validate(object container, ModelMetadata meta, ControllerContext context);
IEnumerable<ModelClientValidationRule> GetClientValidationRules(object container, ModelMetadata meta, ControllerContext context);
}
public class DynamicValidatorProvider: ModelValidatorProvider
{
public class DynamicValidator : ModelValidator
{
public DynamicValidator(ModelMetadata metadata, ControllerContext controllerContext)
: base(metadata, controllerContext ?? new ControllerContext())
{ }
public override IEnumerable<ModelValidationResult> Validate(object container)
{
var field = container as IDynamicMetadata;
if(field != null)
return field.Validate(container, Metadata, ControllerContext);
return Enumerable.Empty<ModelValidationResult>();
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
object container;
if (Metadata.AdditionalValues.TryGetValue(DynamicMetadataProvider.DynamicContainerKey, out container)
&& container is IDynamicMetadata)
{
var field = container as IDynamicMetadata;
return field.GetClientValidationRules(container, Metadata, ControllerContext);
}
return base.GetClientValidationRules();
}
}
public override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context)
{
if (typeof(IDynamicMetadata).IsAssignableFrom(metadata.ContainerType))
yield return new DynamicValidator(metadata, context);
}
}
public class DynamicMetadataProvider: DataAnnotationsModelMetadataProvider
{
public const string DynamicContainerKey = "DynamicContainer";
private object _lastDynamicMetadataModel;
protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
{
var ret = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
// what a pain -- this is the only function that gets called from all critical code paths
// if we have a model and it is IDynamicMetadata
// cache it as we will recall this function for each of his properties
// and we don't have access to it when this method is called for each specific property
var model = modelAccessor != null ? modelAccessor.Invoke() : null;
if (model is IDynamicMetadata)
{
_lastDynamicMetadataModel = model;
}
// this is a hack to work around the problem of not having the container in ModelValidator.GetClientValidationRules
if (typeof(IDynamicMetadata).IsAssignableFrom(containerType))
ret.AdditionalValues.Add(DynamicContainerKey, _lastDynamicMetadataModel);
return ret;
}
}
BranTheMan
None
0 Points
17 Posts
where's the container in GetClientValidationRules?
Dec 30, 2009 11:20 PM|LINK
I'm creating my own ModelValidator inheritor. I'm essentially performing a "Required" check on properties based upon a property of the container. This worked out beautifully in the override of Validate(object container). However, GetClientValidationRules gives me no such "container" parameter/feature. How can I determine the container from inside the GetClientValidationRules override? The callstack shows that GetClientValidationRules is called as part of my call: Html.ValidationMessageFor(f=>f.Field). I need "f.IsRequired" to determine whether or not to send the client-side rule. Or can the rule be disabled on the client side with some other parameter for ValidationMessageFor?
ModelValidator Required GetClientValidationRules ValidationMessageFor
bradwils
Contributor
5779 Points
691 Posts
Microsoft
Re: where's the container in GetClientValidationRules?
Jan 01, 2010 06:09 PM|LINK
Container is an inbound model-binding concept.
At the point which client validation is being set up, there may not be a container; in fact, we may not even know what the intended data-bound container type will be, or even if the outbound model will be the same as the inbound model.
BranTheMan
None
0 Points
17 Posts
Re: where's the container in GetClientValidationRules?
Jan 01, 2010 06:18 PM|LINK
GetClientValidationRules is only called from one place in the codebase, and at that place we do have access to htmlHelper.ViewData.Model. It wouldn't hurt to pass that object in even if it is not valid in all instances.
container
BranTheMan
None
0 Points
17 Posts
Re: where's the container in GetClientValidationRules?
Jan 25, 2010 05:52 PM|LINK
Here is some code that I'm using to do this currently that illustrates how useful it would be to have the container passed into the GetClientValidationRules method. Obvserve that if the container were passed into that method the entire inheritor of the DataAnnotationsModelMetadataProvider could go away and the Validate and GetClientValidationRules methods would then be perfectly symmetrical.
public interface IDynamicMetadata { IEnumerable<ModelValidationResult> Validate(object container, ModelMetadata meta, ControllerContext context); IEnumerable<ModelClientValidationRule> GetClientValidationRules(object container, ModelMetadata meta, ControllerContext context); } public class DynamicValidatorProvider: ModelValidatorProvider { public class DynamicValidator : ModelValidator { public DynamicValidator(ModelMetadata metadata, ControllerContext controllerContext) : base(metadata, controllerContext ?? new ControllerContext()) { } public override IEnumerable<ModelValidationResult> Validate(object container) { var field = container as IDynamicMetadata; if(field != null) return field.Validate(container, Metadata, ControllerContext); return Enumerable.Empty<ModelValidationResult>(); } public override IEnumerable<ModelClientValidationRule> GetClientValidationRules() { object container; if (Metadata.AdditionalValues.TryGetValue(DynamicMetadataProvider.DynamicContainerKey, out container) && container is IDynamicMetadata) { var field = container as IDynamicMetadata; return field.GetClientValidationRules(container, Metadata, ControllerContext); } return base.GetClientValidationRules(); } } public override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context) { if (typeof(IDynamicMetadata).IsAssignableFrom(metadata.ContainerType)) yield return new DynamicValidator(metadata, context); } } public class DynamicMetadataProvider: DataAnnotationsModelMetadataProvider { public const string DynamicContainerKey = "DynamicContainer"; private object _lastDynamicMetadataModel; protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName) { var ret = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName); // what a pain -- this is the only function that gets called from all critical code paths // if we have a model and it is IDynamicMetadata // cache it as we will recall this function for each of his properties // and we don't have access to it when this method is called for each specific property var model = modelAccessor != null ? modelAccessor.Invoke() : null; if (model is IDynamicMetadata) { _lastDynamicMetadataModel = model; } // this is a hack to work around the problem of not having the container in ModelValidator.GetClientValidationRules if (typeof(IDynamicMetadata).IsAssignableFrom(containerType)) ret.AdditionalValues.Add(DynamicContainerKey, _lastDynamicMetadataModel); return ret; } }ModelValidator Required GetClientValidationRules ModelValidatorProvider
BranTheMan
None
0 Points
17 Posts
Re: where's the container in GetClientValidationRules?
Jan 25, 2010 06:08 PM|LINK
If you want this fixed go vote for it:
http://aspnet.codeplex.com/WorkItem/View.aspx?WorkItemId=5137
jonjenkins
Member
2 Points
11 Posts
Re: where's the container in GetClientValidationRules?
Sep 03, 2010 04:23 AM|LINK
Voted. This one is a "no duh" to me, but then again, a lot of the existing validation patterns and perils are :)