Multiple contexts in a Singleton service - c#

Currently i'm designing a logger service to log HttpRequests made by HttpClient.
This logger service is Singleton and i want to have scoped contexts inside it.
Here's my logger:
public Logger
{
public LogContext Context { get; set; }
public void LogRequest(HttpRequestLog log)
{
context.AddLog(log);
}
}
I'm using the logger inside a DelegatingHandler:
private readonly Logger logger;
public LoggingDelegatingHandler(Logger logger)
{
this.logger = logger;
}
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
await base.SendAsync(request, cancellationToken);
this.logger.LogRequest(new HttpRequestog());
}
Then when i make some request using HttpClient, i want to have the logs for this specific call:
private void InvokeApi()
{
var logContext = new LogContext();
this.Logger.LogContext = logContext;
var httpClient = httpClientFactory.CreateClient(CustomClientName);
await httpClient.GetAsync($"http://localhost:11111");
var httpRequestLogs = logContext.Logs;
}
The problem is, it works but it's not thread safe. If i have parallel executions of InvokeApi, the context will not be the correct.
How can i have a attached context for each execution properly?
I'm registering the HttpClient like this:
services.AddSingleton<Logger>();
services.AddHttpClient(CentaurusHttpClient)
.ConfigurePrimaryHttpMessageHandler((c) => new HttpClientHandler()
{
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
})
.AddHttpMessageHandler(sp => new LoggingDelegatingHandler(sp.GetRequiredService<Logger>()));
I'm testing this piece of code using this:
public void Test_Parallel_Logging()
{
Random random = new Random();
Action test = async () =>
{
await RunScopedAsync(async scope =>
{
IServiceProvider sp = scope.ServiceProvider;
var httpClientFactory = sp.GetRequiredService<IHttpClientFactory>();
using (var context = new HttpRequestContext(sp.GetRequiredService<Logger>()))
{
try
{
var httpClient = httpClientFactory.CreateClient();
await httpClient.GetAsync($"http://localhost:{random.Next(11111, 55555)}");
}
catch (HttpRequestException ex)
{
Output.WriteLine("count: " + context?.Logs?.Count);
}
}
});
};
Parallel.Invoke(new Action[] { test, test, test });
}

This logger service is Singleton and i want to have scoped contexts inside it.
The Logger has a singleton lifetime, so its LogContext property also has a singleton lifetime.
For the kind of scoped data you're wanting, AsyncLocal<T> is an appropriate solution. I tend to prefer following a "provider"/"consumer" pattern similar to React's Context, although the more common name in the .NET world for "consumer" is "accessor". In this case, you could make the Logger itself into the provider, though I generally try to keep it a separate type.
IMO this is most cleanly done by providing your own explicit scope, and not tying into the "scope" lifetime of your DI container. It's possible to do it with DI container scopes but you end up with some weird code like resolving producers and then doing nothing with them - and if a future maintainer removes (what appears to be) unused injected types, then the accessors break.
So I recommend your own scope, as such:
public Logger
{
private readonly AsyncLocal<LogContext> _context = new();
public void LogRequest(HttpRequestLog log)
{
var context = _context.Value;
if (context == null)
throw new InvalidOperationException("LogRequest was called without anyone calling SetContext");
context.AddLog(log);
}
public IDisposable SetContext(LogContext context)
{
var oldValue = _context.Value;
_context.Value = context;
// I use Nito.Disposables; replace with whatever Action-Disposable type you have.
return new Disposable(() => _context.Value = oldValue);
}
}
Usage (note the explicit scoping provided by using):
private void InvokeApi()
{
var logContext = new LogContext();
using (this.Logger.SetContext(logContext))
{
var httpClient = httpClientFactory.CreateClient(CustomClientName);
await httpClient.GetAsync($"http://localhost:11111");
var httpRequestLogs = logContext.Logs;
}
}

Related

WPF SimpleInjector call to client.GetAsync hanging

I am trying to use SimpleInjector in a WPF Application (.NET Framework). We use it in exactly the same way in many of our Services but for some reason when I am attempting to implement the same logic in this WPF Application, the call to the HttpClient().GetAsync is hanging. We think it is because for some reason the Task is not executing.
I am registering the objects from the OnStartUp element of App.xaml.cs as below. Inside the SetupService Constructor we call a SetupService URL (set in the SetupConfiguration Section of the App.Config) to get the SetupResponse to use in the app.
It is ultimately hanging in the ServiceClient.GetAsync method, I have tried to show the flow below:
All classes appear to have been injected correctly, and the ServiceClient is populated in exactly the same way as the same point in one of our working services. We're at a loss as to what is happening, and how to fix this.
Finally, SetupService is being injected in other Classes - so I would rather get it working like this, rather than remove the call from the SimpleInjector mechanism.
Any help is very much appreciated.
public partial class App : Application
{
private static readonly Container _container = new Container();
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
RegisterDependencies();
_container.Verify();
}
private void RegisterDependencies()
{
var serviceConfigSection = ServiceConfigurationSection.Get();
_container.RegisterSingle<ILoggingProvider, LoggingProvider>();
_container.RegisterSingle<IServiceClient>(() => new ServiceClient(_container.GetInstance<ILoggingProvider>()));
_container.RegisterSingle<IConfigurationSection>(() => SetupConfigurationSection.Get());
_container.RegisterSingle<ISetupService, SetupService>();
}
}
public class SetupService: ISetupService
{
private static readonly Dictionary<string, string> AcceptType = new Dictionary<string, string>
{
{"Accept", "application/xml"}
};
private const string AuthenticationType = "Basic";
private readonly IServiceClient _serviceClient;
private readonly ILoggingProvider _logger;
private readonly IConfigurationSection _configuration;
public SetupService(IConfigurationSection configuration, IServiceClient serviceClient, ILoggingProvider logger)
{
_serviceClient = serviceClient;
_logger = logger;
_configuration = kmsConfiguration;
RefreshSetup();
}
public void RefreshSetup()
{
try
{
var token = BuildIdentityToken();
var authHeaderClear = string.Format("IDENTITY_TOKEN:{0}", token);
var authenticationHeaderValue =
new AuthenticationHeaderValue(AuthenticationType, Convert.ToBase64String(Encoding.ASCII.GetBytes(authHeaderClear)));
_serviceClient.Url = _configuration.Url;
var httpResponse = _serviceClient.GetAsync(string.Empty, authenticationHeaderValue, AcceptType).Result;
var responseString = httpResponse.Content.ReadAsStringAsync().Result;
_response = responseString.FromXML<SetupResponse>();
}
catch (Exception e)
{
throw
}
}
public class ServiceClient : IServiceClient
{
private const string ContentType = "application/json";
private string _userAgent;
private ILoggingProvider _logger;
public string Url { get; set; }
public string ProxyAddress { get; set; }
public int TimeoutForRequestAndResponseMs { get; set; }
public int HttpCode { get; private set; }
public ServiceClient(ILoggingProvider logger = null)
{
_logger = logger;
}
public async Task<HttpResponseMessage> GetAsync(string endpoint, AuthenticationHeaderValue authenticationHeaderValue = null, IDictionary<string, string> additionalData = null, IDictionary<string, string> additionalParams = null)
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(Url);
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(ContentType));
if (authenticationHeaderValue != null)
client.DefaultRequestHeaders.Authorization = authenticationHeaderValue;
ProcessHeader(client.DefaultRequestHeaders, additionalData);
var paramsQueryString = ProcessParams(additionalParams);
if (!string.IsNullOrEmpty(paramsQueryString))
endpoint = $"{endpoint}?{paramsQueryString}";
return await client.GetAsync(endpoint); **// HANGS ON THIS LINE!**
}
}
}
}
If you block on asynchronous code from a UI thread, then you can expect deadlocks. I explain this fully on my blog. In this case, the cause of the deadlock is Result. There's a couple of solutions.
The one I recommend is to rethink your user experience. Your UI shouldn't be blocking on an HTTP call to complete before it shows anything; instead, immediately (and synchronously) display a UI (i.e., some "loading..." screen), and then update that UI when the HTTP call completes.
The other is to block during startup. There's a few patterns for this. None of them work in all situations, but one that usually works is to wrap the asynchronous work in Task.Run and then block on that, e.g., var httpResponse = Task.Run(() => _serviceClient.GetAsync(string.Empty, authenticationHeaderValue, AcceptType)).GetAwaiter().GetResult(); and similar for other blocking calls.
Blocking before showing a UI is generally considered a bad UX. App stores generally disallow it. So that's why I recommend changing the UX. You may find an approach like this helpful.
Thanks for your Responses, I just wanted to sync the solution I've gone for.
It was risky for me to change the code in SetupService to remove the .Result, even though this was probably the correct solution, as I did not want to affect the other working Services using the SetupService library already there.
I ended up moving the regsitrations off the UI Thread by embedding the SimpleInjector code in a Code library, Creating a Program.cs and Main() and setting that as my Entry point.
static class Program
{
public static readonly Container _container = new Container();
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
public static void Main(){
var app = new MyApp.App();
Register();
app.Run(_container.GetInstance<MainWindow>());
}
static void Register()
{
_container.Register<MainWindow>();
MySimpleInjector.Register(_container);
_container.Verify();
}
}
and then, in a Separate .dll project, MyApp.Common
public class MySimpleInjector
{
private readonly Container _container;
public static void Register(Container container)
{
var injector = new MySimpleInjector(container);
}
private void RegisterDependencies()
{
var serviceConfigSection = ServiceConfigurationSection.Get();
_container.RegisterSingle<ILoggingProvider, LoggingProvider>();
_container.RegisterSingle<IServiceClient>(() => new ServiceClient(_container.GetInstance<ILoggingProvider>()));
_container.RegisterSingle<IConfigurationSection>(() => SetupConfigurationSection.Get());
_container.RegisterSingle<ISetupService, SetupService>();
}
}
I appreciate that this may not be the ideal solution - but it suits my purposes.
Again, thanks for your help and comments!
Andrew.

Dispose context instance error when trying to connect to my DB after Azure service bus message is consumed

I'm listening for an incoming Azure service bus message. Following the documentation and receiving the message, I parse the message body and then I want to connect to my DB to edit an entry and then save. But I'm getting this error below when trying to make the call
var ybEvent = await _unitOfWork.Repository<YogabandEvent>().GetEntityWithSpec(spec);
Error
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.\nObject name: 'DataContext'.
Here is the full service with the method that listens for and picks up incoming Azure messages.
Error is on the last line of MessageHandler()
FYI - If I remove the 'await' on the DB call, I still get the same error for a disposed context.
QUESTION - how do I fix this?
public class ServiceBusConsumer : IServiceBusConsumer
{
private readonly IConfiguration _config;
private readonly ServiceBusClient _queueClient;
private readonly ServiceBusProcessor _processor;
private readonly IUnitOfWork _unitOfWork;
private readonly IEventConsumer _eventConsumer;
public ServiceBusConsumer(IConfiguration config, IEventConsumer eventConsumer, IUnitOfWork unitOfWork)
{
_config = config;
_unitOfWork = unitOfWork;
_eventConsumer = eventConsumer;
_queueClient = new ServiceBusClient(_config["ServiceBus:Connection"]);
_processor = _queueClient.CreateProcessor(_config["ServiceBus:Queue"], new ServiceBusProcessorOptions());
}
public void RegisterOnMessageHandlerAndReceiveMessages() {
_processor.ProcessMessageAsync += MessageHandler;
_processor.ProcessErrorAsync += ErrorHandler;
_processor.StartProcessingAsync();
}
private async Task MessageHandler(ProcessMessageEventArgs args)
{
string body = args.Message.Body.ToString();
JObject jsonObject = JObject.Parse(body);
var eventStatus = (string)jsonObject["EventStatus"];
await args.CompleteMessageAsync(args.Message);
var spec = new YogabandEventWithMessageIdSpecification(args.Message.SequenceNumber);
// error here...
var ybEvent = await _unitOfWork.Repository<YogabandEvent>().GetEntityWithSpec(spec);
// do something then save
}
private Task ErrorHandler(ProcessErrorEventArgs args)
{
var error = args.Exception.ToString();
return Task.CompletedTask;
}
}
Here is my unit of work
public IGenericRepository<TEntity> Repository<TEntity>() where TEntity : class // : BaseEntity
{
if(_repositories == null)
_repositories = new Hashtable();
var type = typeof(TEntity).Name;
if (!_repositories.ContainsKey(type))
{
var repositoryType = typeof(GenericRepository<>);
var repositoryInstance = Activator.CreateInstance(repositoryType.MakeGenericType(typeof(TEntity)), _context);
_repositories.Add(type, repositoryInstance);
}
return (IGenericRepository<TEntity>) _repositories[type];
}
I tried to call my generic repo directly inside the handler but that still fails with the dispose error.
Here is the call I changed in the handler, now I call the gen repo instead of the unit of work
var ybEvent = await _eventsRepo.GetEntityWithSpec(spec);
Here is GetEntityWIthSpec() from my generic repo
public async Task<T> GetEntityWithSpec(ISpecification<T> spec)
{
return await ApplySpecification(spec).FirstOrDefaultAsync();
}
private IQueryable<T> ApplySpecification(ISpecification<T> spec)
{
return SpecificationEvaluator<T>.GetQuery(_context.Set<T>().AsQueryable(), spec);
}
FYI - here is how I init my repo call
private readonly IGenericRepository<YogabandEvent> _eventsRepo;
then I inject it into the constructor
public ServiceBusConsumer(IConfiguration config, IEventConsumer eventConsumer, IUnitOfWork unitOfWork, IGenericRepository<YogabandEvent> eventsRepo)
{
_config = config;
_unitOfWork = unitOfWork;
_eventConsumer = eventConsumer;
_eventsRepo = eventsRepo;
_queueClient = new ServiceBusClient(_config["ServiceBus:Connection"]);
_processor = _queueClient.CreateProcessor(_config["ServiceBus:Queue"], new ServiceBusProcessorOptions());
}
Code that starts the ServiceBusConsumer it's in Main()
public static async Task Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var loggerFactory = services.GetRequiredService<ILoggerFactory>();
try
{
// do some work here
// https://stackoverflow.com/questions/48590579/cannot-resolve-scoped-service-from-root-provider-net-core-2
var bus = services.GetRequiredService<IServiceBusConsumer>();
bus.RegisterOnMessageHandlerAndReceiveMessages();
}
catch (Exception ex)
{
var logger = loggerFactory.CreateLogger<Program>();
logger.LogError(ex, "An error occured during migration");
}
}
host.Run();
}
Here is my unit of work
public class UnitOfWork : IUnitOfWork
{
private readonly DataContext _context;
private Hashtable _repositories;
public UnitOfWork(DataContext context)
{
_context = context;
}
public async Task<int> Complete()
{
return await _context.SaveChangesAsync();
}
public void Dispose()
{
_context.Dispose();
}
public IGenericRepository<TEntity> Repository<TEntity>() where TEntity : class // : BaseEntity
{
if(_repositories == null)
_repositories = new Hashtable();
var type = typeof(TEntity).Name;
if (!_repositories.ContainsKey(type))
{
var repositoryType = typeof(GenericRepository<>);
var repositoryInstance = Activator.CreateInstance(repositoryType.MakeGenericType(typeof(TEntity)), _context);
_repositories.Add(type, repositoryInstance);
}
return (IGenericRepository<TEntity>) _repositories[type];
}
}
I cannot verify this but I had a similar situation where creating a response call to a service actually disposed my UoW and the datacontext and I faced the same error
I'm suspecting that the this call await args.CompleteMessageAsync(args.Message); is doing the disposing somewhere between the lines, (you can continue to trace here CompleteMessageAsync calls
this and a lot of disposing is going on)
to verify that, you can try to postpone that call till after you use the repository to save the changes.
// await args.CompleteMessageAsync(args.Message); <-- comment this line
var spec = new YogabandEventWithMessageIdSpecification(args.Message.SequenceNumber);
// error here...
var ybEvent = await _unitOfWork.Repository<YogabandEvent>().GetEntityWithSpec(spec);
// do something then save
await args.CompleteMessageAsync(args.Message); // <-- add it here
Remove this dispose from UnitOfWork:
public void Dispose()
{
_context.Dispose();
}
Simple rule: if you have not created object - do not dispose. It will be disposed automatically when scope diposed.
Also consider to remove this boilerplate. DbContext is already UoW, DbSet is already repository.

Dependency injection problem - Initializing remote service connection

In my .Net Core 3.0 app I want to use the Microsoft Graph Nuget library. I have created a connection class that authenticates my application using [MSAL][1] and then creates the connection and returns this. My idea was to inject this connection object in the constructor using Dependency Injection. However, since the method that creates the connection is async, I seem to have a problem how to use it in the constructor.
My Connect Class
public class AuthorizeGraphApi: IAuthorizeGraphApi
{
private readonly IConfiguration _config;
public AuthorizeGraphApi(IConfiguration config)
{
_config = config;
}
public async Task<GraphServiceClient> ConnectToAAD()
{
string accessToken = await GetAccessTokenFromAuthorityAsync();
var graphServiceClient = new GraphServiceClient(new DelegateAuthenticationProvider((requestMessage) => {
requestMessage
.Headers
.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
return Task.FromResult(0);
}));
return graphServiceClient;
}
private async Task<string> GetAccessTokenFromAuthorityAsync()
{
// clientid, authUri, etc removed for this example.
IConfidentialClientApplication _conn;
_conn = ConfidentialClientApplicationBuilder.Create(clientId)
.WithClientSecret(clientSecret)
.WithAuthority(new Uri(authUri))
.Build();
string[] scopes = new string[] { $"api://{clientId}/.default" };
AuthenticationResult result = null;
// AcquireTokenForClient only has async method.
result = await _conn.AcquireTokenForClient(scopes)
.ExecuteAsync();
return result.AccessToken;
}
}
My Graph Service to send requests
public class AzureIntuneService
{
private readonly IAuthorizeGraphApi _graphClient;
public AzureIntuneService(IAuthorizeGraphApi client)
{
//Gives: cannot implicitely convert to Threading.Tasks.Task.... error
_graphClient = client.ConnectToAAD();
}
public async Task<IList<string>> GetAADInformationAsync()
{
// then here, use the graphClient object for the request...
var payload = await _graphClient.Groups.Request().GetAsync();
return payload
}
}
I register the above classess in my startup as follows:
services.AddScoped<IAuthorizeGraphApi, AuthorizeGraphApi>();
The idea was that this way, I don't need to call the _graphClient in each method. How can I inject the connection object in a correct way? Or what are the best practices regarding this (injecting connection objects)?
One way would be to store a reference to the Task and make sure any public methods that use the connection are async:
public class AzureIntuneService
{
private readonly Task<GraphServiceClient> _graphClientTask;
public AzureIntuneService(IAuthorizeGraphApi client)
{
_graphClientTask = client.ConnectToAAD();
}
public async Task<IList<string>> GetAADInformationAsync()
{
var client = await _graphClientTask; // Get the client when connected
var payload = await client.Groups.Request().GetAsync();
return payload;
}
}
Constructors aren't async and should never be used to initialize anything async. The only way to workaround it is to do sync-over-async by doing a .Result which is always a problem.
In your case, the GraphServiceClient that takes in DelegateAuthenticationProvider, accepts an AuthenticateRequestAsyncDelegate. This allows you to have an async delegate to construct the client.
So now you can do
new DelegateAuthenticationProvider(async requestMessage =>
{
string accessToken = await GetAccessTokenFromAuthorityAsync();
//rest of code here
}
)
and this allows you to change your ConnectToAAD signature to just return a GraphServiceClient and not a Task<GraphServiceClient>.
When you need async data you have to look away from the regular constructor and create a factory method (private static function). Something like below:
public sealed class MyClass
{
private MyData asyncData;
private MyClass() { ... }
private async Task<MyClass> InitializeAsync()
{
asyncData = await GetDataAsync();
return this;
}
public static Task<MyClass> CreateAsync()
{
var ret = new MyClass();
return ret.InitializeAsync();
}
}
public static async Task UseMyClassAsync()
{
MyClass instance = await MyClass.CreateAsync();
...
}
More here: https://blog.stephencleary.com/2013/01/async-oop-2-constructors.html

Dependency Injection of DbContext in .Net Core console app: A second operation started on this context before a previous operation completed

I have implemented a project on .NET Core 2.2 which consists of 1 console app and 1 class library. Console app is basically a consumer which subscribes to some topics and processes messages that come in. Class library is a database layer, where I have repository. The problem is that sometimes I get error "System.InvalidOperationException: A seconds operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe" when there is interaction with database.
I searched for similar problems, but couldn't find solution that would fix my issue. I have tried registering DbContext and IRepository both as Scoped and Transient, but still keep getting the error. Is it wrong the way I am trying to register DbContext?
This is my code in Consumer.cs
class Consumer
{
static readonly IConfigurationRoot _configuration;
static readonly IServiceProvider _serviceProvider;
static readonly IRepository _repository;
static Consumer()
{
_configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.Build();
_serviceProvider = new ServiceCollection()
.AddSingleton(_configuration)
.AddTransient<IRepository, Repository>()
.AddDbContext<QueueContext>(options => options.UseNpgsql(_configuration.GetConnectionString("Queue")), ServiceLifetime.Transient)
.BuildServiceProvider();
_repository = _serviceProvider.GetService<IRepository>();
}
static void Main(string[] args)
{
...
RunPoll(here some parameters);
}
private static void RunPoll(here some parameters)
{
...
consumer.OnMessage += async (_, msg) => await ProcessMessageAsync(consumer, msg);
...
}
private static async Task ProcessMessageAsync(here some parameters)
{
...
var message = _repository.GetQueueMessage(msg.Value.Id); // On this line I get exception
if (message == null)
{
message = await _repository.AddQueueMessageAsync(msg.Value.Id); // On this line I get exception
}
while(message.NumTries < msg.Value.MaxNumAttempts)
{
message = await _repository.UpdateQueueMessageTriesAsync(message); // On this line I get exception too
}
...
}
}
This is my code in Respository.cs
public class Repository : IRepository
{
private readonly QueueContext _db;
public Repository(QueueContext db)
{
_db = db;
}
public QueueMessage GetQueueMessage(long id)
{
var message = (from qm in _db.QueueMessages
where qm.Id == id
select qm).FirstOrDefault();
return message;
}
public async Task<QueueMessage> AddQueueMessageAsync(long id)
{
var message = new QueueMessage
{
Id = id,
StartDate = DateTime.Now,
LastTryDate = DateTime.Now,
NumTries = 0
}
_db.QueueMessages.Add(message);
await _db.SaveChangesAsync();
return message;
}
public async Task<QueueMessage> UpdateQueueMessageTriesAsync(QueueMessage message)
{
if (message != null)
{
message.NumTries += 1;
message.LastTryDate = DateTime.Now;
_db.QueueMessages.Update(message);
await _db.SaveChangesAsync();
}
return message;
}
}
This is my code in IRepository.cs
public interface IRepository
{
QueueMessage GetQueueMessage(long id);
Task<QueueMessage> AddQueueMessageAsync(long id);
Task<QueueMessage> UpdateQueueMessageTriesAsync(QueueMessage message);
}
This:
_serviceProvider.GetService
is not Dependency Injection. DI is when you define a constructor (or other method) taking an IRepository. Then you can have a Consumer instance with non-static methods, and the repository is appropriately created when the Consumer is created (or the method is called).

Autofac not working with background tasks

I have a task that needs to be run in a separate thread in the background, and I am using SignalR to report progress. This worked some time ago, and I had made some code modifications, but I am at a complete loss as to the error I receive now:
"No scope with a Tag matching 'AutofacWebRequest' is visible from the scope in which the instance was requested. This generally indicates that a component registered as per-HTTP request is being requested by a SingleInstance() component (or a similar scenario.) Under the web integration always request dependencies from the DependencyResolver.Current or ILifetimeScopeProvider.RequestLifetime, never from the container itself."
Any help is greatly appreciated!
public ActionResult DoAction(IEnumerable<string> items){
//...
Func<CancellationToken, Task> taskFunc = CancellationToken => performAction(items);
HostingEnvironment.QueueBackgroundWorkItem(taskFunc);
//...
}
private async Task performAction(IEnumerable<string> items){
var svc = AutofacDependencyResolver.Current.AppicationContainer.BeginLifetimeScope().Resolve<MyService>();
svc.Method(items);
}
public class MyService{
private EntityContext db;
public MyService(EntityContext db){
this.db = db;
}
}
In my Startup.Container.cs file:
builder.RegisterType<MyService>().As<MyService>().InstancePerLifetimeScope();
builder.RegisterType<EntityContext>().InstancePerRequest();
I recently implemented something similar using help from this answer and this answer. You need to create a new lifetime scope - it sounds like your doing this in a web application, so you need to create the scope via the per-request tag (example below).
Another (non-StackOverflow) answer provides similar advice.
public Task Run<T>(Action<T> action)
{
Task.Factory.StartNew(() =>
{
using (var lifetimeScope = _container.BeginLifetimeScope(MatchingScopeLifetimeTags.RequestLifetimeScopeTag))
{
var service = lifetimeScope.Resolve<T>();
action(service);
}
});
return Task.FromResult(0);
}
I did something similar to #Chima Osuji but I think something is off in his answer so I'm gonna describe my solution and explain it.
public class BackgroundTaskFactory : IBackgroundTaskFactory
{
private ILifetimeScope lifetimeScope;
public BackgroundTaskFactory(ILifetimeScope lifetimeScope)
{
this.lifetimeScope = lifetimeScope;
}
public Task Run<T>(Action<T> action)
{
Task task = Task.Factory.StartNew(() =>
{
using (var lifetimeScope = this.lifetimeScope.BeginLifetimeScope())
{
var service = lifetimeScope.Resolve<T>();
action(service);
}
});
return task;
}
}
It's important to point out that my Run method is returning the task that was created on Task.Factory.StartNew. That way someone waits for the result, he gets the right task. In the other solutions they are returning Task.FromResult(0) which returns a dummy task.
BeginLifetimeScope creates a new scope as a child of the injected scope. If the injected scope is an InstancePerLifetimeScope associated to a web request, as soon as the web request scope is disposed, this new scope will also be disposed and it will error out. Child scopes cannot live longer than its parent scopes. Solution? Register BackgroundTaskFactory as singleton. When you do that, the injected lifetime scope will be the root scope, which doesn't get disposed until the app is disposed.
containerBuilder.RegisterType< BackgroundTaskFactory >()
.As< IBackgroundTaskFactory >()
.SingleInstance();
An updated answer based on the code above:
Usage:
public class ServiceModule :Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<AutoFac.AsyncRunner>().As<AutoFac.IAsyncRunner>().SingleInstance();
}
}
public class Controller
{
private AutoFac.IAsyncRunner _asyncRunner;
public Controller(AutoFac.IAsyncRunner asyncRunner)
{
_asyncRunner = asyncRunner;
}
public void Function()
{
_asyncRunner.Run<IService>((cis) =>
{
try
{
//do stuff
}
catch
{
// catch stuff
throw;
}
});
}
}
The Interface:
public interface IAsyncRunner
{
Task Run<T>(Action<T> action);
}
The class:
public class AsyncRunner : IAsyncRunner
{
private ILifetimeScope _lifetimeScope { get; set; }
public AsyncRunner(ILifetimeScope lifetimeScope)
{
//Guard.NotNull(() => lifetimeScope, lifetimeScope);
_lifetimeScope = lifetimeScope;
}
public Task Run<T>(Action<T> action)
{
Task.Factory.StartNew(() =>
{
using (var lifetimeScope = _lifetimeScope.BeginLifetimeScope(MatchingScopeLifetimeTags.RequestLifetimeScopeTag))
{
var service = lifetimeScope.Resolve<T>();
action(service);
}
});
return Task.FromResult(0);
}
}

Categories