Search code examples
c#asp.net-coreaction-filter

ActionFilters for getting the user ID and returning error if it is null


I have a base class from which my (api) controllers inherit. I made it so that i can get the user id from my authentication provider and use it later to do stuff with the user, update it or get its data, etc.

public class BaseController : ControllerBase
{
    protected readonly IBaseData _baseData;

    public BaseController(IBaseData baseData)
    {
        _baseData = baseData;
    }

    public Guid GetUserId()
    {
        string nameIdentifier = User.FindFirst(ClaimTypes.NameIdentifier).Value;
        Guid? userId = _baseData.GetInvestorId(nameIdentifier);

        return userId != null ? (Guid)userId : Guid.Empty;
    }
}

I then call it inside my API end points:

Guid userId = GetUserId();

BaseModel m = _userData.GetBaseModel(userId);

return Ok(m);

Pretty simple. It gets called in multiple places in the controller. Not ideal but works fine. However now i need to catch an error that sometimes happens where the user is not in the DB. I can add some code to the API end point to do that like this:

Guid userId = GetUserId();

        if (userId == Guid.Empty)
            return NotFound(new ResponseGapiModel { Response = Response.NotFound, Message = "user not found in DB" });

        BaseModel m = _userData.GetBaseModel(userId);

        return Ok(m);

But that wold mean i would repeat a lot of code all over the place. I have been trying to use an action filter instead. But cannot get my head around it. I don't know how to pass parameters inside the actionfilter, like the name identifier i need to find the user. nor frankly how pass the ID back.

-UPDATE-

I have now managed to get the actionfilter to return a failed result when the user is not found so half of what i need works. Problem is that now i call the DB twise as i still call the original BaseCass GetUserId to get the ID to be used in later methods.

To get the ActionFilter to check for the missing user i injected my datacontext into it:

private readonly NodeDBContext _context;

    public ValidateAuth0UserInDBAttribute(NodeDBContext context)
    {
        _context = context;
    }

as well as used the HttpContext from the ActionExecutingContext to find my user Nameidentifier:

public void OnActionExecuting(ActionExecutingContext context)
    {
        //check if the user it in DB
        var id = _context.Users.SingleOrDefault(i => i.NameIdentifier == context.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value)?.Id;

        if (id == null)
        {
            context.Result = new NotFoundObjectResult(new ResponseModel { Response = Response.UserNotFound, Message = "User not found in DB" });
            return;
        }
    }

The problem is now how do i get the "id" passed back from this to my controller? Is there a way? or do i have to call the DB twice?


Solution

  • The problem is now how do i get the "id" passed back from this to my controller?

    Try to use below code to pass id back to controller:

    public class MyActionFilter : Attribute,IActionFilter
    {
        public void OnActionExecuting(ActionExecutingContext context)
        {
            var id = _context.Users.SingleOrDefault(i => i.NameIdentifier == context.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value)?.Id;
            if (id == null)
            {
               context.Result = new NotFoundObjectResult(new ResponseModel { Response = Response.UserNotFound, Message = "User not found in DB" });
               return;
            }
    
            var controller = (ControllerBase)context.Controller;
            controller.HttpContext.Items.Add("CurrentUserId", id );
    
        }
        public void OnActionExecuted(ActionExecutedContext context) { }
    }
    

    Action:

    [MyActionFilter]
    public IActionResult Get()
        {
            var id = HttpContext.Items["CurrentUserId"]?.ToString();
            //...
        }