Manual request Validation with IoC registered validator

Would like to manually validate a request dto, but the validator automatically validates before the call to the Post request method.

Is it possible to tell a service or service method to not automatically validate a request?

Using the information from the validation doc, it seems this would work if it wasn’t a validator for a request dto that is registered appHost.RegisterAs<MyFormValidator, IValidator<MyForm>>().

The request dto validator is registered:

public class MyFormFeature : IPlugin {
  public void Register(IAppHost appHost) {
    appHost.RegisterAs<MyFormValidator, IValidator<MyForm>>();
  }
}

So the validator automatically executes prior to the call to the MyFormService.Post(MyForm form) method, so only valid requests go thru.

    [Route("/my-form/", "POST")]
    public class MyForm : IReturn<MyForm> 
    { 
       // props
    }

    public class MyFormService : Service
    {
        public IValidator<MyForm> Validator { get; set; }

        public object Post(MyForm request)
        {
            var validationResult = Validator.Validate(request);

            // always valid, b/c only valid requests get thru
            if (!validationResult.IsValid)
            {
                // do some things b/c it's invalid

                throw validationResult.ToException();
            }

            // biz logic

            return request;
        }
    }

If instead of registering the Validator in the MyFormFeature plugin, we manually instantiate it in the MyFormService e.g.

public class MyFormService : Service
    {
        private IValidator<MyForm> Validator => new MyFormValidator();

        public object Post(MyForm request)
        {
            var validationResult = Validator.Validate(request);

            // this throws a NullReferenceException
            if (!validationResult.IsValid)
            {
                // do some things b/c it's invalid

                throw validationResult.ToException();
            }

            // biz logic

            return request;
        }
    }

Then we get a
'validationResult.IsValid' threw an exception of type 'System.NullReferenceException'

By default the ValidationFeature will scan all AppHost Service Assemblies for validators, you can disable auto scanning with:

Plugins.Add(new ValidationFeature { ScanAppHostAssemblies = false });

Then you can manually register only validators in Assemblies you want with:

container.RegisterValidators(typeof(OnlyValidatorsIn).Assembly)

or individually by Type:

container.RegisterValidator(typeof(MyValidator));

This is the implementation of RegisterValidators, you can use a modified version that excludes type you don’t want to register.

public static void RegisterValidators(this Container container, 
    ReuseScope scope, params Assembly[] assemblies)
{
    foreach (var assembly in assemblies)
    {
        foreach (var validator in assembly.GetTypes()
            .Where(t => t.IsOrHasGenericInterfaceTypeOf(typeof(IValidator<>))))
        {
            container.RegisterValidator(validator, scope);
        }
    }
}

You may also need to inject the IRequest into your custom validator, e.g:

var myValidator = new MyFormValidator {
    Request = base.Request,
};

Changing it to the below and injecting IRequest into the validator seems to get rid of the NullReferenceException, however .IsValid always returns true. Even with an email value of test it returns true. If we switch back to the IoC registered validator, it returns correctly with false.

public class MyFormService : Service
    {
        public object Post(MyForm request)
        {
            var validator = new MyFormValidator() { Request = Request };                    
            var validationResult = validator.Validate(request);

            // this always returns true, but when switched to IoC Validator it is correctly false
            if (!validationResult.IsValid)
            {
                 // do some things b/c it's invalid
                 throw validationResult.ToException();
            }

            // biz logic

            return request;
        }
    }

Update showing validator code:

public class MyFormValidator : AbstractValidator<MyForm>
    {
        public MyFormValidator()
        {
            RuleSet(
                ApplyTo.Put | ApplyTo.Post,
                () =>
                {
                    RuleFor(x => x.Email).EmailAddress();
                });
        }
    }

This is the impl ServiceStack uses for Validate():

Was going to try to build a method similar, but looks like ValidationContext.Request prop is set to readonly. Setter is internal.

There is a note on the Request prop
//Migration: Needs to be injected in Clone(), CloneForChildValidator() + all new ValidationContext()
Not sure if that comment is relevant.

I’ve just made ServiceStack’s Validate() method public (now available from v5.7.1 on MyGet) which you can call with:

validator.Validate(base.Request, request);

I’ve also added a SetRequest() method on ValidationContext in-case you need it in addition to validator.Validate(), let me know if you end up not using it as I’d like to remove it.

You can remove the SetRequest() method on ValidationContext, it isn’t needed.

Everything works with validator.Validate(base.Request, request);

Kind of odd that without base.Request param Validate(...) doesn’t work b/c .IsValid always returns true. Hard to realize that you need base.Request.

One other thing, the new validator.Validate(base.Request, request) method is async, was caught off guard that it wasn’t named .ValidateAsync(...), since there is already other .ValidateAsync(...) methods.

Does this mean there isn’t going to be a sync (non-async) method with the .Validate(base.Request, request)?

No the async Validate() does both sync/async validation. If you know the validator is not async you can block on validator.Validate(...).Result without issue.

Yep. I had no issue with it being async. I wanted to make sure that is how you wanted it named.

Yeah since it’s become public in this release, best to name it appropriately at the start. I’ve also added a sync Validate() to match Fluent Validation APIs, which will throw a NotSupportedException if calling an async validator in this commit.

I’ll let you know when it passes CI and is published on MyGet.

1 Like

This is now available from latest v5.7.1 that’s now on MyGet.

1 Like