OnActionExecutionAsync in IAsyncActionFilter only hit sometimes - c#

I have made an Api Key authorization attribute, and it works... sometimes, which is quite strange.
The attribute code:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class ApiKeyAuthAttribute: Attribute, IAsyncActionFilter
{
private const string ApiKeyHeadername = "ApiKey";
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
DataAcces dataAcces = context.HttpContext.RequestServices.GetService(typeof(DataAcces)) as DataAcces;
if (!context.HttpContext.Request.Headers.TryGetValue(ApiKeyHeadername, out var apiKey) ||
!dataAcces.TryAuthenticate(apiKey, out NsInvoiceRecognitionCredentials credentials))
{
context.Result = new UnauthorizedResult();
return;
}
((JobController)context.Controller).Credentials = credentials;
await next();
}
}
The controller code:
[ApiController]
[Route("jobs")]
[ApiKeyAuth]
public class JobController : ControllerBase
{
private readonly DataAcces _dataAcces;
private readonly ILogger<JobController> _logger;
public NsInvoiceRecognitionCredentials Credentials;
public JobController(ILogger<JobController> logger, DataAcces dataAcces)
{
_logger = logger;
_dataAcces = dataAcces;
}
[HttpPost, Route("add")]
public IActionResult Add(IFormFile document)
{
//Do stuff
return Ok();
}
}
DataAcces code:
public bool TryAuthenticate(string apiKeyString, out NsInvoiceRecognitionCredentials credentials)
{
if (Guid.TryParse(apiKeyString, out Guid apiKey))
{
credentials = _dbContext.Set<NsInvoiceRecognitionCredentials>().FirstOrDefault(x => x.ApiKey == apiKey);
return credentials != null;
}
credentials = default;
return false;
}
Every request I send ends up in the controller constructor, which is expected. After that it should go to OnActionExecutionAsync in the ApiKeyAut hAttribute, but this does not happen 100% of the time, only about 50% of the time. The other 50% of the time it just does nothing and eventually time out.
I don't think the problem is in my filter or the dataAcces code, just because it doesnt even hit my breakpoint on the first line of OnActionExecutionAsync in my filter, so those parts are not even executed.
If I place a breakpoint in the first line of my controller constructor I can step over the next few lines, and about 50% of the time it just stops after those lines are executed, no exception, nothing.
EDIT:
I figured out what is going wrong, I didnt find a proper solution yet. I'll answer my question if I do.
There was nothing going wrong in the filter, the problems all occured due to the fact that I am working with files in the request body. These files apparently made the request too heavy (strange because its only 100kb). When I send requests without any files, it all works fine.
I think the problem might be the way I am sending and receiving files (IFormFiles)
If anyone knows why, or how to fix this I would be most grateful

Try to change it OnActionExecutingAsync and you have to use it like this
[ServiceFilter(typeof(ApiKeyAuthAttribute))]
public class JobController : ControllerBase
And before using it should be registered in startup
services.AddScoped<ApiKeyAuthAttribute>();
bur it usually makes sense to use filter only for the action where it is needed
[ServiceFilter(typeof(ApiKeyAuthAttribute))]
public async Task<IActionResult> GetUData()
and maybe you will have to remove all extra attributes. And I don't see any async tasks in your controller. Maybe you can try just ActionFilter
public class ApiKeyAuthAttribute: IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
or you can try to merge both
public class ApiKeyAuthAttribute: IActionFilter, IAsyncActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
......
}
public async Task OnActionExecutingAsync(ActionExecutingContext context,
ActionExecutionDelegate next)
{
OnActionExecuting(context);
await next();
}
}

Related

Async load data in BaseController, how to make controller constructor async

I run .net core 6 version, and I have no idea how to make constructor of my controller (BaseController) async, because i am calling async service to load items in Menu. Menu is on every page, so any other of my controllers (AccountController, OrderController) inherits from my BaseController.
It must be loaded in constructor, just on creating the controller, I cant hang on it on another action. Right now, when i just make it sync, on first page call there are no preloaded data, because it's not waiting on completition.
public class BaseController : Controller
{
private readonly ICategoryService _categoryService;
protected readonly IMapper _mapper;
protected LayoutViewModel _layoutViewModel = new LayoutViewModel();
public BaseController(ICategoryService categoryService, IMapper mapper)
{
_categoryService = categoryService;
_mapper = mapper;
LoadRankedCategories();
}
public void LoadCategories()
{
var categories = _categoryService.GetCategoriesAsync();
_layoutViewModel.Categories = _mapper.Map<IEnumerable<MenuCategoryViewModel>>(categories);
}
}
Ok, on controller construction start the asynchronous task and save said task in a field. Then make sure you await said task whenever you need the results.
public class BaseController : Controller
{
private Task _categoriesTask;
public BaseController()
{
_categoriesTask = LoadCategoriesAsync();
}
private async Task LoadCategoriesAsync()
{
var rawCategories = await _categoryService.GetCategoriesAsync();
return _mapper.Map<IEnumerable<MenuCategoryViewModel>>(rawCategories);
}
[HttpGet]
public async Task<IActionResult> SomeGet()
{
// Await the class-level task.
var categories = await _categoriesTask;
...
}
}
NOTE: As I see the sample here, however, you should do this in the static constructor or use some caching mechanism like memcached (if microservices or distributed) or Redis, assuming the categories never change. So take the solution as a purely academic response on how to overcome the problem. In reality, this doesn't feel right for the stated reason.
Finally, I wouldn't recommend this at all unless there is absolutely no other way: You can spawn a new thread and block it with Result. This might carry undesired issues, so use it at your own risk.
public BaseController()
{
// This will get you the actual categories.
_categories = Task.Run(() => LoadCategoriesAsync()).Result();
}

.NET Core Set ClaimsPrincipal from another controller

In few places in legacy code (more than 100 controllers), we are running action from other controllers.
In .NET Framework it runs OK - ClaimsPrincipal in both controller's action have correct values, but in .NET Core, running SecondController.internalPut() from FirstController gives me NullReferenceException.
FirstController:
[EnableCors]
public class FirstController : BaseApiController
{
public FirstController(IContextFactory contextFactory) : base(contextFactory)
{
}
[HttpPut]
[HttpPost]
[Route("/api/firstcontroller")]
public IActionResult Put([FromBody] MyDTO data)
{
var token = Identity.Token; // <--- correct value
var secondController = new SecondController(ContextFactory);
secondController.internalPut(something); <--- NullReferenceException
return Ok();
}
}
SecondController:
[EnableCors]
public class SecondController : BaseApiController
{
public SecondController(IContextFactory contextFactory) : base(contextFactory)
{
}
[HttpPut]
[HttpPost]
public async Task<IActionResult> Put(Guid myGuid)
{
internalPut(something); // <-- OK
return Ok();
}
internal void internalPut(object something)
{
var token = Identity.Token; // <--- NullReferenceException when running from FirstController!!
}
}
And BaseApiController with TokenIdentity:
[ApiController]
[Route("/api/[controller]")]
[Route("/api/[controller]/[action]")]
public class BaseApiController : ControllerBase
{
protected readonly IMyContextFactory ContextFactory;
public BaseApiController(IMyContextFactory contextFactory)
{
ContextFactory = contextFactory;
}
public TokenIdentity Identity => User?.Identity as TokenIdentity;
}
public class TokenIdentity : GenericIdentity
{
public Guid Token { get; set; }
public string User { get; set; }
public TokenIdentity(Guid token) : base(token.ToString())
{
Token = token;
}
}
How is the easiest fix for this bug? I know that I can change BaseApiController implementation to get ClaimsPrincipal from IHttpContextAccessor, but this means that I need to update constructors for all > 100 controllers in code...
It is another way to always have ClaimsPrincipal when we are calling action from another controller?
What I recommend as the correct solution
I can't emphasise enough how much I recommend moving shared functionality into its own services, or perhaps look at using the Mediator Pattern (e.g. using the MediatR library) to decouple your controllers from their functionality a little. What I provide below is not a solution, but a band-aid.
What I recommend a QUICK FIX only
Why is this only a quick fix?: because this doesn't instantiate the correct action details and route parameters, so it could potentially cause you some hard-to-find bugs, weird behaviour, URLs maybe not generating correctly (if you use this), etc.
Why am I recommending it?: because I know that sometimes time is not on our side and that perhaps you need a quick fix to get this working while you work on a better solution.
Hacky quick fix
You could add the following method to your base controller class:
private TController CreateController<TController>() where TController: ControllerBase
{
var actionDescriptor = new ControllerActionDescriptor()
{
ControllerTypeInfo = typeof(TController).GetTypeInfo()
};
var controllerFactory = this.HttpContext.RequestServices.GetRequiredService<IControllerFactoryProvider>().CreateControllerFactory(actionDescriptor);
return controllerFactory(this.ControllerContext) as TController;
}
Then instead of var secondController = new SecondController(ContextFactory); you would write:
var secondController = CreateController<SecondController>();

Fire and Forget database-related method in Hosted Service

I'm having issues finding a solution to my problem and mostly because i don't understand them completely so, here I am asking your support.
I need to fire and forget a method that selects and updates records from database, the problem is I have a 15seconds range between record creation and its appearance in my database (synchronization issue not fixable by me, so i have to accept it) without freezing user's interface and meanwhile letting it create other records.
I tried to simply Task.Run(method) it but every time it's fired the dbContext it's refreshed so nothing is done.
Googling around I found a lot of IHostedService and BackgroundService solutions but i really can't get to the point in them: if I understand what i'm trying to do, I need to call an Hosted Service and passing them a scoped version of my dbContext so every fired method will have it's own dbContext and they could work simultaneously. But can't really get HOW TO DO that.
I managed my code in various layers and repositories, so I'll try to be as clear as possible.
Controller:
public class RMAController : BaseController
{
private readonly ApplicationServiceRecords applicationServiceRecords;
public RMAController(ApplicationServiceRecords
applicationServiceRecords,
IConfiguration configuration,
AuthenticationService authenticationService,
AuthorizationService authorizationService)
: base(
configuration,
authenticationService,
authorizationService)
{
this.applicationService = applicationService;
this.applicationServiceRecords= applicationServiceRecords;
}
[HttpPost]
[Authorize]
public async Task<IActionResult>CreateRecord(
ResponseCreateRecord viewModel)
{
if (!ModelState.IsValid)
return Content("Error X");
//this is the method i want to fire and forget
await ApplicationServiceRecords.CreateRecordAsync(viewModel);
return RedirectToAction("TestPage");
}
}
Inside "CreateRecordAsync" i call other method's from Domain Layer that create, waits and update the record (again, can't get rid of waits nor create it without the need to update it immediately)
I tried using BackgroundService, this way:
public class BackgroundWorkerQueue
{
private ConcurrentQueue <Func< CancellationToken,Task >> _workItems = new ConcurrentQueue < Func < CancellationToken,Task >> ();
private SemaphoreSlim _signal = new SemaphoreSlim(0);
public async Task < Func < CancellationToken,
Task >> DequeueAsync(CancellationToken cancellationToken) {
await _signal.WaitAsync(cancellationToken);
_workItems.TryDequeue(out
var workItem);
return workItem;
}
public void QueueBackgroundWorkItem(Func < CancellationToken, Task > workItem) {
if (workItem == null) {
throw new ArgumentNullException(nameof(workItem));
}
_workItems.Enqueue(workItem);
_signal.Release();
}
}
public class LongRunningService: BackgroundService {
private readonly BackgroundWorkerQueue queue;
private readonly ILogger < LongRunningService > _logger;
private readonly MyContext _dbcontext;
public LongRunningService(BackgroundWorkerQueue queue, ILogger < LongRunningService > logger, IServiceProvider serviceProvider) {
_logger = logger;
_dbcontext = serviceProvider.CreateScope().ServiceProvider.GetRequiredService <MyContext > ();//thought thiw could be the solution, yet nope (probably can't get how to use it)
this.queue = queue;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var workItem = await queue.DequeueAsync(stoppingToken);
await workItem(stoppingToken);
}
}
}
Added them in startup:
services.AddHostedService<LongRunningService>();
services.AddSingleton<BackgroundWorkerQueue>();
And Fired the method using(?) this from controller :
_backgroundWorkerQueue.QueueBackgroundWorkItem(async token =>{
ApplicationServiceRecords.CreateRecordAsync(viewModel); });
But I got "Invalid attempt to call ReadAsync when reader is closed." on first attemp using DB in a simple select client = await repository.GetClienteByIdAsync(client.Id);
And that's all.
I'm sorry for bad english/ bad programming/bad explanation, and thank you in advance to everyone'll help.

Value cannot be null. (Parameter 'principal') [ Identity ] [ ASP.net core ]

I'm working on an Asp.Net core project, and I use Identity framework.
Problem:
Inside a Controller I try to get the current user information based on User (his type is ClaimsPrincipal) but I got a runtime error.
What I try:
That is my Controller code
[Authorize]
public class ServiceController : Controller
{
private readonly UserManager<AppUser> _userManager;
private RoleManager<IdentityRole> _roleManager;
private AppUser _currentUser;
private AppRoles _currentUserRole;
public ServiceController(UserManager<AppUser> userManager, RoleManager<IdentityRole> roleManager)
{
_userManager = userManager;
_roleManager = roleManager;
SetCurrentUser();
SetCurrentUserRole();
}
#region Private Methods
private async void SetCurrentUser()
{
var us = User;
_currentUser = await _userManager.GetUserAsync(User);
}
private async void SetCurrentUserRole()
{
string currentUserRoleName = _userManager.GetRolesAsync(_currentUser).Result.FirstOrDefault();
if (User.IsInRole(AppRoles.Admin.ToString()))
{
_currentUserRole = AppRoles.Admin;
}
else if (User.IsInRole(AppRoles.User.ToString()))
{
_currentUserRole = AppRoles.User;
}
}
#endregion
public ActionResult Get()
{
return _currentUserRole switch
{
AppRoles.User => RedirectToAction("Services", "User"),
AppRoles.Admin => RedirectToAction("Services", "Admin"),
_ => RedirectToPage("Login")
};
}
}
Where I got the error?
The error happened in SetCurrentUser method, Visual Studio thrown this exception:
System.ArgumentNullException: 'Value cannot be null. (Parameter
'principal')'
Error screenshot:
Additional info:
My project depends on .Net 5
Please any help to fix this issue?
The User and HttpContext are not set yet (still null) in the controller's constructor. If you want to access it, try injecting the IHttpContextAccessor into the constructor like this:
public ServiceController(UserManager<AppUser> userManager,
RoleManager<IdentityRole> roleManager,
IHttpContextAccessor httpContextAccessor){
var user = httpContextAccessor.HttpContext.User;
//now pass the user above around to use it instead of basing on the property User
//...
}
However that way is not every standard especially when you need to call async methods in the constructor, which should not be done at all. Your methods are even declared using async void which should be absolutely avoided. Actually what you want is automatically make some info available before the action being executed. That's the perfect fit for an IActionFilter or IAsyncActionFilter (here using the async filter is better because you're going to call async methods in there). Because the info made available right in the controller (as properties/fields) so it will be more convenient to override the OnActionExecutionAsync method of the controller instead of using the filters I've mentioned. Using the filters in this case may require you to share the info around (such as via HttpContext.Items or request feature ...) which is a bit inconvenient.
Here's how you should do it:
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
await SetCurrentUserAsync();
await SetCurrentUserRoleAsync();
await base.OnActionExecutionAsync(context, next);
}
//adjust your methods
private async Task SetCurrentUserAsync() { ... }
private async Task SetCurrentUserRoleAsync() { ... }
That's all, remember to remove your equivalent code in the constructor.

Order of filters in .NET Core

How do I get a filter added as decorator in a controller to trigger AFTER a filter added in startup please?
I.e.
My Startup.cs looks like this:
services.AddControllers(options =>
{
options.Filters.Add<MyErrorHandlingFilter>();
});
My controller:
[HttpPost()]
[SignResponseFilter]
public async Task<ActionResult> DoSomething([FromBody] request)
{
// does stuff and causes an exception(the MyErrorHandlingFilter.OnExceptionAsync() to be called)
}
My SignResponseFilter:
public class SignResponseFilter : TypeFilterAttribute
{
public SignResponseFilter() : base(typeof(SignResponseFilterImplementation))
{
}
private class SignResponseFilter: IAsyncResultFilter
{
private readonly ISign _signer;
public SignResponseImplementation(ISign signer)
{
_signer= signer;
}
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
await next();
var response = await ResponseBodyReader.ReadResponseBody(context.HttpContext.Response);
var signature = await _signer.signIt(response);
context.HttpContext.Response.Headers.Add("myheader", signature);
}
}
}
MyErrorHandlingerfilter:
public class MyErrorHandlingerfilter: ExceptionFilterAttribute
{
private readonly IFormatter _formatter;
public CustomErrorHandlerFilterAttribute(IFormatter fortmatter)
{
_formatter = fortmatter;
}
public override async Task OnExceptionAsync(ExceptionContext context)
{
_formatter.DoFormatting(); // does some formatting
await base.OnExceptionAsync(context);
}
My problem is that SignResponseFilter is skipped when an exception occurs. MyErrorHandlingFilter does its formatting though. I would like it that SignResponse occurs on success and also, even when an exception occurs.
Following diagram shows how these filters interact in filter pipeline during request and response life cycle.
According your code, the SignResponseFilter is a Result filter, so, from above diagram, we can know that when the exception executed, it will trigger the Exception Filter first, not trigger the Result Filter. So, the SignResponse not occurs.
If you want to use other Filters with the Exception Filter, you could consider using the Action Filter.
More detail information, check Filters in ASP.NET Core

Categories