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.
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
}