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?
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();
//...
}