How to validate body of Azure Function

Anna Melashkina
medialesson
Published in
3 min readSep 25, 2023

Yet another way how to validate body request of azure function (AF).

Let’s imagine you have user registration and user must provide valid email.

public class User
{
public string Email { get; set; }
}

Your AF looks something like this:

public class RegisterFunction
{
[FunctionName(nameof(RegisterFunction))]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req)
{
var user = await req.Body.Deserialize<User>();

if (user == null)
{
return new BadRequestObjectResult("Body is required.");
}

if (string.IsNullOrWhiteSpace(user.Email))
{
return new BadRequestObjectResult("Email is required.");
}

if (!new EmailAddressAttribute().IsValid(user.Email))
{
return new BadRequestObjectResult("Email is invalid.");
}

// Registration logic...

return new OkResult();
}
}

And small extension method for Stream :

public static class StreamExtentions
{
public async static Task<dynamic> Deserialize<T>(this Stream stream)
{
using (var reader = new StreamReader(stream, leaveOpen: true))
{
var text = await reader.ReadToEndAsync();
stream.Position = 0; // Set position back to 0, so we can read it again later.
return JsonConvert.DeserializeObject<T>(text);
}
}
}

Code above is not bad, but it would be much cleaner if we could move validation out of function execution scope. Let’s use FluentValidation first and move validation logic into Validator .

public class UserValidator : AbstractValidator<User>
{
public UserValidator()
{
RuleFor(x => x.Email)
.NotEmpty().WithMessage("Email is required.")
.EmailAddress().WithMessage("Email is invalid.");
}
}

So, now we can have something like this:

public class RegisterFunction
{
[FunctionName(nameof(RegisterFunction))]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req)
{
var user = await req.Body.Deserialize<User>();

if (user == null)
{
return new BadRequestObjectResult("Body is required.");
}

var validator = new UserValidator();
var validationResult = validator.Validate(user);

if (!validationResult.IsValid)
{
return new BadRequestObjectResult(validationResult.Errors.Select(x => x.ErrorMessage));
}

// Registration logic...

return new OkResult();
}
}

This is just a little cleaner, but still we have to create validator and check if body is not empty.

The next step is to create ValidateBodyAttribute , that would be called before each function run. We will use a little black magic of reflections:

public class ValidateBodyAttribute : FunctionInvocationFilterAttribute
{
private readonly Type _type;
public ValidateBodyAttribute(Type type)
{
_type = type;
}

public async override Task OnExecutingAsync(FunctionExecutingContext executingContext, CancellationToken cancellationToken)
{
if (!executingContext.Arguments.ContainsKey("req"))
{
throw new NotSupportedException("Not http function is not supported.");
}

var req = (HttpRequest)executingContext.Arguments["req"];
req.EnableBuffering(); // Body can be read multiple times.

var validationResult = await ValidateBody(req, cancellationToken).ConfigureAwait(false);
if (!validationResult.IsValid)
{
req.HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;

await req.HttpContext.Response.WriteAsync(JsonConvert.SerializeObject(validationResult.Errors.Select(x => x.ErrorMessage)), cancellationToken).ConfigureAwait(false);
await req.HttpContext.Response.CompleteAsync().ConfigureAwait(false);

throw new ValidationException(validationResult.Errors);
}
}

private async Task<ValidationResult> ValidateBody(HttpRequest req, CancellationToken cancellationToken)
{
// Transform body to object.
var method = typeof(StreamExtentions).GetMethod(nameof(StreamExtentions.Deserialize)).MakeGenericMethod(_type);

var task = method.Invoke(req.Body, new object[] { req.Body }) as Task<dynamic>;

var body = await task.ConfigureAwait(false);
if (body is null)
{
return new ValidationResult()
{
Errors = { new ValidationFailure("body", "Body is empty.") }
};
}

// Try to find validator for this body type.
// Construct the generic IValidator<T> type using reflection.
var validatorType = typeof(IValidator<>).MakeGenericType(_type);
var validator = req.HttpContext.RequestServices.GetService(validatorType) as dynamic;
if (validator is null)
{
throw new Exception($"Validator of type {_type} is not registered.");
}

return await validator.ValidateAsync(body, cancellationToken).ConfigureAwait(false);
}
}

Also we need to register validators. Add them in your Startup.cs :

class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddFluentValidationAutoValidation();
builder.Services.AddFluentValidationClientsideAdapters();
builder.Services.AddValidatorsFromAssemblyContaining<Startup>();
}
}

And after these manipulations our AZ would look like this:

public class RegisterFunction
{
[FunctionName(nameof(RegisterFunction))]
[ValidateBody(typeof(User))]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req)
{
var user = await req.Body.Deserialize<User>();

// Registration logic...

return new OkResult();
}
}

The code of registration function would not be even executed, if request body didn’t pass validation. So, our function stays clean and simple!

--

--