3

Consider the following controller action signatures:

[HttpGet]
public IEnumerable<Whatever> Get(DateTime from, DateTime to)

Or

[HttpGet]
public Whatever Get(int amount, SomeUnit unit)

I'd like to be able to run a validation on parameters supplied by the caller. In both cases what passes as valid input in the first parameter depends on the value of second one.

So far I have failed to find an explanation or example of how this can be achieved with validation attributes in ASP.NET Core 3.0

I see that for example CustomValidationAttribute allows using it on methods. I can give it a method that IsValid will delegate to and provide object and validation context:

IsValid(Object, ValidationContext)

I checked documentation of both IsValid and ValidationContext and I cannot find any hint on how would I access the parameters passed to the validated action.

While googling for hints I found an excerpt from "Pro ASP.NET Web API" HTTP Web Services in ASP.NET" where they implement something similar. The excerpt ends however before the code is shown and I don't have that book (didn't find a corresponding book but on ASP.NET Core).

2 Answers 2

3

CustomValidationAttribute is used for properties and parameters. For an action validation you should write your own filter. Here is an example:

public class MyValidationAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if(context.ModelState.IsValid)
        {
            var dateFrom = (DateTime)context.ActionArguments["from"];
            var dateTo = (DateTime)context.ActionArguments["to"];

            if(dateFrom <= dateTo)
            {
                // continue the flow in pipeline
                return;
            }
        }

        context.Result = new BadRequestResult();
    }
}

And then you can use it in your controller:

[HttpGet]
[MyValidation]
public IEnumerable<Whatever> Get(DateTime from, DateTime to)
{
    // Here ModelState.IsValid is true
}
Sign up to request clarification or add additional context in comments.

3 Comments

Thank you for answering. I see this will work, but I was hoping asp.net core would offer a more elegant solution. You see, I already have ActionFilter (applied globally to all controllers) which checks if ModelState is not valid and then returns my custom error reponse with all the validation errors. With your approach, I am not sure how much control do I have over which action filter acts first. If there are already some validation errors on the model, and my error-returning filter would act first, I would return potentially incomplete number of validation errors to the caller.
@Emil you can set a filter's Order property
Is there any way to use existing validators? [StringLength(1)] etc?
0

You can put your action's parameters into an object that defines validation logic using the IValidatableObject interface. For example:

using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;


public class WhateverRequest : IValidatableObject
{
    public DateTime From { get; set; }

    public DateTime To { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        // Sample validations that use both properties:
        if (From > To)
        {
            yield return new ValidationResult(
                "The 'from' date must be earlier than or equal to the 'to' date.",
                [nameof(From), nameof(To)]
            );
        }

        if ((To - From).TotalDays > 365)
        {
            yield return new ValidationResult(
                "The date range cannot exceed 1 year.",
                [nameof(From), nameof(To)]
            );
        }
    }
}

Used like so:

[HttpGet]
public IActionResult Get([FromQuery] WhateverRequest request)
{
    // Your filter can handle the validation state, or you could do it here:
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    return Ok(/* your response */);
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.