I get an ObjectDisposedException when reading a stream in the background service - c#

I want to process a Request.Body in a background-service in ASP.Net Core, so I first add it to a ConcurrentQueue.
public class BackgroundJobs
{
public ConcurrentQueue<Stream> Queue { get; set; } = new ConcurrentQueue<Stream>();
}
The concurrentQueue is instantiated with AddSingleton in Startup.cs
[ApiController]
public class NotificationsController : ControllerBase
{
private readonly BackgroundJobs backgroundJobs;
public NotificationsController( BackgroundJobs backgroundJobs)
{
this.backgroundJobs = backgroundJobs;
}
//...........
[HttpPost]
public ActionResult<string> Post([FromQuery] string validationToken = null)
{
if (!string.IsNullOrEmpty(validationToken))
{
return Ok(validationToken);
}
else
{
backgroundJobs.Queue.Enqueue(Request.Body);
return Accepted();
}
}
}
BackgroundServices.cs was instantiated in Startup.cs with AddHostedService.
When the Request.Body is in the queue I want to process it further.
public class BackgroundServices : BackgroundService, IHostedService
{
private readonly BackgroundJobs backgroundJobs;
private IServiceScopeFactory iServiceScopeFactory;
public BackgroundServices(BackgroundJobs backgroundJobs, IServiceScopeFactory iServiceScopeFactory)
{
this.backgroundJobs = backgroundJobs;
this.iServiceScopeFactory = iServiceScopeFactory;
}
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
Stream stream;
while (!cancellationToken.IsCancellationRequested)
{
if (this.backgroundJobs.Queue.TryDequeue(out stream))
{
StreamReader streamReader = new StreamReader(stream);
string content = await streamReader.ReadToEndAsync();
var list = JsonConvert.DeserializeObject<Notifications>(content)
//......
But get on string content = await streamReader.ReadToEndAsync() the following error message: System.ObjectDisposedException in System.Private.CoreLib.dll

Related

How to invoke MassTransit consumer using reflection

I am building a inbox transactional pattern approach for "intercepting" messages using an oberserver and filter to log them to a database. The idea is if a consumer failed to let a background worker periodically check the database for any records where the Integration event has failed, retrieve them and re-execute them.
The data being stored in the DB looks like this:
public class InboxMessage
{
public long Id { get; private set; }
public DateTime CreatedDate { get; private set; }
public string CreatedUser { get; private set; }
public DateTime EditedDate { get; private set; }
public string EditedUser { get; private set; }
public string MessageType { get; private set; }
public string? ConsumerType { get; private set; }
public string Data { get; private set; }
public Guid EventNumber { get; private set; }
public EventStatus Status { get; private set; }
}
The idea is to retrieve all messages with a Status = Failed, use reflection (or perhaps something else?) to Deserialise the "Data" prop to the MessageType. The ConsumerType would then be used to re-execute the consumer.
I use a Filter to log the initial message (before being transfered to the consumer):
public class InboxPatternConsumerFilter<T> : IFilter<ConsumeContext<T>> where T : class
{
private readonly IntegrationEventsContext _context;
private readonly ILogger _logger;
private const string MassTransitDynamicTypeName = "MassTransit.DynamicInternal.";
public InboxPatternConsumerFilter(ILoggerFactory logger, IntegrationEventsContext context)
{
_logger = logger.CreateLogger("InboxPatternConsumerFilter");
_context = context ?? throw new ArgumentNullException(nameof(context));
}
public async Task Send(ConsumeContext<T> context, IPipe<ConsumeContext<T>> next)
{
if (context.Message is IIntegrationEvent #event)
{
try
{
_logger.LogInformation("Integration event is type of - {generic}. Applying inbox pattern.", nameof(IGenericIntegrationEvent));
var message = new InboxMessage(
#event.EditedUser ?? "unknown",
context.Message.GetType().FullName?.Replace(MassTransitDynamicTypeName, string.Empty) ?? string.Empty,
null,
System.Text.Json.JsonSerializer.Serialize(context.Message),
#event.EventId,
EventStatus.Received);
_context.InboxMessages.Add(message);
await _context.SaveChangesAsync(context.CancellationToken);
}
catch (Exception ex)
{
// exception is catched to ensure the consumer can still continue.
_logger.LogError(ex, "Failed to create inbox message");
}
}
await next.Send(context);
}
public void Probe(ProbeContext context) {}
}
My reason for using a filter would be to check the EventNumber to confirm whether this message already exists in the DB, this should allow me to prevent sending this message to the consumer to resolve the idempotent issue in cases where we are using Retry mechanism for failed messaged.
I use a basic ReceiveObserver to update the messages as follows:
public class ReceiveObserver : IReceiveObserver
{
private readonly IntegrationEventsContext _context;
private readonly ILogger<ReceiveObserver> _logger;
public ReceiveObserver(IntegrationEventsContext context, ILogger<ReceiveObserver> logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_context = context ?? throw new ArgumentNullException(nameof(context));
}
public async Task PostConsume<T>(ConsumeContext<T> context, TimeSpan duration, string consumerType) where T : class
{
try
{
if (context.Message is IIntegrationEvent #event)
{
var message = await _context.InboxMessages.FirstOrDefaultAsync(x => x.EventNumber == #event.EventId);
if (message is not null)
{
message.Update(EventStatus.Completed, "post-consumer", consumerType);
_context.Update(message);
await _context.SaveChangesAsync();
var typeTest = System.Reflection.Assembly
.GetEntryAssembly()?
.GetType(consumerType);
}
else
{
_logger.LogWarning("Inbox Message not found");
}
}
}
catch (Exception ex)
{
_logger.LogError("An error occurred trying to update the Message's complete status", ex);
}
// called when the message was consumed, once for each consumer
}
public async Task ConsumeFault<T>(ConsumeContext<T> context, TimeSpan elapsed, string consumerType, Exception exception) where T : class
{
if (context.Message is IIntegrationEvent #event)
{
var message = await _context.InboxMessages.FirstOrDefaultAsync(x => x.EventNumber == #event.EventId);
if (message is not null)
{
message.Update(EventStatus.Failed, "consumer-fault", consumerType);
_context.Update(message);
await _context.SaveChangesAsync();
}
else
{
_logger.LogWarning("Inbox Message not found");
}
}
// called when the message is consumed but the consumer throws an exception
}
public Task ReceiveFault(ReceiveContext context, Exception exception)
{
// TODO: Get the message id and update the status in db.
// called when an exception occurs early in the message processing, such as deserialization, etc.
return Task.CompletedTask;
}
}
The idea is then to use a background service to check for any failed messages as follows:
public class InboxMessageService : IHostedService
{
private readonly IBusControl _bus;
private readonly IntegrationEventsContext _context;
private readonly ILogger<InboxMessageService> _logger;
private readonly IServiceProvider _serviceProvider;
public InboxMessageService(
IBusControl bus,
IntegrationEventsContext context,
ILogger<InboxMessageService> logger,
IServiceProvider serviceProvider)
{
_bus = bus ?? throw new ArgumentNullException(nameof(context)); ;
_context = context ?? throw new ArgumentNullException(nameof(context));
_logger = logger ?? throw new ArgumentNullException(nameof(context));
_serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
}
public async Task StartAsync(CancellationToken cancellationToken)
{
List<InboxMessage> messages = await _context.InboxMessages
.Where(x => x.Status != EventStatus.Completed)
.ToListAsync(cancellationToken);
foreach(InboxMessage message in messages)
{
try
{
if (message.ConsumerType is null)
{
_logger.LogWarning("Unable to find the consumer type to start this message.");
continue;
}
var typeTest = System.Reflection.Assembly
.GetEntryAssembly()?
.GetType(message.ConsumerType);
if (typeTest is null)
{
throw new Exception();
}
var constructor = typeTest.GetConstructors().First(); // We can always assume that the consumer will contain a ctor
var parameters = new List<object?>();
foreach (var param in constructor.GetParameters())
{
var service = _serviceProvider.GetService(param.ParameterType);//get instance of the class
parameters.Add(service);
}
var obj = Activator.CreateInstance(typeTest, parameters.ToArray());
// TODO: fiqure out how to create a ConsumeContext<T> message from the DB data
typeTest.GetMethod("Consume")?.Invoke(obj, System.Reflection.BindingFlags.InvokeMethod, Type.DefaultBinder, null, null);
}
catch (Exception ex)
{
// ...
}
}
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
The part I am struggling with is the TODO in the background service to figure out how to essentially recreate the Message (using the MessageType and Data props) and invoke the Consumer (using the ConsumerType).

How to use Mediator inside Background Service in C# ASP NET Core?

I am trying to implement a background service, QueuedBackground service with Mediator.
So far I am able to implement the queues but I am unable to execute Mediator.
Interface
public interface IBackgroundTaskQueueService
{
void QueueBackgroundWorkItem(object workItem, CancellationToken token);
Task<object> DequeueAsync(
CancellationToken cancellationToken);
}
Implementation
public class BackgroundTaskQueueService : IBackgroundTaskQueueService
{
private readonly ConcurrentQueue<(object,CancellationToken)> _workItems =
new ConcurrentQueue<(object,CancellationToken)>();
private SemaphoreSlim _signal = new SemaphoreSlim(0);
public void QueueBackgroundWorkItem(object workItem, CancellationToken token)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
_workItems.Enqueue((workItem,token));
_signal.Release();
}
public async Task<object> DequeueAsync( CancellationToken cancellationToken)
{
await _signal.WaitAsync(cancellationToken);
_workItems.TryDequeue(out var workItem);
return workItem.Item1;
}
}
Background Service
public class QueuedHostedService : BackgroundService
{
private readonly ILogger _logger;
private readonly IMediator _mediator;
public QueuedHostedService(IBackgroundTaskQueueService taskQueue, ILoggerFactory loggerFactory, IMediator mediator)
{
TaskQueue = taskQueue;
_mediator = mediator;
_logger = loggerFactory.CreateLogger<QueuedHostedService>();
}
public IBackgroundTaskQueueService TaskQueue { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (false == stoppingToken.IsCancellationRequested)
{
try
{
var workItem = await TaskQueue.DequeueAsync(stoppingToken);
await _mediator.Send(workItem, stoppingToken);
// await _mediator.Send(new UpdateProductCostByMaterialRequestModel()
// {
// Id = 1
// }, stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error occurred executing Work item.");
}
}
}
}
Usage
_queueService.QueueBackgroundWorkItem(new UpdateProductCostByMaterialRequestModel()
{
Id = request.ProductId
}, CancellationToken.None);
Now with the above code I am able to receive the class object but when I pass it in the Mediator I get InvalidOperation Handler not registered.
I am confused.
Okay I found the issue
Instead of passing it from the Constructor I had to use the ServiceFactory Interface
My Solution
BackgroundService is a Singleton. You cannot inject a Scoped into a Singleton.
public class QueuedHostedService : BackgroundService
{
private readonly ILogger _logger;
private readonly IServiceScopeFactory _serviceScopeFactory;
public QueuedHostedService(IBackgroundTaskQueueService taskQueue, ILoggerFactory loggerFactory, IServiceScopeFactory serviceScopeFactory)
{
TaskQueue = taskQueue;
_serviceScopeFactory = serviceScopeFactory;
_logger = loggerFactory.CreateLogger<QueuedHostedService>();
}
public IBackgroundTaskQueueService TaskQueue { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
using var scope = _serviceScopeFactory.CreateScope();
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
while (false == stoppingToken.IsCancellationRequested)
{
try
{
var workItem = await TaskQueue.DequeueAsync(stoppingToken);
if (workItem is IRequest<object> item)
{
await mediator.Send(workItem, stoppingToken);
}
// await _mediator.Send(new UpdateProductCostByMaterialRequestModel()
// {
// Id = 1
// }, stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error occurred executing Work item.");
}
}
}
}

Receiving Unauthorized Access Error on RenewToken Periodically with Azure Service Bus Queue Listener

I am receiving a Microsoft.Azure.ServiceBus.ServiceBusException (message below with sensitive information removed) periodically in my queue receiver. The SAS key has send/listen access and the error seems inconsequential as processing continues as normal. However, the message is creating a signal to noise problem in my dashboards (receiving 10-70 errors per day). Any ideas on why this is happening? The listener is running in an Azure App Service, but I don't think that matters. I have adjusted my retry logic to use a RetryExponential with a 1 second to 1 minute backoff with 5 retries.
Request for guidance from SDK developers
Packages
Net Core 3.1
Microsoft.Azure.ServiceBus, Version=4.1.3.0, Culture=neutral, PublicKeyToken=7e34167dcc6d6d8c
Error Message
The link 'xxx;xxx:xxx:xxx:source(address:xxx):xxx' is force detached. Code: RenewToken. Details: Unauthorized access. 'Listen' claim(s) are required to perform this operation. Resource: 'sb://xxx.servicebus.windows.net/xxx'.. TrackingId:xxx, SystemTracker:xxx, Timestamp:2020-04-27T09:36:04 The link 'xxx;xxx:xxx:xxx:source(address:xxx):xxx' is force detached. Code: RenewToken. Details: Unauthorized access. 'Listen' claim(s) are required to perform this operation. Resource: 'sb://xxx.servicebus.windows.net/xxx'.. TrackingId:xxx, SystemTracker:xxx, Timestamp:2020-04-27T09:36:04
Source
internal delegate TClient ClientFactory<out TClient>(string connectionString, string entityPath,
RetryPolicy retryPolicy);
internal delegate Task OnMessageCallback<in TMessage>(TMessage message,
CancellationToken cancellationToken = default) where TMessage : ICorrelative;
internal sealed class ReceiverClientWrapper<TMessage> : IReceiverClientWrapper<TMessage>
where TMessage : ICorrelative
{
// ReSharper disable once StaticMemberInGenericType
private static readonly Regex TransientConnectionErrorRegex =
new Regex(
#"(The link '([a-f0-9-]+);([0-9]*:)*source\(address:([a-z0-9_]+)\):([a-z0-9_]+)' is force detached. Code: RenewToken. Details: Unauthorized access. 'Listen' claim\(s\) are required to perform this operation. Resource: 'sb:\/\/([a-z0-9-_.\/]+)'.. TrackingId:([a-z0-9_]+), SystemTracker:([a-z0-9]+), Timestamp:([0-9]{4}(-[0-9]{2}){2}T([0-9]{2}:){2}[0-9]{2}) )+",
RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase);
private readonly IReceiverClient _receiverClient;
private readonly IMessageConverter<TMessage> _messageConverter;
private readonly ILogger _logger;
private readonly int _maximumConcurrency;
public ReceiverClientWrapper(IReceiverClient receiverClient, IMessageConverter<TMessage> messageConverter,
ILogger logger, int maximumConcurrency)
{
_receiverClient = receiverClient;
_messageConverter = messageConverter;
_logger = logger;
_maximumConcurrency = maximumConcurrency;
}
public Task SubscribeAsync(OnMessageCallback<TMessage> onMessageCallback,
OnFailureCallback onFailureCallback, CancellationToken cancellationToken = default)
{
var messageHandlerOptions = CreateMessageHandlerOptions(onFailureCallback, cancellationToken);
async Task Handler(Message message, CancellationToken token)
{
var convertedMessage = _messageConverter.Convert(message);
await onMessageCallback(convertedMessage, cancellationToken);
await _receiverClient.CompleteAsync(message.SystemProperties.LockToken);
}
_receiverClient.RegisterMessageHandler(Handler, messageHandlerOptions);
return Task.CompletedTask;
}
private MessageHandlerOptions CreateMessageHandlerOptions(OnFailureCallback onFailureCallback,
CancellationToken cancellationToken)
{
async Task HandleExceptionAsync(ExceptionReceivedEventArgs arguments)
{
var exception = arguments.Exception;
if (TransientConnectionErrorRegex.IsMatch(exception.Message))
{
_logger.LogWarning(exception, #"Transient connectivity error occurred");
return;
}
await onFailureCallback(exception, cancellationToken);
}
return new MessageHandlerOptions(HandleExceptionAsync)
{
AutoComplete = false,
MaxConcurrentCalls = _maximumConcurrency
};
}
public async ValueTask DisposeAsync()
{
await _receiverClient.CloseAsync();
}
}
internal sealed class SenderClientWrapper<TMessage> : ISenderClientWrapper<TMessage> where TMessage : ICorrelative
{
private readonly ISenderClient _senderClient;
private readonly IMessageConverter<TMessage> _messageConverter;
public SenderClientWrapper(ISenderClient senderClient, IMessageConverter<TMessage> messageConverter)
{
_senderClient = senderClient;
_messageConverter = messageConverter;
}
public Task SendAsync(TMessage message, CancellationToken cancellationToken = default)
{
var internalMessage = _messageConverter.Convert(message);
return _senderClient.SendAsync(internalMessage);
}
public Task SendAsync(IEnumerable<TMessage> messages, CancellationToken cancellationToken = default)
{
var internalMessages = messages
.Select(_messageConverter.Convert)
.ToImmutableArray();
return _senderClient.SendAsync(internalMessages);
}
public async ValueTask DisposeAsync()
{
await _senderClient.CloseAsync();
}
}
internal abstract class AbstractClientWrapperFactory
{
private const int MaximumRetryCount = 5;
private static readonly TimeSpan MinimumRetryBackOff = TimeSpan.FromSeconds(1);
private static readonly TimeSpan MaximumRetryBackOff = TimeSpan.FromMinutes(1);
protected AbstractClientWrapperFactory(IOptions<MessageBusConfiguration> options)
{
Options = options;
}
protected IOptions<MessageBusConfiguration> Options { get; }
protected static string GetEntityPath<TMessage>() where TMessage : class
{
var messageAttribute = typeof(TMessage).GetCustomAttribute<AbstractMessageAttribute>();
if (messageAttribute == null)
{
throw new ArgumentException($#"Message requires {nameof(AbstractMessageAttribute)}");
}
return messageAttribute.EntityName;
}
protected TClient CreateClientEntity<TMessage, TClient>(ClientFactory<TClient> clientFactory)
where TMessage : class
{
var entityPath = GetEntityPath<TMessage>();
var retryPolicy = CreateRetryPolicy();
return clientFactory(Options.Value.ConnectionString, entityPath, retryPolicy);
}
protected static IQueueClient QueueClientFactory(string connectionString, string entityPath,
RetryPolicy retryPolicy)
{
return new QueueClient(connectionString, entityPath, retryPolicy: retryPolicy);
}
private static RetryPolicy CreateRetryPolicy()
{
return new RetryExponential(MinimumRetryBackOff, MaximumRetryBackOff, MaximumRetryCount);
}
}
internal sealed class SenderClientWrapperFactory : AbstractClientWrapperFactory, ISenderClientWrapperFactory
{
private readonly IMessageConverterFactory _messageConverterFactory;
public SenderClientWrapperFactory(IMessageConverterFactory messageConverterFactory,
IOptions<MessageBusConfiguration> options) : base(options)
{
_messageConverterFactory = messageConverterFactory;
}
public ISenderClientWrapper<TEvent> CreateTopicClient<TEvent>() where TEvent : class, IEvent
{
return CreateWrapper<TEvent, ITopicClient>(TopicClientFactory);
}
public ISenderClientWrapper<TRequest> CreateQueueClient<TRequest>() where TRequest : class, IRequest
{
return CreateWrapper<TRequest, IQueueClient>(QueueClientFactory);
}
private ISenderClientWrapper<TMessage> CreateWrapper<TMessage, TClient>(ClientFactory<TClient> clientFactory)
where TMessage : class, ICorrelative
where TClient : ISenderClient
{
var clientEntity = CreateClientEntity<TMessage, TClient>(clientFactory);
var messageConverter = _messageConverterFactory.Create<TMessage>();
return new SenderClientWrapper<TMessage>(clientEntity, messageConverter);
}
private static ITopicClient TopicClientFactory(string connectionString, string entityPath,
RetryPolicy retryPolicy)
{
return new TopicClient(connectionString, entityPath, retryPolicy);
}
}
internal sealed class ReceiverClientWrapperFactory : AbstractClientWrapperFactory, IReceiverClientWrapperFactory
{
private readonly IMessageConverterFactory _messageConverterFactory;
private readonly ILogger<ReceiverClientWrapperFactory> _logger;
public ReceiverClientWrapperFactory(IOptions<MessageBusConfiguration> options,
IMessageConverterFactory messageConverterFactory,
ILogger<ReceiverClientWrapperFactory> logger) : base(options)
{
_messageConverterFactory = messageConverterFactory;
_logger = logger;
}
public IReceiverClientWrapper<TEvent> CreateTopicClient<TEvent>() where TEvent : class, IEvent
{
return CreateReceiverClientWrapper<TEvent, ISubscriptionClient>(SubscriptionClientFactory);
}
public IReceiverClientWrapper<TRequest> CreateQueueClient<TRequest>() where TRequest : class, IRequest
{
return CreateReceiverClientWrapper<TRequest, IQueueClient>(QueueClientFactory);
}
private IReceiverClientWrapper<TMessage> CreateReceiverClientWrapper<TMessage, TClient>(
ClientFactory<TClient> clientFactory)
where TMessage : class, ICorrelative
where TClient : IReceiverClient
{
var clientEntity = CreateClientEntity<TMessage, TClient>(clientFactory);
var messageConverter = _messageConverterFactory.Create<TMessage>();
return new ReceiverClientWrapper<TMessage>(clientEntity, messageConverter, _logger,
Options.Value.MaximumConcurrency);
}
private ISubscriptionClient SubscriptionClientFactory(string connectionString, string entityPath,
RetryPolicy retryPolicy)
{
return new SubscriptionClient(connectionString, entityPath, Options.Value.SubscriberName,
retryPolicy: retryPolicy);
}
}
internal sealed class RequestService<TRequest> : IRequestService<TRequest> where TRequest : class, IRequest
{
private readonly Lazy<ISenderClientWrapper<TRequest>> _senderClient;
private readonly Lazy<IReceiverClientWrapper<TRequest>> _receiverClient;
public RequestService(ISenderClientWrapperFactory senderClientWrapperFactory,
IReceiverClientWrapperFactory receiverClientWrapperFactory)
{
_senderClient =
new Lazy<ISenderClientWrapper<TRequest>>(senderClientWrapperFactory.CreateQueueClient<TRequest>,
LazyThreadSafetyMode.PublicationOnly);
_receiverClient
= new Lazy<IReceiverClientWrapper<TRequest>>(receiverClientWrapperFactory.CreateQueueClient<TRequest>,
LazyThreadSafetyMode.PublicationOnly);
}
public Task PublishRequestAsync(TRequest requestMessage, CancellationToken cancellationToken = default)
{
return _senderClient.Value.SendAsync(requestMessage, cancellationToken);
}
public Task PublishRequestAsync(IEnumerable<TRequest> requestMessages,
CancellationToken cancellationToken = default)
{
return _senderClient.Value.SendAsync(requestMessages, cancellationToken);
}
public Task SubscribeAsync(OnRequestCallback<TRequest> onRequestCallback, OnFailureCallback onFailureCallback,
CancellationToken cancellationToken = default)
{
return _receiverClient
.Value
.SubscribeAsync((message, token) => onRequestCallback(message, cancellationToken), onFailureCallback,
cancellationToken);
}
public async ValueTask DisposeAsync()
{
if (_senderClient.IsValueCreated)
{
await _senderClient.Value.DisposeAsync();
}
if (_receiverClient.IsValueCreated)
{
await _receiverClient.Value.DisposeAsync();
}
}
public Task ThrowIfNotReadyAsync(CancellationToken cancellationToken = default)
{
return _senderClient.Value.SendAsync(ImmutableArray<TRequest>.Empty, cancellationToken);
}
}
This was never resolved using the original Nuget package, but the new Azure.Messaging.ServiceBus package does not appear to have the issue. I have opted to move to that.

Reading HttpContext.Request as object?

My base Request class looks like this:
public class GetAllProjectsQuery : QueryBase<ProjectsListModel>
{
}
public abstract class QueryBase<T> : UserContext, IRequest<T> // IRequest is MediatR interface
{
}
public abstract class UserContext
{
public string ApplicationUserId { get; set; } // and other properties
}
I want to write a middleware to my .NET Core 3.1 WebApi that will grab JWT from request header amd read ApplicationUserId from it. I started to code something:
public class UserInformation
{
private readonly RequestDelegate next;
public UserInformation(RequestDelegate next)
{
this.next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var jwt = context.Request.Headers["Authorization"];
// read jwt here
var userContext = (UserContext)context.Request.Body; // i know it wont work
userContext.ApplicationUserId = //whats next? Any ideas?
await this.next(context);
}
}
But to be honest i have no idea how to start so here are my questions:
As you can see, every request will be packed with my UserContext class and so on. How to cast HttpContext.Request.Body to my request object and attach ApplicationUserId to it? Is it possible? I want to acces to user credentials from my JWT from headers and i want to have that information in every request in my API (pass it to controller, then to command etc).
If getting this information from middleware is not the best practice, what is?
EDIT: Mcontroller that using MediatR:
// base controller:
[ApiController]
[Route("[controller]")]
public abstract class BaseController : ControllerBase
{
private IMediator mediator;
protected IMediator Mediator => this.mediator ?? (this.mediator = HttpContext.RequestServices.GetService<IMediator>());
}
// action in ProjectControlle
[HttpGet]
[Authorize]
public async Task<ActionResult<ProjectsListModel>> GetAllProjects()
{
return Ok(await base.Mediator.Send(new GetAllProjectsQuery()));
}
// query:
public class GetAllProjectsQuery : QueryBase<ProjectsListModel>
{
}
// handler:
public class GetAllProjectsQueryHandler : IRequestHandler<GetAllProjectsQuery, ProjectsListModel>
{
private readonly IProjectRepository projectRepository;
public GetAllProjectsQueryHandler(IProjectRepository projectRepository)
{
this.projectRepository = projectRepository;
}
public async Task<ProjectsListModel> Handle(GetAllProjectsQuery request, CancellationToken cancellationToken)
{
var projects = await this.projectRepository.GetAllProjectsWithTasksAsync();
return new ProjectsListModel
{
List = projects
};
}
}
You might not need a middleware, but need a model binder:
See: https://learn.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-3.1
Also see: https://learn.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-model-binding?view=aspnetcore-3.1
public class UserContextModelBinder : IModelBinder
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IModelBinder _defaultModelBinder;
public UserContextModelBinder(
IHttpContextAccessor httpContextAccessor,
IOptions<MvcOptions> mvcOptions,
IHttpRequestStreamReaderFactory streamReaderFactory)
{
_httpContextAccessor = httpContextAccessor;
_defaultModelBinder = new BodyModelBinder(mvcOptions.Value.InputFormatters, streamReaderFactory);
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
if (!typeof(UserContext).IsAssignableFrom(bindingContext.ModelType))
{
return;
}
await _defaultModelBinder.BindModelAsync(bindingContext);
if (bindingContext.Result.IsModelSet && bindingContext.Result.Model is UserContext)
{
var model = (UserContext)bindingContext.Result.Model;
var httpContext = _httpContextAccessor.HttpContext;
// Read JWT
var jwt = httpContext.Request.Headers["Authorization"];
model.ApplicationUserId = jwt;
bindingContext.Result = ModelBindingResult.Success(model);
}
}
}
Then add model binder to UserContext class:
[ModelBinder(typeof(UserContextModelBinder))]
public abstract class UserContext
{
public string ApplicationUserId { get; set; }
}
Also add IHttpContextAccessor to services in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddHttpContextAccessor();
}

Property injection

I'm trying make a telegram bot with reminder. I'm using Telegram.Bot 14.10.0, Quartz 3.0.7, .net core 2.0. The first version should : get message "reminder" from telegram, create job (using Quartz) and send meaasage back in 5 seconds.
My console app with DI looks like:
Program.cs
static IBot _botClient;
public static void Main(string[] args)
{
// it doesn't matter
var servicesProvider = BuildDi(connecionString, section);
_botClient = servicesProvider.GetRequiredService<IBot>();
_botClient.Start(appModel.BotConfiguration.BotToken, httpProxy);
var reminderJob = servicesProvider.GetRequiredService<IReminderJob>();
reminderJob.Bot = _botClient;
Console.ReadLine();
_botClient.Stop();
// it doesn't matter
}
private static ServiceProvider BuildDi(string connectionString, IConfigurationSection section)
{
var rJob = new ReminderJob();
var sCollection = new ServiceCollection()
.AddSingleton<IBot, Bot>()
.AddSingleton<ReminderJob>(rJob)
.AddSingleton<ISchedulerBot>(s =>
{
var schedBor = new SchedulerBot();
schedBor.StartScheduler();
return schedBor;
});
return sCollection.BuildServiceProvider();
}
Bot.cs
public class Bot : IBot
{
static TelegramBotClient _botClient;
public void Start(string botToken, WebProxy httpProxy)
{
_botClient = new TelegramBotClient(botToken, httpProxy);
_botClient.OnReceiveError += BotOnReceiveError;
_botClient.OnMessage += Bot_OnMessage;
_botClient.StartReceiving();
}
private static async void Bot_OnMessage(object sender, MessageEventArgs e)
{
var me = wait _botClient.GetMeAsync();
if (e.Message.Text == "reminder")
{
var map= new Dictionary<string, object> { { ReminderJobConst.ChatId, e.Message.Chat.Id.ToString() }, { ReminderJobConst.HomeWordId, 1} };
var job = JobBuilder.Create<ReminderJob>().WithIdentity($"{prefix}{rnd.Next()}").UsingJobData(new JobDataMap(map)).Build();
var trigger = TriggerBuilder.Create().WithIdentity($"{prefix}{rnd.Next()}").StartAt(DateTime.Now.AddSeconds(5).ToUniversalTime())
.Build();
await bot.Scheduler.ScheduleJob(job, trigger);
}
}
}
Quartz.net not allow use constructor with DI. That's why I'm trying to create property with DI.
ReminderJob.cs
public class ReminderJob : IJob
{
static IBot _bot;
public IBot Bot { get; set; }
public async Task Execute(IJobExecutionContext context)
{
var parameters = context.JobDetail.JobDataMap;
var userId = parameters.GetLongValue(ReminderJobConst.ChatId);
var homeWorkId = parameters.GetLongValue(ReminderJobConst.HomeWordId);
await System.Console.Out.WriteLineAsync("HelloJob is executing.");
}
}
How can I pass _botClient to reminderJob in Program.cs?
If somebody looks for answer, I have one:
Program.cs (in Main)
var schedBor = servicesProvider.GetRequiredService<ISchedulerBot>();
var logger = servicesProvider.GetRequiredService<ILogger<DIJobFactory>>();
schedBor.StartScheduler();
schedBor.Scheduler.JobFactory = new DIJobFactory(logger, servicesProvider);
DIJobFactory.cs
public class DIJobFactory : IJobFactory
{
static ILogger<DIJobFactory> _logger;
static IServiceProvider _serviceProvider;
public DIJobFactory(ILogger<DIJobFactory> logger, IServiceProvider sp)
{
_logger = logger;
_serviceProvider = sp;
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
IJobDetail jobDetail = bundle.JobDetail;
Type jobType = jobDetail.JobType;
try
{
_logger.LogDebug($"Producing instance of Job '{jobDetail.Key}', class={jobType.FullName}");
if (jobType == null)
{
throw new ArgumentNullException(nameof(jobType), "Cannot instantiate null");
}
return (IJob)_serviceProvider.GetRequiredService(jobType);
}
catch (Exception e)
{
SchedulerException se = new SchedulerException($"Problem instantiating class '{jobDetail.JobType.FullName}'", e);
throw se;
}
}
// get from https://github.com/quartznet/quartznet/blob/139aafa23728892b0a5ebf845ce28c3bfdb0bfe8/src/Quartz/Simpl/SimpleJobFactory.cs
public void ReturnJob(IJob job)
{
var disposable = job as IDisposable;
disposable?.Dispose();
}
}
ReminderJob.cs
public interface IReminderJob : IJob
{
}
public class ReminderJob : IReminderJob
{
ILogger<ReminderJob> _logger;
IBot _bot;
public ReminderJob(ILogger<ReminderJob> logger, IBot bot)
{
_logger = logger;
_bot = bot;
}
public async Task Execute(IJobExecutionContext context)
{
var parameters = context.JobDetail.JobDataMap;
var userId = parameters.GetLongValue(ReminderJobConst.ChatId);
var homeWorkId = parameters.GetLongValue(ReminderJobConst.HomeWordId);
await _bot.Send(userId.ToString(), "test");
}
}

Categories

Resources