Let's suppose a transient Service:
Startup.cs / ConfigureServices:
services.AddTransient<IMyService, MyService>();
Here is the content of the service:
interface IMyService
{
public Task Traitement1();
public Task Traitement2();
}
class MyService: IMyService
{
MyDbContext _db;
public MyService(MyDbContext db)
{
_db = db;
}
public async Task Traitement1()
{
var query = await _db.Table1.Where(...).SelectAsync(...);
...
// Very long tasks, reading and writing database
await _db.SaveChangesASync();
}
public async Task Traitement2()
{
var query = await _db.Table2.Where(...).SelectAsync(...);
...
// Very long tasks, reading and writing database
await _db.SaveChangesASync();
}
}
I have a blazor page:
#inject IMyService service
I have 2 buttons: The first calls Traitement1 and the second calls Traitement2.
User can click on the first button and click on the second button without waiting the end of the first traitement.
I get an error message because 2 queries are running on the same dbcontext.
My question: How can i force framework to create 2 instances of MyService ?
Thanks
Related
I am having an ASP.net core 3.0 app and I want to see if I can register some of my Orleans Cluster Clients asynchronously on app startup, due to the fact the creation and making the connections to Orleans Cluster are heavy. According to this article I created my own IHostedService, but when I implemented startAsync method I am not sure how to get the autofac container which I am using in Startup.cs and update it with my clients registrations. I have read this but see my below code, still I don't see the clients are getting registered. Is it doable or am I missing anything here? thanks!
Startup.cs
...
public static IServiceProvider ConfigureServices(IServiceCollection services)
{
var coreBuilder = new ContainerBuilder();
// other autofac registrations...
services.AddHostedService<MyService>();
coreBuilder.populate(services);
var container = coreBuilder.Build();
var serviceProvider = new AutofacServiceProvider(container);
return serviceProvider;
}
MyService.cs
public MyService : IHostedService
{
private readonly IServiceProvider _serviceProvider;
public MyService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
// get the autofac container from Startup.cs and update with cluster client registrations?
using(var scope = this._serviceProvider.GetRequiredService<ILifeTimeScope>()
.BeginLifeTimeScope(builder => do registration here...)) {}
}
// noop
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
You cannot update the DI container on-the-fly like that. Once it's built, it's built.
You have another option: make a factory class that caches the clients, initialize them in the background, then retrieve them from the factory.
class MyService
{
// ...
}
class MyServiceFactory
{
private ConcurrentDictionary<string, MyService> _instances = new ConcurrentDictionary<string, MyService>();
public async Task<MyService> CreateAsync(string key)
{
if (_instances.TryGetValue(key, out var service))
{
return service;
}
// perform expensive initialization
// ...
service = new MyService();
_instances[key] = service;
return service;
}
}
class MyServiceInitializer: BackgroundService
{
private MyServiceFactory _serviceFactory;
public MyServiceInitializer(MyServiceFactory serviceFactory)
{
_serviceFactory = serviceFactory;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await _serviceFactory.CreateAsync("first instance");
await _serviceFactory.CreateAsync("second instance");
}
}
Register the factory as singleton, (or make Instances a static property).
services.AddSingleton<MyServiceFactory>();
services.AddHostedService<MyServiceInitializer>();
Then resolve an instance you need. It will resolve instantly, because it's been initialized in the background.
class MyController
{
private MyServiceFactory _serviceFactory;
public MyController(MyServiceFactory serviceFactory)
{
_serviceFactory = serviceFactory;
}
[HttpGet]
public async Task<ActionResult> Index()
{
var service = await _serviceFactory.CreateAsync("first instance");
// use the service
}
}
I have a standard .Net core Api and want to use a Open Generic IReposistory and decorate that with a DomainEventPublisher for pushing out events to servicsBus after persisting.
However, I have used Simple Injector a lot earlier which I'm a big fan of. But now when using MediatR Im trying to simplify DI by using just .net Core DI together with Scrutor package for decorating.
Problem is an error I get:
"The number of generic arguments provided doesn't equal the arity of the generic type definition." from Scrutor when trying to register decorator in Startup (2nd line below).
services.AddSingleton(typeof(IRepository<>), typeof(Repository<>));
services.Decorate(typeof(IRepository<>), typeof(DomainEventPublisher<>));
I have closed these generic classes/interfaces and then it works. But Im not good with that. I would to i the right way like I used to do in Simpleinjector, and register open generic decorator.
Any suggestions what might be the problem?
public class Repository<TEntity> : IRepository<TEntity>
{
private readonly CosmosClient _client;
private readonly IDataContext<TEntity> _context;
private readonly Container _container;
public Repository(CosmosClient client, IDataContext<TEntity> context)
{
_client = client;
_context = context ?? throw new ArgumentNullException(nameof(context));
_container = _client.GetContainer(_context.GetDatabase(), _context.GetContainer());
}
public virtual async Task Add(TEntity entity)
{
try
{
var response = await _container.CreateItemAsync(entity, new PartitionKey(_context.GetPartitionKey(entity)));
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
public virtual async Task<TEntity> Get(string id)
{
var response = await _container.ReadItemAsync<TEntity>(id, new PartitionKey(_context.GetPartitionKey(id)));
return response.Resource;
}
public virtual async Task<TEntity> Update(TEntity entity)
{
var response = await _container.UpsertItemAsync(entity, new PartitionKey(_context.GetPartitionKey(entity)));
return response.Resource;
}
public async Task Remove(string id)
{
var response = await _container.DeleteItemAsync<TEntity>(id, new PartitionKey(_context.GetPartitionKey(id)));
}
public class DomainEventPublisher<TEntity> : IRepository<TEntity>
{
private readonly IRepository<TEntity> _decoratedRepository;
private readonly ITopicAdapter _bus;
private readonly IMapper _mapper;
private List<IDomainEvent> _eventsToProcess = new List<IDomainEvent>();
public DomainEventPublisher(IRepository<TEntity> decoratedRepository, ITopicAdapter bus, IMapper mapper)
{
_decoratedRepository = decoratedRepository ?? throw new ArgumentNullException(nameof(decoratedRepository));
_bus = bus ?? throw new ArgumentNullException(nameof(bus));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
}
public async Task Add(TEntity entity)
{
// Get all domain events raised by source entity
var events = CollectEvents(entity);
await _decoratedRepository.Add(entity);
await HandleEvents(events);
}
public async Task<TEntity> Get(string id)
{
return await _decoratedRepository.Get(id);
}
public async Task<TEntity> Update(TEntity entity)
{
// Get all domain events raised by source entity
var events = CollectEvents(entity);
var result = await _decoratedRepository.Update(entity);
await HandleEvents(events);
return result;
}
public async Task Remove(string id)
{
await _decoratedRepository.Remove(id);
}
private List<IDomainEvent> CollectEvents(TEntity entity)
{
if (entity is IEntity entityWithEvents)
return entityWithEvents.Events;
return new List<IDomainEvent>();
}
private async Task HandleEvents(List<IDomainEvent> events)
{
// if we ended up on this line we know that repository persisted changes and now send events to bus
foreach (var domainEvent in events)
{
await _bus.Send(_mapper.MapTo(domainEvent));
}
}
}
It's impossible to apply decorators to open-generic registration with Scrutor. This is discussed here on the Scrutor forum. This is due to a limitation of the underlying Microsoft DI Container. This is a limitation that can't be circumvented by Scrutor.
Instead, switch to one of the mature DI Containers that do support this.
With ASP.NET MVC Core it is possible to use the Unity DI packages by loading the appropriate NuGet packages (Unity.Container and Unity.Microsoft.DependencyInjection) and then calling the UseUnityServiceProvider() extension method when building the web host in Program.cs. This extension method is based off of the IWebHostBuilder interface. After initializing the host it is possible to access the Unity DI functionality via the .NET Core GetService interface and any constructor injection.
I'm working on a console based application that will use HostBuilder and the IHostBuilder interface. I've tried reimplementing the registration logic from the UseUnityServiceProvider() extension (available here: https://github.com/unitycontainer/microsoft-dependency-injection/blob/master/src/HostingExtension.cs) as part of the ConfigureServices() method call against IHostBuilder, but the change in service provider does not appear to be visible to downstream DI calls.
Has anyone been successful in getting Unity to work with the Microsoft DI methods in an application created using HostBuilder?
Update As requested, here is an example of what I was trying (taken from the Unity source). Of course, this does not work. TestService has a constructor which should be injected with an object defined in MyUnityExtension. This does not happen.
private static async Task MainTest()
{
var container = new UnityContainer().AddNewExtension<MyUnityExtension>();
var factory = new ServiceProviderFactory(container);
var hostBuilder = new HostBuilder()
.ConfigureServices((hostBuilderContext, services) =>
{
services.Replace(ServiceDescriptor.Singleton<IServiceProviderFactory<IUnityContainer>>(factory));
services.Replace(ServiceDescriptor.Singleton<IServiceProviderFactory<IServiceCollection>>(factory));
services.AddHostedService<TestService>();
});
await hostBuilder.RunConsoleAsync();
}
Not sure what you want to achieve with new UnityContainer().AddNewExtension<MyUnityExtension>().
If you just want some registered service to be injected into TestService, why not just use IUnityContainer.RegisterType<TInterface, TImplementation>() ?
Here is a working IHost setup in Program.cs (.NET Core 3.1, Microsoft.Extensions.Hosting v3.10, Unity.Microsoft.DependencyInjection v5.11.5):
public static async Task Main(string[] args)
{
var container = new UnityContainer();
container.RegisterType<IService, MyService>();
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<TestService>();
})
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConsole();
})
.UseUnityServiceProvider(container);
await builder.RunConsoleAsync();
}
The interface:
public interface IService
{
string Name { get; }
}
The implementation:
public class MyService : IService
{
public string Name => "My name";
}
The TestService:
public class TestService : IHostedService
{
private readonly IService service;
private readonly ILogger logger;
public TestService(IService service, ILogger<TestService> logger)
{
this.service = service;
this.logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
this.logger.LogInformation("Hello {n}", this.service.Name);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
I have a saga I'm trying to run with masstransit that requires a refit client, and i want to have the client dependency injected into the saga.
I'm using MassTransit 5.2.3 with MassTransit.Extensions.DependencyInjection 5.2.3 to setup as follows:
...
var serviceCollection = new ServiceCollection();
serviceCollection.AddRefitClient<IClient>(...).ConfigureHttpClient(...);
serviceCollection.AddMassTransit(c =>
{
c.AddSaga<MySaga>();
});
serviceCollection.AddScoped<MySaga>();
serviceCollection.AddSingleton<ISagaRepository<MySaga>, MessageSessionSagaRepository<MySaga>>(x => new MessageSessionSagaRepository<MySaga>());
var serviceProvider = serviceCollection.BuildServiceProvider();
var bus = Bus.Factory.CreateUsingAzureServiceBus(cfg =>
{
var host = ...;
...
cfg.ReceiveEndpoint(host, "MyQueue", e =>
{
...
e.Saga<MySaga>(serviceProvider);
});
});
bus.Start();
...
the code for the saga is:
class MySaga :
ISaga,
InitiatedBy<IStep1>,
Orchestrates<IStep2>
{
private readonly IClient _client;
public Guid CorrelationId { get; set; }
public MySaga(IClient client)
{
_client = client;
}
public async Task Consume(ConsumeContext<IStep1> context) {...}
public async Task Consume(ConsumeContext<IStep2> context) {...}
}
This causes a "Failed to create the saga connector for MyNamespace.MySaga" exception, with inner exception "ConfigurationException: The saga MyNamespace.MySaga must have either a default constructor and a writable CorrelationId property or a constructor with a single Guid argument to assign the CorrelationId"
MassTransit focus for sagas had been moved to state machine sagas, so overall Automatonymous sagas are preferred. For the "classic" sagas, the requirement is given to you in the error message. You can use the code from the Injection_Specs.cs
var refitClient = ...;
var serviceCollection = new ServiceCollection();
serviceCollection.AddMassTransit(c =>
{
c.AddSaga<MySaga>();
});
serviceCollection.AddScoped<MySaga>();
serviceCollection.AddSingleton<ISagaRepository<MySaga>, MessageSessionSagaRepository<MySaga>>(x => new MessageSessionSagaRepository<MySaga>());
var serviceProvider = serviceCollection.BuildServiceProvider();
var bus = Bus.Factory.CreateUsingAzureServiceBus(cfg =>
{
var host = ...;
...
cfg.ReceiveEndpoint(host, "MyQueue", e =>
{
...
e.Saga<MySaga>(serviceProvider,
x => x.UseExecute(ctx => ctx.Saga.Client = refitClient));
});
});
bus.Start();
Of course, you will need to have the Client public property in the saga class and you won't be using the constructor injection. You also need those two constructors that the error message is telling you to have:
class MySaga :
ISaga,
InitiatedBy<IStep1>,
Orchestrates<IStep2>
{
public IClient Client { get; set; }
public Guid CorrelationId { get; set; }
public MySaga()
{
}
public MySaga(Guid correlationId)
{
CorrelationId = correlationId;
}
public async Task Consume(ConsumeContext<IStep1> context) {...}
public async Task Consume(ConsumeContext<IStep2> context) {...}
}
Design consideration
One thing that I have to add is that the Saga in MassTransit is close to the Process Manager pattern. It means that sagas should not have any logic except the orchestration logic and should not do anything else except for handling messages. So, I won't recommend using things like REST API clients inside sagas. If the saga needs to get some data to make a decision about message routing, it should use messages to get that data.
I'm using VS Code and following an ASP.NET Core/ EF Core tutorial and admit I'm not quite clear on how the async, await and Task do (well, I know the first two, but not the third.) I am implementing a repository for the first time, and a UnitofWork Class and Interface to go with it. Here is the UnitofWork Class:
using System.Threading.Tasks;
namespace vega.Persistence
{
public class UnitOfWork : IUnitOfWork
{
private readonly VegaDbContext context;
public UnitOfWork(VegaDbContext context)
{
this.context = context;
}
public async Task CompleteAsync()
{
await context.SaveChangesAsync();
}
}
}
In addition to the subject-line error shown by VS-Code intellisense when I hover over the CompleteAsync action name, I get this:
'UnitOfWork.CompleteAsync()': not all code paths return a value [AppName]
Other perhaps relevant snippets:
using System;
using System.Threading.Tasks;
namespace vega.Persistence
{
public interface IUnitOfWork
{
Task CompleteAsync();
}
}
In my Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
// Add Repository and UnitOfWork --scoped (instance persits for life of request),
// not Transient or Singleton
services.AddScoped<IVehicleRepository, VehicleRepository>();
services.AddScoped<IUnitOfWork, UnitOfWork>();
}
You have another vega.Persistence.Task type defined in your project. Just add the namespace to correct System.Threading.Tasks.Task as return type of your method:
public async System.Threading.Tasks.Task CompleteAsync()
{
await context.SaveChangesAsync();
}
And same in your interface.