FluentValidation message validation

Source
NuGet Package NServiceBus.FluentValidation (2.x) | License
This is a community maintained project. License and support are independent of Particular Software.
Target NServiceBus Version: 7.x

Uses FluentValidation to validate incoming and outgoing messages.

FluentValidation message validation can be enabled using the following:

var validationConfig = endpointConfiguration.UseFluentValidation();
validationConfig.AddValidatorsFromAssemblyContaining<MyMessage>();

This will result in, when an invalid message being detected, a validation exception being thrown and that message being handled by Recoverability. The validation exception will also be added to Unrecoverable exceptions to avoid unnecessary retries.

By default, incoming and outgoing messages are validated.

To disable for incoming messages use the following:

endpointConfiguration.UseFluentValidation(
    incoming: false);

To disable for outgoing messages use the following:

endpointConfiguration.UseFluentValidation(
    outgoing: false);

Enabling validation on outgoing message will result in the validation exception be thrown in the context of the sender, instead of during message processing on the receiving endpoint. This can be particularly helpful in development and/or debugging scenarios since the stack trace and debugger will more accurately reflect the cause of the invalid message.

Messages can then have an associated validator:

public class MyMessage :
    IMessage
{
    public string Content { get; set; }
}

public class MyMessageValidator :
    AbstractValidator<MyMessage>
{
    public MyMessageValidator()
    {
        RuleFor(_ => _.Content)
            .NotEmpty();
    }
}

Accessing the current pipeline context

In some cases a validator may need to use data from the current message context.

The current message context can be accessed via two extension methods:

  • The current message headers can be accessed via FluentValidationExtensions.Headers(this CustomContext customContext)
  • The current ContextBag can be accessed via FluentValidationExtensions.ContextBag(this CustomContext customContext).
public class ContextValidator :
    AbstractValidator<MyMessage>
{
    public ContextValidator()
    {
        RuleFor(_ => _.Content)
            .Custom((propertyValue, validationContext) =>
            {
                var messageHeaders = validationContext.Headers();
                var pipelineContextBag = validationContext.ContextBag();
                if (propertyValue == "User" &&
                    !messageHeaders.ContainsKey("Auth"))
                {
                    validationContext.AddFailure("Expected Auth header to exist");
                }
            });
    }
}

Validator scanning

Validators are registered and resolved using dependency injection. Assemblies can be added for validator scanning using either a generic Type, a Type instance, or an assembly instance.

var validationConfig = endpointConfiguration.UseFluentValidation();
validationConfig.AddValidatorsFromAssemblyContaining<MyMessage>();
validationConfig.AddValidatorsFromAssemblyContaining(typeof(SomeOtherMessage));
validationConfig.AddValidatorsFromAssembly(assembly);

Validator lifecycle can either be per endpoint (Single instance):

endpointConfiguration.UseFluentValidation(ValidatorLifecycle.Endpoint);

Or instance per unit of work:

endpointConfiguration.UseFluentValidation(ValidatorLifecycle.UnitOfWork);

The default lifecycle is per endpoint.

By default, there are two exception scenarios when adding validators. An exception will be thrown if:

  • No validators are found in an assembly that is scanned.
  • Any non-public validators are found in an assembly that is scanned.

These exception scenarios can be excluded using the following:

var validationConfig = endpointConfiguration.UseFluentValidation();
validationConfig.AddValidatorsFromAssembly(assembly,
    throwForNonPublicValidators: false,
    throwForNoValidatorsFound: false);

Samples

  • Fluent Validation
    Using NServiceBus.FluentValidation to validate properties on incoming and outgoing messages.

Last modified