Writing Azure Functions with Function Monkey: Validation

Function Monkey is a framework to define Azure Functions in a fluent way as opposed to using binding attributes on function methods.

Other Function Monkey articles:

In addition to offering a different way to define functions, Function Monkey offers features such as validation.

Consider the following setup that generates a greeting:

using System.Net.Http;
using System.Threading.Tasks;
using AzureFromTheTrenches.Commanding.Abstractions;
using FunctionMonkey.Abstractions;
using FunctionMonkey.Abstractions.Builders;

namespace FunctionApp2
{
    public class GenerateGreetingCommand : ICommand<string>
    {
        public string Name { get; set; }
    }

    public class GenerateGreetingHandler : ICommandHandler<GenerateGreetingCommand, string>
    {
        public Task<string> ExecuteAsync(GenerateGreetingCommand command, string previousResult) => Task.FromResult($"Hello {command.Name}");
    }

    public class FunctionAppConfiguration : IFunctionAppConfiguration
    {
        public void Build(IFunctionHostBuilder builder)
        {
            builder
                .Setup((serviceCollection, commandRegistry) =>
                {
                    commandRegistry.Register<GenerateGreetingHandler>();                    
                })
                .Functions(functions => functions
                    .HttpRoute("v1/GenerateGreeting", route => route
                        .HttpFunction<GenerateGreetingCommand>(HttpMethod.Get))
                );
        }
    }  
}

If we run this and send a JSON payload of {"Name": ""} we’ll get back a response of "Hello ".

There is currently no validation on the name in the GenerateGreetingCommand.

To add validation with Function Monkey install the additional package: FunctionMonkey.FluentValidation. This will also install the dependent package FluentValidation.

To add validation to the Name property, we create a new class that inherits from AbstractValidator<T> where T is the command we want to validate, in this case the GenerateGreetingCommand:

public class GenerateGreetingCommandValidator : AbstractValidator<GenerateGreetingCommand>
{
    public GenerateGreetingCommandValidator()
    {
        RuleFor(x => x.Name).NotEmpty();
    }
}

In the constructor we use the FluentValidation syntax to define what validation to perform on the Name property of the command. In the preceding code we are saying name cannot be empty.

Next we need to wire up this new validator by adding the call to AddFluentValidation() and also registering the validator with serviceCollection.AddTransient<IValidator<GenerateGreetingCommand>, GenerateGreetingCommandValidator>();

So the setup now looks like:

public class FunctionAppConfiguration : IFunctionAppConfiguration
{
    public void Build(IFunctionHostBuilder builder)
    {
        builder
            .Setup((serviceCollection, commandRegistry) =>
            {
                serviceCollection.AddTransient<IValidator<GenerateGreetingCommand>, GenerateGreetingCommandValidator>();
                commandRegistry.Register<GenerateGreetingHandler>();                    
            })
            .AddFluentValidation()
            .Functions(functions => functions
                .HttpRoute("v1/GenerateGreeting", route => route
                    .HttpFunction<GenerateGreetingCommand>(HttpMethod.Get))
            );
    }
}

If we run the app again and try and submit an empty name, this time we get the following response:

{
  "errors": [
    {
      "severity": 0,
      "errorCode": "NotEmptyValidator",
      "property": "Name",
      "message": "'Name' must not be empty."
    }
  ],
  "isValid": false
}

If we wanted to enforce minimum and maximum Name length:

public class GenerateGreetingCommandValidator : AbstractValidator<GenerateGreetingCommand>
{
    public GenerateGreetingCommandValidator()
    {
        RuleFor(x => x.Name).NotEmpty()
                            .MinimumLength(5)
                            .MaximumLength(10);
    }
}

Now if we try and submit a name of “Joe”:

{
  "errors": [
    {
      "severity": 0,
      "errorCode": "MinimumLengthValidator",
      "property": "Name",
      "message": "The length of 'Name' must be at least 5 characters. You entered 3 characters."
    }
  ],
  "isValid": false
}

To add some custom validation in the form of an Action:

public class GenerateGreetingCommandValidator : AbstractValidator<GenerateGreetingCommand>
{
    public GenerateGreetingCommandValidator()
    {
        RuleFor(x => x.Name).NotEmpty()
                            .MinimumLength(5)
                            .MaximumLength(10)
                            .Custom((name, context) =>
                                {
                                    if (name == "Jason")
                                    {
                                        context.AddFailure("Jason is not a valid name");
                                    }
                                });

    }
}

Submitting a name of “Jason” now results in:

{
  "errors": [
    {
      "severity": 0,
      "errorCode": null,
      "property": "Name",
      "message": "Jason is not a valid name"
    }
  ],
  "isValid": false
}

We could also go and write units tests for the validator.

The ability to define command validation could also be useful if you had multiple ways for a client to submit requests, for example the same command (and validation) could be triggered from HTTP and a queue for example. In this case you could ensure the same validation is executed regardless of the input “channel”.

SHARE:

Pingbacks and trackbacks (1)+

Add comment

Loading