Transferring objects across service provider scopes - asp.net-core

I have a CorrelationIdMiddleware that is inspecting incoming request headers and setting a scoped CorrelationId later propagated to all HttpClients.
public class CorrelationId {
public string Value { get;set; }
}
public void ConfigureServices(IServiceCollection services) {
...
services.AddScoped<CorrelationId>();
...
}
I have run into a use case where I need to create an isolated scope around a section of code, but would like the CorrelationId from the scope of the http request to propagate into the isolated scope (The isolated scope has an HttpClient which I would like to have the same header attached).
I would like to spawn off a background Task that is created from DI w/ any required dependencies and for any HttpClients to have headers injected via HttpClientFactory plugins.
public Controller {
public Controller(IServiceProvider serviceProvider, CorrelationId correlationId) { ... }
public IActionResult PostTask() {
var isolatedScope = _serviceProvider.CreateScope();
var action = () => {
using(isolatedScope) {
var backgroundJob = isolatedScope
.ServiceProvider
.GetRequiredService<IBackgroundJob>();
backgroundJob.Execute();
// scopedCorrelationId =/= correlationId
// how can i get correlationId to jump scopes?
}
};
return Task.Factory.StartNew(
action,
CancellationToken.None,
TaskCreationOptions.LongRunning,
TaskScheduler.Default);
}
}
Is there a way to transfer certain objects into the isolated scope? Ideally without having to know the typeof(object) i need to transfer.

It is not possible to transfer objects between the parent scope and the isolated scope.
Information like the CorrelationId arriving with the headers of a request better fits with the HttpContext or with an AsyncLocal variable if it needs to be propagated through an async execution flow.

Related

Asp.Net Core Cannot access a disposed context instance

I'm trying to implement SignalR in order to consume data from a angular frontend application.
I've checked all the results on google that I can find, but I still can't solve my issue.
The error I'm getting is:
Cannot access a disposed context instance. A common cause of this
error is disposing a context instance that was resolved from
dependency injection and then later trying to use the same context
instance elsewhere in your application. This may occur if you are
calling 'Dispose' on the context instance, or wrapping it in a using
statement. If you are using dependency injection, you should let the
dependency injection container take care of disposing context
instances. Object name: 'AdminContext'
Controller
[Route("api/[controller]")]
[ApiController]
public class ChartController : ControllerBase
{
private IHubContext<ChartHub> _hub;
private readonly ILiveMonitoringService _service;
public ChartController(IHubContext<ChartHub> hub, ILiveMonitoringService service)
{
_hub = hub;
_service = service;
}
public IActionResult Get()
{
var timerManager = new TimerManager(async () => await _hub.Clients.All.SendAsync("transferchartdata", await _service.GetAllAsync()));
return Ok(new { Message = "Request Completed" });
}
}
Service
public Task<List<LiveMonitoring>> GetAllAsync()
{
return _repository.GetAll().Take(100).ToListAsync();
}
Repository
public IQueryable<TEntity> GetAll()
{
try
{
return _adminContext.Set<TEntity>();
}
catch (Exception ex)
{
throw new Exception("Couldn't retrieve entities");
}
}
What could be the problem?
I'm pretty sure that TimerManager is your issue. You did not show its declaration but looks like its constructor accepts a callback to be called at some later point of time. And that's the issue. Your scoped service _service is captured in the callback and used at some later point of time when the request has already ended. So after the request ended, the DbContext is disposed and your _service will consume a disposed context.
The fix is to simply get the data first before passing it into your callback so that the _service will not be captured into that callback, like this:
public async Task<IActionResult> Get()
{
var liveMonitorings = await _service.GetAllAsync();
var timerManager = new TimerManager(async () => await _hub.Clients.All.SendAsync("transferchartdata", liveMonitorings));
return Ok(new { Message = "Request Completed" });
}
We need to change the returned type of Get to Task<IActionResult> to support async call.
If you actually want to call _service.GetAllAsync() at some time later (not at the time of requesting Get) inside the callback, you need to inject an IServiceScopeFactory to create a scope for your service inside that callback, like this:
public IActionResult Get([FromServices] IServiceScopeFactory serviceScopeFactory)
{
var timerManager = new TimerManager(async () =>
{
using(var scope = serviceScopeFactory.CreateScope()){
var service = scope.ServiceProvider.GetRequiredService<ILiveMonitoringService>(); ​
​var liveMonitorings = await service.GetAllAsync();
​return await _hub.Clients.All.SendAsync("transferchartdata", liveMonitorings);
​ }
​});
​return Ok(new { Message = "Request Completed" });
}
This way you don't need to inject your _service into the controller's constructor (because it's not used at all).
​

Mediatr Scope problems

I am using Mediatr to handle messages from a queue. I can get a simple example to work. However I have run into problems when I try to inject an object into my handler
public class MessageCommandHandler : IRequestHandler<MessageCommand, bool>
{
private IMyDependency myDependency;
public MessageCommandHandler(IMyDependency myDependency)
{
this.myDependency = myDependency;
}
public Task<bool> Handle(MessageCommand request, CancellationToken cancellationToken)
{
return Task.FromResult(true);
}
}
This only works when I register IMyDependency as a transient scope, however when I register it as scoped lifetime it fails with the error
Cannot resolve 'MediatR.IRequestHandler`2[MyNamespace.MessageCommand,System.Boolean]' from root provider because it requires scoped service 'MyNamespace.IMyDependency'
I need to be able to inject dependencies with scoped lifetime. Has anyone got a solution for this.
I am using the .NET Core dependency injection framework. It is setup as follows
services.AddHostedService<QueueConsumer>();
services.AddScoped<IMyDependency, MyDependency>();
services.AddMediatR(new Assembly[] { Assembly.GetExecutingAssembly() });
Any ideas?
Any time you use a dependency with a Scoped lifetime, you will need to use it inside a pre-created scope. In the case of MVC this would happen automatically behind the scenes but if you're using direct from your own code, say via a console application or something, you will need to create the scope yourself.
This can be done by injecting an instance of IServiceScopeFactory and then using this factory to create a scope and then retrieve the dependency from that scope e.g.
public class MessageCommandHandler : IRequestHandler<MessageCommand, bool>
{
private IServiceScopeFactory _serviceScopeFactory;
public MessageCommandHandler(IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = serviceScopeFactory;
}
public Task<bool> Handle(MessageCommand request, CancellationToken cancellationToken)
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var scopedServices = scope.ServiceProvider;
var myDependency = scopedServices.GetRequiredService<IMyDependency>();
return Task.FromResult(true);
}
}
}
However (and note that the code above is untested), in my own systems I would almost always create the scope around whatever is sending the mediator request in which case any Scoped dependencies will still be injected automatically at this scope e.g.
... // some other calling class / Main method etc..
using (var scope = _serviceScopeFactory.CreateScope())
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
mediator.Send(new MessageCommand());
}

How to send a SignalR message to a user?

How does the client authorize to send a message to the user?
Sending from the controller
hubContext.Clients.User(User.Identity.Name).SendAsync();
At the moment the message is not sent. Do I need to add something in OnConnection ()? Or does SignalR have a ready-made mapping mechanism for ConnectionId and User.Identity.Name?
That's how I implemented it at the moment, but it seems to me not quite right. The question is how to make the same standard tools?
public static class HubConnections
{
public static Dictionary<string, List<string>> Users = new Dictionary<string, List<string>>();
public static List<string> GetUserId(string name)
{
return Users[name];
}
}
public class GameHub : Hub
{
public override Task OnConnectedAsync()
{
if (Context.User.Identity.IsAuthenticated
&& HubConnections.Users.ContainsKey(Context.User.Identity.Name)
&& !HubConnections.Users[Context.User.Identity.Name].Contains(Context.ConnectionId))
HubConnections.Users[Context.User.Identity.Name].Add(Context.ConnectionId);
else
HubConnections.Users.Add(Context.User.Identity.Name, new List<string> { Context.ConnectionId });
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception exception)
{
if (Context.User.Identity.IsAuthenticated) HubConnections.Users.Remove(Context.User.Identity.Name);
return base.OnDisconnectedAsync(exception);
}
}
As I said above, I tried just like this, and it does not work
hubContext.Clients.User(User.Identity.Name).SendAsync();
Was chasing the same issue and got the solution from https://github.com/aspnet/SignalR/issues/2498
One needs to set the NameIdentifier claim. That is the one checked by SignalR instead of the Name claim which I assumed. I set the NameIdentifier claim and I got my non-hub class to send a notification to a specific user.
The claim that signalR is using to identify the user can be changed. It is important to ensure that this claim has unique values.
Documentation says to setup a custom UserIdProvider like this:
public class NameUserIdProvider : IUserIdProvider
{
public string GetUserId(HubConnectionContext connection)
{
return connection.User?.Identity?.Name;
}
}
Add then add it to services:
public void ConfigureServices(IServiceCollection services)
{
// ... other services ...
services.AddSignalR();
services.AddSingleton<IUserIdProvider, NameUserIdProvider>();
}
The snippets are taken from official documentation:
https://learn.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-3.1#use-claims-to-customize-identity-handling
There's a client-side component. You must reference the SignalR JS file, create a connection and then subscribe to a particular message from the server. Only then will sending that message actually do something.
<script src="~/lib/signalr/signalr.js"></script>
<script>
const connection = new signalR.HubConnectionBuilder()
.withUrl("/gameHub")
.configureLogging(signalR.LogLevel.Information)
.build();
connection.on("Foo", (data) => {
// do something
});
connection.start().catch(err => console.error(err.toString()));
</script>
The above will then cause the client to run the function defined for "Foo" above whenever the server sends a "Foo" message like:
hubContext.Clients.User(User.Identity.Name).SendAsync("Foo", data);
You are using the Users as a store for the connection id. So, for each username, you can send the message to each of the client connections you have stored for that user. Something like this:
public void SendMessage(string username, object data)
{
var connections = HubConnections.Users[Context.User.Identity.Name];
foreach(var id in connections)
{
Clients.client(id).SendAsync("Foo", data);
}
}

Is there some way to keep domain events context unaware with NServiceBus 6 and the removal of IBus?

I'm wrestling with a situation where we currently use the IBus interface (NServiceBus v5) in domain event handlers to send commands to a backend service for processing. With the IBus, these commands could be sent regardless of what triggered the event, whether while receiving a Web API request or as part of an NServiceBus handler (common domain model). But, in NServiceBus v6, with the shift to context specific interfaces, IEndpointInstance or IMessageHandlerContext, it seems that my domain event handlers now need to become context aware. And further, it looks like the IMessageHandlerContext is only available via method injection, so I may have to sprinkle this parameter all throughout the call stack?
Is there some approach that I'm not seeing whereby I can keep my domain event handlers context unaware? Or have I followed some bad practice that's revealing itself through this code smell?
EDIT
Here's an attempt at boiling down the scenario to the most relevant pieces. There's an order in the domain model whose status may change. When the status of the order changes, we've been firing off a StatusChanged domain event through a publisher. A subscriber to this particular domain event writes out a record of the status change and also sends out an NServiceBus command to communicate this status out - the handler for this particular command will follow some further logic on whether to send out emails, SMS messages, etc., the details of which I don't think are relevant.
Order Domain Object
public class Order
{
private OrderStatusCode _statusCode;
public OrderStatusCode StatusCode
{
get { return _statusCode; }
private set { _statusCode = value; }
}
public void ChangeStatus(OrderStatusCode status)
{
Status = status;
Publish(new StatusChanged(CreateOrderSnapshot(), status));
}
protected void Publish<T>(T #event) where T : IDomainEvent
{
DomainEventPublisher.Instance.Publish(#event);
}
}
Domain Event Publisher
public class DomainEventPublisher : IDomainEventPublisher
{
private static IDomainEventPublisher _instance;
public static IDomainEventPublisher Instance
{
get { return _instance ?? (_instance = new DomainEventPublisher()); }
}
public ISubscriptionService SubscriptionService { get; set; }
public void Publish<T>(T #event) where T : IDomainEvent
{
if (SubscriptionService == null) return;
var subscriptions = SubscriptionService.GetSubscriptions<T>();
subscriptions.ToList().ForEach(x => PublishToConsumer(x, #event).GetAwaiter().GetResult());
}
private static async Task PublishToConsumer<T>(IEventSubscriber<T> x, T eventMessage) where T : IDomainEvent
{
await x.HandleEvent(eventMessage);
}
}
Status Changed Domain Event Handler
public class StatusChangedHandler : IEventSubscriber<StatusChanged>
{
private readonly IBus _bus;
private readonly IOrdersRepository _ordersRepository;
public StatusChangedHandler(IBus bus, IOrdersRepository ordersRepository)
{
_bus = bus;
_ordersRepository = ordersRepository;
}
public async Task HandleEvent(StatusChanged #event)
{
var statusTrailEntry = new OrderStatusTrailEntry(#event.OrderSnapshot, #event.Status);
var txOptions = new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted };
using (
var scope = new TransactionScope(TransactionScopeOption.Required, txOptions))
{
await _ordersRepository.SaveStatusTrail(statusTrailEntry);
if (communicateStatus)
{
_bus.Send(new SendCommunicationCommand(#event.OrderSnapshot, #event.Status));
}
scope.Complete();
}
}
}
The things is, up until now none of the sample code above has needed to know whether the status changed as a result of a request coming in through a Web API request or as a result of a status being changed within the context of an NServiceBus message handler (within a windows service) - the IBus interface is not context specific. But with the differentiation between IEndpointInstance and IMessageHandlerContext in NServiceBus v6, I don't feel that I have the same flexibility.
If I understand correctly, I'm able to register the IEndpointInstance with my container and inject into the EventSubscriber, so I'd be covered in the case of a Web API call, but I'd also need to add an IMessageHandlerContext as a parameter to optionally be passed down through the call stack from ChangeStatus to the Publisher and finally to the Domain Event Subscriber if the status happens to be changed within the context of a message handler. Really doesn't feel right to be adding this parameter all throughout the call stack.

Register RavenDb using Autofac?

Can anyone guide me on how I could register RavenDB using Autofac?
builder.Register<DocumentStore>(.. what after that?
Here is a sample console program that illustrates not only how to wire up the document store, but also how to set it up so you can just inject your document session:
using System.Threading.Tasks;
using Autofac;
using Raven.Client;
using Raven.Client.Document;
namespace ConsoleApplication1
{
internal class Program
{
private static void Main()
{
var builder = new ContainerBuilder();
// Register the document store as single instance,
// initializing it on first use.
builder.Register(x =>
{
var store = new DocumentStore { Url = "http://localhost:8080" };
store.Initialize();
return store;
})
.As<IDocumentStore>()
.SingleInstance();
// Register the session, opening a new session per lifetime scope.
builder.Register(x => x.Resolve<IDocumentStore>().OpenSession())
.As<IDocumentSession>()
.InstancePerLifetimeScope()
.OnRelease(x =>
{
// When the scope is released, save changes
// before disposing the session.
x.SaveChanges();
x.Dispose();
});
// Register other services as you see fit
builder.RegisterType<OrderService>().As<IOrderService>();
var container = builder.Build();
// Simulate some activity. 5 users are placing orders simultaneously.
Parallel.For(0, 5, i =>
{
// Each user gets their own scope. In the real world this would be
// a new inbound call, such as a web request, and you would let an
// autofac plugin create the scope rather than creating it manually.
using (var scope = container.BeginLifetimeScope())
{
// Let's do it. Again, in the real world you would just inject
// your service to something already wired up, like an MVC
// controller. Here, we will resolve the service manually.
var orderService = scope.Resolve<IOrderService>();
orderService.PlaceOrder();
}
});
}
}
// Define the order service
public interface IOrderService
{
void PlaceOrder();
}
public class OrderService : IOrderService
{
private readonly IDocumentSession _session;
// Note how the session is being constructor injected
public OrderService(IDocumentSession session)
{
_session = session;
}
public void PlaceOrder()
{
_session.Store(new Order { Description = "Stuff", Total = 100.00m });
// we don't have to call .SaveChanges() here because we are doing it
// globally for the lifetime scope of the session.
}
}
// Just a sample of something to save into raven.
public class Order
{
public string Id { get; set; }
public string Description { get; set; }
public decimal Total { get; set; }
}
}
Note that DocumentStore is single instance, but DocumentSession is instance per lifetime scope. For this sample, I am manually creating the lifetime scopes and doing it in parallel, simulating how 5 different users might be placing orders at the same time. They will each get their own session.
Putting SaveChanges in the OnRelease event is optional, but will save you from having to put it in every service.
In the real world, this might be a web application, or a service bus application, in which case your session should be scoped to either the single web request or the lifetime of the message, respectively.
If you are using ASP.Net WebApi, you should go get the Autofac.WebApi package off NuGet and use their .InstancePerApiRequest() method, which automatically creates the appropriate lifetime scope.