Search code examples
c#.netasp.net-corewebapivalidationattribute

Change Response Status Code from ValidationAttribute


In an aspnet core web api project I have the logic

 if(!_entityService.Exists(entityId))
    return NotFound($"{entityId} not found.");

Scattered across many endpoints/controllers. I would like to refactor this cross-cutting concern to an Attribute, example a ValidationAttribute:

class EntityExistsAttribute : ValidationAttribute
{
    protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
    {
        var entityId = (int)value;

        var entityService = validationContext.GetRequiredService<EntityService>();

        if(entityService.Exists(entityId))
            return ValidationResult.Success;

        var httpContext = validationContext.GetRequiredService<IHttpContextAccessor>().HttpContext;

        httpContext.Response.StatusCode = StatusCodes.Status404NotFound;

        return new ValidationResult($"{entityId} not found.");
    }
}

With the attempted application in a controller:

    class EntitiesController
    {
        [HttpGet("{id:int}")]
        public Entity Get([FromRoute] [EntityExists] int id)
        {
            // no need to clutter here with the 404 anymore
        }
    }

Though this doesn't seem to work - the endpoint will still return 400 Bad Request.

Is it possible/advised to do anything like this in an asp.net core web api? I have thought about using middleware/action filters but couldn't see how I would achieve the above from those either.


Solution

  • You can custom ActionFilter attribute like below:

    public class CustomActionFilter:ActionFilterAttribute
    {
        
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            var entityId = int.Parse(context.HttpContext.Request.RouteValues["id"].ToString());
    
            var entityService = context.HttpContext.RequestServices.GetService<EntityService>();
            if (!entityService.Exists(entityId))
            {
                context.HttpContext.Response.StatusCode = StatusCodes.Status404NotFound;
                context.Result = new NotFoundObjectResult($"{entityId} not found.");
    
            }
            base.OnActionExecuting(context);
        }
    }
    

    Controller:

    [HttpGet("{id:int}")]
    [CustomActionFilter]
    public void Get([FromRoute] int id)
    {
        // no need to clutter here with the 404 anymore
    }
    

    Result: enter image description here