Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<h1>Update</h1> <p>In addition to the class shown below, I've done a similar thing for <code>IValidatableObject</code> implementations as well <strike>(short notes towards the end of the answer instead of a full code sample because then the answer just gets too long)</strike> - I've added the code for that class as well in response to a comment - it does make the answer <em>very</em> long, but at least you'll have all the code you need.</p> <h1>Original</h1> <p>Since I'm targeting <code>ValidationAttribute</code>-based validation at the moment I researched where MVC creates the <code>ValidationContext</code> that gets fed to the <code>GetValidationResult</code> method of that class.</p> <p>Turns out it's in the <code>DataAnnotationsModelValidator</code>'s <code>Validate</code> method:</p> <pre><code>public override IEnumerable&lt;ModelValidationResult&gt; Validate(object container) { // Per the WCF RIA Services team, instance can never be null (if you have // no parent, you pass yourself for the "instance" parameter). ValidationContext context = new ValidationContext( container ?? Metadata.Model, null, null); context.DisplayName = Metadata.GetDisplayName(); ValidationResult result = Attribute.GetValidationResult(Metadata.Model, context); if (result != ValidationResult.Success) { yield return new ModelValidationResult { Message = result.ErrorMessage }; } } </code></pre> <p>(Copied and reformatted from MVC3 RTM Source)</p> <p>So I figured some extensibility here would be in order:</p> <pre><code>public class DataAnnotationsModelValidatorEx : DataAnnotationsModelValidator { public DataAnnotationsModelValidatorEx( ModelMetadata metadata, ControllerContext context, ValidationAttribute attribute) : base(metadata, context, attribute) { } public override IEnumerable&lt;ModelValidationResult&gt; Validate(object container) { ValidationContext context = CreateValidationContext(container); ValidationResult result = Attribute.GetValidationResult(Metadata.Model, context); if (result != ValidationResult.Success) { yield return new ModelValidationResult { Message = result.ErrorMessage }; } } // begin Extensibility protected virtual ValidationContext CreateValidationContext(object container) { IServiceProvider serviceProvider = CreateServiceProvider(container); //TODO: add virtual method perhaps for the third parameter? ValidationContext context = new ValidationContext( container ?? Metadata.Model, serviceProvider, null); context.DisplayName = Metadata.GetDisplayName(); return context; } protected virtual IServiceProvider CreateServiceProvider(object container) { IServiceProvider serviceProvider = null; IDependant dependantController = ControllerContext.Controller as IDependant; if (dependantController != null &amp;&amp; dependantController.Resolver != null) serviceProvider = new ResolverServiceProviderWrapper (dependantController.Resolver); else serviceProvider = ControllerContext.Controller as IServiceProvider; return serviceProvider; } } </code></pre> <p>So I check first for my <code>IDependant</code> interface from the controller, in which case I create an instance of a wrapper class that acts as an adapter between my <code>IDependencyResolver</code> interface and <code>System.IServiceProvider</code>.</p> <p>I thought I'd also handle cases where a controller itself is an <code>IServiceProvider</code> too (not that that applies in my case - but it's a more general solution).</p> <p>Then I make the <code>DataAnnotationsModelValidatorProvider</code> use this validator by default, instead of the original:</p> <pre><code>//register the new factory over the top of the standard one. DataAnnotationsModelValidatorProvider.RegisterDefaultAdapterFactory( (metadata, context, attribute) =&gt; new DataAnnotationsModelValidatorEx(metadata, context, attribute)); </code></pre> <p>Now 'normal' <code>ValidationAttribute</code>-based validators, can resolve services:</p> <pre><code>public class ExampleAttribute : ValidationAttribute { protected override ValidationResult IsValid(object value, ValidationContext validationContext) { ICardTypeService service = (ICardTypeService)validationContext.GetService(typeof(ICardTypeService)); } } </code></pre> <p>This still leaves direct <code>ModelValidator</code>-derived needing to be reimplemented to support the same technique - although they already have access to the <code>ControllerContext</code>, so it's less of an issue.</p> <h1>Update</h1> <p>A similar thing has to be done if you want <code>IValidatableObject</code>-implementing types to be able to resolve services during the implementation of <code>Validate</code> without having to keep deriving your own adapters for each type.</p> <ul> <li>Derive a new class from <code>ValidatableObjectAdapter</code>, I called it <code>ValidatableObjectAdapterEx</code></li> <li>from MVCs v3 RTM source, copy the <code>Validate</code> and <code>ConvertResults</code> private method of that class.</li> <li>Adjust the first method to remove references to internal MVC resources, and</li> <li>change how the <code>ValidationContext</code> is constructed</li> </ul> <h1>Update (in response to comment below)</h1> <p>Here's the code for the <code>ValidatableObjectAdapterEx</code> - and I'll point out hopefully more clearly that <code>IDependant</code> and <code>ResolverServiceProviderWrapper</code> used here and before are types that only apply to my environment - if you're using a global, statically-accessible DI container, however, then it should be trivial to re-implement these two classes' <code>CreateServiceProvider</code> methods appropriately.</p> <pre><code>public class ValidatableObjectAdapterEx : ValidatableObjectAdapter { public ValidatableObjectAdapterEx(ModelMetadata metadata, ControllerContext context) : base(metadata, context) { } public override IEnumerable&lt;ModelValidationResult&gt; Validate(object container) { object model = base.Metadata.Model; if (model != null) { IValidatableObject instance = model as IValidatableObject; if (instance == null) { //the base implementation will throw an exception after //doing the same check - so let's retain that behaviour return base.Validate(container); } /* replacement for the core functionality */ ValidationContext validationContext = CreateValidationContext(instance); return this.ConvertResults(instance.Validate(validationContext)); } else return base.Validate(container); /*base returns an empty set of values for null. */ } /// &lt;summary&gt; /// Called by the Validate method to create the ValidationContext /// &lt;/summary&gt; /// &lt;param name="instance"&gt;&lt;/param&gt; /// &lt;returns&gt;&lt;/returns&gt; protected virtual ValidationContext CreateValidationContext(object instance) { IServiceProvider serviceProvider = CreateServiceProvider(instance); //TODO: add virtual method perhaps for the third parameter? ValidationContext context = new ValidationContext( instance ?? Metadata.Model, serviceProvider, null); return context; } /// &lt;summary&gt; /// Called by the CreateValidationContext method to create an IServiceProvider /// instance to be passed to the ValidationContext. /// &lt;/summary&gt; /// &lt;param name="container"&gt;&lt;/param&gt; /// &lt;returns&gt;&lt;/returns&gt; protected virtual IServiceProvider CreateServiceProvider(object container) { IServiceProvider serviceProvider = null; IDependant dependantController = ControllerContext.Controller as IDependant; if (dependantController != null &amp;&amp; dependantController.Resolver != null) { serviceProvider = new ResolverServiceProviderWrapper(dependantController.Resolver); } else serviceProvider = ControllerContext.Controller as IServiceProvider; return serviceProvider; } //ripped from v3 RTM source private IEnumerable&lt;ModelValidationResult&gt; ConvertResults( IEnumerable&lt;ValidationResult&gt; results) { foreach (ValidationResult result in results) { if (result != ValidationResult.Success) { if (result.MemberNames == null || !result.MemberNames.Any()) { yield return new ModelValidationResult { Message = result.ErrorMessage }; } else { foreach (string memberName in result.MemberNames) { yield return new ModelValidationResult { Message = result.ErrorMessage, MemberName = memberName }; } } } } } } </code></pre> <h1>End Code</h1> <p>With that class in place, you can register this as the default adapter for <code>IValidatableObject</code> instances with the line:</p> <pre><code>DataAnnotationsModelValidatorProvider. RegisterDefaultValidatableObjectAdapterFactory( (metadata, context) =&gt; new ValidatableObjectAdapterEx(metadata, context) ); </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload