1

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.

Ned Grady
  • 33
  • 3
  • Sadly this isn't ideal anyway as likely you'd need to load the entity again in your business logic (causing worse performance). – harvzor Mar 20 '23 at 08:59

1 Answers1

0

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

Rena
  • 30,832
  • 6
  • 37
  • 72