TLDR: Is it possible to modify the IServiceProvider after the Startup has ran?
I am running dll's (which implement a interface of me) during run-time. Therefore there's a file listener background job, which waits till the plugin-dll is dropped. Now I want to register classes of this dll to the dependency-injection system. Therefore I added IServiceCollection as a Singleton to the DI inside ConfigureServices to use inside another method.
In therefore I created a test-project and just tried to modify the ServiceCollection in the controller, because it was easier than stripping the background job down.
services.AddSingleton<IServiceCollection>(services);
So I added IServiceCollection to my controller to check if I can add a class to the DI after the Startup class has ran.
[Route("api/v1/test")]
public class TestController : Microsoft.AspNetCore.Mvc.Controller
{
private readonly IServiceCollection _services;
public TestController(IServiceCollection services)
{
_services = services;
var myInterface = HttpContext.RequestServices.GetService<IMyInterface>();
if (myInterface == null)
{
//check if dll exist and load it
//....
var implementation = new ForeignClassFromExternalDll();
_services.AddSingleton<IMyInterface>(implementation);
}
}
[HttpGet]
public IActionResult Test()
{
var myInterface = HttpContext.RequestServices.GetService<IMyInterface>();
return Json(myInterface.DoSomething());
}
}
public interface IMyInterface { /* ... */ }
public class ForeignClassFromExternalDll : IMyInterface { /* ... */ }
The Service was successfully added to the IServiceCollection, but the change is not persisted yet to HttpContext.RequestServices even after multiple calls the service count increases each time but I don't get the reference by the IServiceProvider.
Now my question is: Is that possible to achieve and yes how. Or should I rather not do that?
Is it possible to modify the IServiceProvider after the Startup has ran?
Short answer: No.
Once IServiceCollection.BuildServiceProvider() has been invoked, any changes to the collection has no effect on the built provider.
Use a factory delegate to defer the loading of the external implementation but this has to be done at start up like the rest of registration.
services.AddSingleton<IMyInterface>(_ => {
//check if dll exist and load it
//....
var implementation = new ForeignClassFromExternalDll();
return implementation;
});
You can now explicitly inject your interface into the controller constructor
private readonly IMyInterface myInterface;
public MyController(IMyInterface myInterface) {
this.myInterface = myInterface;
}
[HttpGet]
public IActionResult MyAction() {
return Json(myInterface.DoSomething());
}
and the load dll logic will be invoked when that interface is being resolved as the controller is resolved.
Related
is it possible to use Microsoft.Extensions.Logging like use logging in controllers(put in constructor and framework handle it with DI), in class library which my ASP.NET Core web application use that library? and how instantiate class and use method?
public class MyMathCalculator
{
private readonly ILogger<MyMathCalculator> logger;
public MyMathCalculator(ILogger<MyMathCalculator> logger)
{
this.logger = logger;
}
public int Fact(int n)
{
//logger.LogInformation($"Fact({n}) called.");
if (n == 0)
{
return 1;
}
return Fact(n - 1) * n;
}
}
Taked from a previous answer:
...That is the magic of dependency injection, just let the system create the object for you, you just have to ask for the type.
This is also a big topic, ... basically, all you have to do is to define classes as dependencies, so, when you ask for one, the system itself check the dependencies, and the dependencies of that objects, until resolves all the tree of dependencies.
With this, if you need one more dependency latter in your class, you can add directly but you do not need to modify all the classes that uses that class.
To use this in the controller, please check the official docs, you just have to add you dependencies to the constructor, and win!, basically two parts:
Add in your Startup.class
public void ConfigureServices(IServiceCollection services)
{
...
services.AddTransient<MySpecialClassWithDependencies>();
...
}
Then in your controller:
public class HomeController : Controller
{
private readonly MySpecialClassWithDependencies _mySpecialClassWithDependencies;
public HomeController(MySpecialClassWithDependencies mySpecialClassWithDependencies)
{
_mySpecialClassWithDependencies = mySpecialClassWithDependencies;
}
public IActionResult Index()
{
// Now i can use my object here, the framework already initialized for me!
return View();
}
This sould be no different if you library class is in other project, at the end of the day you will be adding the class to the startup, that is how asp net knows what to load.
If you want your code clean, you can use an Extension method to group all your declarations and the just calling services.AddMyAwesomeLibrary(), for example:
In your awesomeLibraryProject:
public static class MyAwesomeLibraryExtensions
{
public static void AddMyAwesomeLibrary(this IServiceCollection services)
{
services.AddSingleton<SomeSingleton>();
services.AddTransient<SomeTransientService>();
}
}
And in your Startup
public void ConfigureServices(IServiceCollection services)
{
...
services.AddMyAwesomeLibrary();
}
I have read many other SO questions on the same topic, but none of the answers that I found applies to my case.
I have successfully added 4 services in my Startup.cs, and it was working fine before. I then added the 5th, and now I realize that something is broken - none of the services work. Even if I remove the 5th completely, the other ones are now also broken with the same error.
Unable to resolve service for type xx while attempting to activate
This is my Startup.cs ConfigureServices.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddStorage();
services.AddSingleton<IMyLocalStorage, MyLocalStorage>();
services.AddSingleton<IFrontEndService, FrontEndService>();
services.AddSingleton<ISystemProvider, SystemProviderService>();
services.AddSingleton<IAuthenticationService, AuthenticationService>();
}
It's the last AuthenticationService that I noticed the error, but even the older previously working services fails now.
public interface IAuthenticationService
{
// ...
}
public class AuthenticationService : IAuthenticationService
{
private readonly FrontEndService frontEndService;
private readonly MyLocalStorage myLocalStorage;
public AuthenticationService(FrontEndService frontEndService, MyLocalStorage myLocalStorage)
{
this.frontEndService = frontEndService;
this.myLocalStorage = myLocalStorage;
}
// ...
}
The services are simple; one interface, one implementation of that interface, and then adding in Startup.cs. I can't figure out why it stopped working.
So if I remove IAuthenticationService, then the error instead shows up in FrontEndService, then complaining on the MyLocalStorage:
public interface IFrontEndService
{
Task<T> GetAsync<T>(string requestUri);
}
public class FrontEndService : IFrontEndService
{
private readonly HttpClient client;
private readonly MyLocalStorage myLocalStorage;
public FrontEndService(HttpClient client, MyLocalStorage myLocalStorage)
{
// ...
}
}
and
public class MyLocalStorage : IMyLocalStorage
{
public MyLocalStorage(LocalStorage storage)
{
this.storage = storage;
}
}
What am I missing here?
When you call methods on IServiceCollection such as .AddSingleton<IFrontEndService, FrontEndService>(), you're saying to the container, "Whenever you see an IFrontEndService dependency, inject an instance of FrontEndService." Now if you take a look at your AuthenticationService:
public class AuthenticationService : IAuthenticationService
{
private readonly FrontEndService frontEndService;
private readonly MyLocalStorage myLocalStorage;
public AuthenticationService(FrontEndService frontEndService, MyLocalStorage myLocalStorage)
{
this.frontEndService = frontEndService;
this.myLocalStorage = myLocalStorage;
}
// ...
}
Notice how you're passing in dependencies of FrontEndService and MyLocalStorage, rather than the interfaces you registered. That means the container doesn't recognise them, so it doesn't know how to fulfil the dependency graph.
You need to change the service to depend on the interfaces, as those are what you've registered with the container:
public class AuthenticationService : IAuthenticationService
{
private readonly IFrontEndService frontEndService;
private readonly IMyLocalStorage myLocalStorage;
public AuthenticationService(IFrontEndService frontEndService, IMyLocalStorage myLocalStorage)
{
this.frontEndService = frontEndService;
this.myLocalStorage = myLocalStorage;
}
// ...
}
#Ted,
Do you remember a question of yours from a couple of weeks ago, in which you used LocalStorage in a service ? At that service you had a constructor with IStorage parameter, but this caused an error, the reason of which was that though the LocalStorage class implements the IStorage interface, the creators of this library added the LocalStorage to the DI container as a concrete class like this:
public static IServiceCollection AddStorage(this IServiceCollection services)
{
return services.AddSingleton<SessionStorage>()
.AddSingleton<LocalStorage>();
}
And therefore, you had to use
(LocalStorage storage)
instead of
(IStorage storage)
The extension method above, could be rewritten thus:
public static IServiceCollection AddStorage(this IServiceCollection services)
{
return services.AddSingleton<IStorage, SessionStorage>()
.AddSingleton<IStorage, LocalStorage>();
}
In which case, you could use the IStorage interface in your constructor.
Now you may form a general rule, and act accordingly.
Ted says:
Thats odd, cause I have used exactly this approach before, and it
worked fine. If you read the docs, Microsoft also uses the concrete
class, not the interface
HttpClient derives from HttpMessageInvoker. It does not implement any interface.
This code-snippet shows how the HttpClient is added to the service container, and made available for injection in your client-side Blazor:
services.AddSingleton<HttpClient>(s =>
{
// Creating the URI helper needs to wait until the JS Runtime is initialized, so defer it.
var uriHelper = s.GetRequiredService<IUriHelper>();
return new HttpClient
{
BaseAddress = new Uri(WebAssemblyUriHelper.Instance.GetBaseUri())
};
});
Hope this helps...
I'm trying to write an ASP.NET Core 2.2 integration test, where the test setup decorates a specific service that would normally be available to the API as a dependency. The decorator would give me some additional powers I'd need in my integration tests to intercept calls to the underlying service, but I can't seem to properly decorate a normal service in ConfigureTestServices, as my current setup will give me:
An exception of type 'System.InvalidOperationException' occurred in Microsoft.Extensions.DependencyInjection.Abstractions.dll but was not handled in user code
No service for type 'Foo.Web.BarService' has been registered.
To reproduce this, I've just used VS2019 to create a fresh ASP.NET Core 2.2 API Foo.Web project...
// In `Startup.cs`:
services.AddScoped<IBarService, BarService>();
public interface IBarService
{
string GetValue();
}
public class BarService : IBarService
{
public string GetValue() => "Service Value";
}
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
private readonly IBarService barService;
public ValuesController(IBarService barService)
{
this.barService = barService;
}
[HttpGet]
public ActionResult<string> Get()
{
return barService.GetValue();
}
}
...and a companion xUnit Foo.Web.Tests project I utilize a WebApplicationfactory<TStartup>...
public class DecoratedBarService : IBarService
{
private readonly IBarService innerService;
public DecoratedBarService(IBarService innerService)
{
this.innerService = innerService;
}
public string GetValue() => $"{innerService.GetValue()} (decorated)";
}
public class IntegrationTestsFixture : WebApplicationFactory<Startup>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
builder.ConfigureTestServices(servicesConfiguration =>
{
servicesConfiguration.AddScoped<IBarService>(di
=> new DecoratedBarService(di.GetRequiredService<BarService>()));
});
}
}
public class ValuesControllerTests : IClassFixture<IntegrationTestsFixture>
{
private readonly IntegrationTestsFixture fixture;
public ValuesControllerTests(IntegrationTestsFixture fixture)
{
this.fixture = fixture;
}
[Fact]
public async Task Integration_test_uses_decorator()
{
var client = fixture.CreateClient();
var result = await client.GetAsync("/api/values");
var data = await result.Content.ReadAsStringAsync();
result.EnsureSuccessStatusCode();
Assert.Equal("Service Value (decorated)", data);
}
}
The behavior kind of makes sense, or at least I think it does: I suppose that the little factory lambda function (di => new DecoratedBarService(...)) in ConfigureTestServices cannot retrieve the concrete BarService from the di container because it's in the main service collection, not in the test services.
How can I make the default ASP.NET Core DI container provide decorator instances that have the original concrete type as their inner service?
Attempted solution 2:
I've tried the following:
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
builder.ConfigureTestServices(servicesConfiguration =>
{
servicesConfiguration.AddScoped<IBarService>(di
=> new DecoratedBarService(Server.Host.Services.GetRequiredService<BarService>()));
});
}
But this surprisingly runs into the same problem.
Attempted solution 3:
Asking for IBarService instead, like this:
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
builder.ConfigureTestServices(servicesConfiguration =>
{
servicesConfiguration.AddScoped<IBarService>(di
=> new DecoratedBarService(Server.Host.Services.GetRequiredService<IBarService>()));
});
}
Gives me a different error:
System.InvalidOperationException: 'Cannot resolve scoped service 'Foo.Web.IBarService' from root provider.'
Workaround A:
I can work around the issue in my small repro like this:
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
builder.ConfigureTestServices(servicesConfiguration =>
{
servicesConfiguration.AddScoped<IBarService>(di
=> new DecoratedBarService(new BarService()));
});
}
But this hurts a lot in my actual application, because BarService doesn't have a simple parameterless constructor: it has a moderately complex dependency graph, so I really would like to resolve instances from the Startup's DI container.
PS. I've tried to make this question fully self-contained, but there's also a clone-and-run rep(r)o for your convenience.
Contrary to popular belief, the decorator pattern is fairly easy to implement using the built-in container.
What we generally want is to overwrite the registration of the regular implementation by the decorated one, making use of the original one as a parameter to the decorator. As a result, asking for an IDependency should lead to a DecoratorImplementation wrapping the OriginalImplementation.
(If we merely want to register the decorator as a different TService than the original, things are even easier.)
public void ConfigureServices(IServiceCollection services)
{
// First add the regular implementation
services.AddSingleton<IDependency, OriginalImplementation>();
// Wouldn't it be nice if we could do this...
services.AddDecorator<IDependency>(
(serviceProvider, decorated) => new DecoratorImplementation(decorated));
// ...or even this?
services.AddDecorator<IDependency, DecoratorImplementation>();
}
The above code works once we add the following extension methods:
public static class DecoratorRegistrationExtensions
{
/// <summary>
/// Registers a <typeparamref name="TService"/> decorator on top of the previous registration of that type.
/// </summary>
/// <param name="decoratorFactory">Constructs a new instance based on the the instance to decorate and the <see cref="IServiceProvider"/>.</param>
/// <param name="lifetime">If no lifetime is provided, the lifetime of the previous registration is used.</param>
public static IServiceCollection AddDecorator<TService>(
this IServiceCollection services,
Func<IServiceProvider, TService, TService> decoratorFactory,
ServiceLifetime? lifetime = null)
where TService : class
{
// By convention, the last registration wins
var previousRegistration = services.LastOrDefault(
descriptor => descriptor.ServiceType == typeof(TService));
if (previousRegistration is null)
throw new InvalidOperationException($"Tried to register a decorator for type {typeof(TService).Name} when no such type was registered.");
// Get a factory to produce the original implementation
var decoratedServiceFactory = previousRegistration.ImplementationFactory;
if (decoratedServiceFactory is null && previousRegistration.ImplementationInstance != null)
decoratedServiceFactory = _ => previousRegistration.ImplementationInstance;
if (decoratedServiceFactory is null && previousRegistration.ImplementationType != null)
decoratedServiceFactory = serviceProvider => ActivatorUtilities.CreateInstance(
serviceProvider, previousRegistration.ImplementationType, Array.Empty<object>());
if (decoratedServiceFactory is null) // Should be impossible
throw new Exception($"Tried to register a decorator for type {typeof(TService).Name}, but the registration being wrapped specified no implementation at all.");
var registration = new ServiceDescriptor(
typeof(TService), CreateDecorator, lifetime ?? previousRegistration.Lifetime);
services.Add(registration);
return services;
// Local function that creates the decorator instance
TService CreateDecorator(IServiceProvider serviceProvider)
{
var decoratedInstance = (TService)decoratedServiceFactory(serviceProvider);
var decorator = decoratorFactory(serviceProvider, decoratedInstance);
return decorator;
}
}
/// <summary>
/// Registers a <typeparamref name="TService"/> decorator on top of the previous registration of that type.
/// </summary>
/// <param name="lifetime">If no lifetime is provided, the lifetime of the previous registration is used.</param>
public static IServiceCollection AddDecorator<TService, TImplementation>(
this IServiceCollection services,
ServiceLifetime? lifetime = null)
where TService : class
where TImplementation : TService
{
return AddDecorator<TService>(
services,
(serviceProvider, decoratedInstance) =>
ActivatorUtilities.CreateInstance<TImplementation>(serviceProvider, decoratedInstance),
lifetime);
}
}
This seems like a limitation of the servicesConfiguration.AddXxx method which will first remove the type from the IServiceProvider passed to the lambda.
You can verify this by changing servicesConfiguration.AddScoped<IBarService>(...) to servicesConfiguration.TryAddScoped<IBarService>(...) and you'll see that the original BarService.GetValue is getting called during the test.
Additionally, you can verify this because you can resolve any other service inside the lambda except the one you're about to create/override. This is probably to avoid weird recursive resolve loops which would lead to a stack-overflow.
There's actually a few things here. First, when you register a service with an interface, you can only inject that interface. You are in fact saying: "when you see IBarService inject an instance of BarService". The service collection doesn't know anything about BarService itself, so you cannot inject BarService directly.
Which leads to the second issue. When you add your new DecoratedBarService registration, you now have two registered implementations for IBarService. There's no way for it to know which to actually inject in place of IBarService, so again: failure. Some DI containers have specialized functionality for this type of scenario, allowing you to specify when to inject which, Microsoft.Extensions.DependencyInjection does not. If you truly need this functionality, you can use a more advanced DI container instead, but considering this is only for testing, that would like be a mistake.
Third, you have a bit of a circular dependency here, as DecoratedBarService itself takes a dependency on IBarService. Again, a more advanced DI container can handle this sort of thing; Microsoft.Extensions.DependencyInjection cannot.
Your best bet here is to use an inherited TestStartup class and factor out this dependency registration into a protected virtual method you can override. In your Startup class:
protected virtual void AddBarService(IServiceCollection services)
{
services.AddScoped<IBarService, BarService>();
}
Then, where you were doing the registration, call this method instead:
AddBarService(services);
Next, in your test project create a TestStartup and inherit from your SUT project's Startup. Override this method there:
public class TestStartup : Startup
{
protected override void AddBarService(IServiceCollection services)
{
services.AddScoped(_ => new DecoratedBarService(new BarService()));
}
}
If you need to get dependencies in order to new up any of these classes, then you can use the passed in IServiceProvider instance:
services.AddScoped(p =>
{
var dep = p.GetRequiredService<Dependency>();
return new DecoratedBarService(new BarService(dep));
}
Finally, tell your WebApplicationFactory to use this TestStartup class. This will need to be done via the UseStartup method of the builder, not the generic type param of WebApplicationFactory. That generic type param corresponds to the entry point of the application (i.e. your SUT), not which startup class is actually used.
builder.UseStartup<TestStartup>();
All the other answers were very helpful:
#ChrisPratt clearly explains the underlying problem, and offers a solution where Startup makes the service registration virtual and then overrides that in a TestStartup that is forced upon the IWebHostBuilder
#huysentruitw answers as well that this is a limitation of the underlying default DI container
#KirkLarkin offers a pragmatic solution where you register BarService itself in Startup and then use that to overwrite the IBarService registration completely
And still, I'd like to offer yet another answer.
The other answers helped me find the right terms to Google for. Turns out, there is the "Scrutor" NuGet package which adds the needed decorator support to the default DI container. You can test this solution yourself as it simply requires:
builder.ConfigureTestServices(servicesConfiguration =>
{
// Requires "Scrutor" from NuGet:
servicesConfiguration.Decorate<IBarService, DecoratedBarService>();
});
Mentioned package is open source (MIT), and you can also just adapt only the needed features yourself, thus answering the original question as it stood, without external dependencies or changes to anything except the test project:
public class IntegrationTestsFixture : WebApplicationFactory<Startup>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
base.ConfigureWebHost(builder);
builder.ConfigureTestServices(servicesConfiguration =>
{
// The chosen solution here is adapted from the "Scrutor" NuGet package, which
// is MIT licensed, and can be found at: https://github.com/khellang/Scrutor
// This solution might need further adaptation for things like open generics...
var descriptor = servicesConfiguration.Single(s => s.ServiceType == typeof(IBarService));
servicesConfiguration.AddScoped<IBarService>(di
=> new DecoratedBarService(GetInstance<IBarService>(di, descriptor)));
});
}
// Method loosely based on Scrutor, MIT licensed: https://github.com/khellang/Scrutor/blob/68787e28376c640589100f974a5b759444d955b3/src/Scrutor/ServiceCollectionExtensions.Decoration.cs#L319
private static T GetInstance<T>(IServiceProvider provider, ServiceDescriptor descriptor)
{
if (descriptor.ImplementationInstance != null)
{
return (T)descriptor.ImplementationInstance;
}
if (descriptor.ImplementationType != null)
{
return (T)ActivatorUtilities.CreateInstance(provider, descriptor.ImplementationType);
}
if (descriptor.ImplementationFactory != null)
{
return (T)descriptor.ImplementationFactory(provider);
}
throw new InvalidOperationException($"Could not create instance for {descriptor.ServiceType}");
}
}
There's a simple alternative to this that just requires registering BarService with the DI container and then resolving that when performing the decoration. All it takes is updating ConfigureTestServices to first register BarService and then use the instance of IServiceProvider that's passed into ConfigureTestServices to resolve it. Here's the complete example:
builder.ConfigureTestServices(servicesConfiguration =>
{
servicesConfiguration.AddScoped<BarService>();
servicesConfiguration.AddScoped<IBarService>(di =>
new DecoratedBarService(di.GetRequiredService<BarService>()));
});
Note that this doesn't require any changes to the SUT project. The call to AddScoped<IBarService> here effectively overrides the one provided in the Startup class.
I am going to implement repository pattern in my asp.net core mvc application , for that i am trying my hands on a simple demo application which include repository and Unit of Work concept.
My First Repository
public interface ICustomerRepository
{
bool Add();
bool Update();
bool Delete();
}
and
public class CustomerRepository:ICustomerRepository
{
public bool Add()
{
return true;
}
public bool Update()
{
return true;
}
public bool Delete()
{
return true;
}
}
Second Repository
public interface IOrderRepository
{
bool Add();
bool Update();
bool Delete();
}
and
public class OrderRepository:IOrderRepository
{
public bool Add()
{
return true;
}
public bool Update()
{
return true;
}
public bool Delete()
{
return true;
}
}
IUnit Of Work
public interface IUnitOfWork
{
IOrderRepository Order {get;}
ICustomerRepository Customer { get; }
void Save();
void Cancel();
}
and
public class UnitOfWork:IUnitOfWork
{
public UnitOfWork(IOrderRepository order, ICustomerRepository customer)
{
Order = order;
Customer = customer;
}
public IOrderRepository Order { get; }
public ICustomerRepository Customer { get; }
public void Save() { }
public void Cancel() { }
}
And in my controller ,
public class HomeController : Controller
{
IUnitOfWork UW { get; }
public HomeController(IUnitOfWork uw)
{
UW = uw;
}
public IActionResult Index()
{
UW.Customer.Add();
UW.Order.Update();
UW.Save();
return View();
}
}
I will add more code later for dapper , but at least it should work wiyhout any error , but it give me error
InvalidOperationException: Unable to resolve service for type 'CoreTS.Repository.UnitOfWork.IUnitOfWork' while attempting to activate 'CoreTS.Controllers.HomeController'.
Someone suggested me to add IUnitOfWork as service in startup.cs under ConfigureService Method, as
services.AddSingleton<IUnitOfWork, UnitOfWork>();
And After Adding this another error
InvalidOperationException: Unable to resolve service for type 'CoreTS.Repository.Order.IOrderRepository' while attempting to activate 'CoreTS.Repository.UnitOfWork.UnitOfWork'.
To make it work i had to add other two repository also in startup.cs also
services.AddSingleton<IOrderRepository, OrderRepository>();
services.AddSingleton<ICustomerRepository, CustomerRepository>();
If there going to be n number of repository than i have to add everyone of them in startup.cs (according to this code ), what is the solution for that.
So
1.] What does these errors means ?
2.] What will be the correct configuration here ?
3.] What is the way to not to add n number of repository as service here ?
Note: As a mentioned already , this is just to understand the flow of pattern , i will add code for Dapper or EF Core later in this
What does these errors means ?
These error means that you are using the services through constructor Dependency Injection but you have not registered those services to DI resolver.
What will be the correct configuration here ?
What you have done is the correct way to resolve services.
What is the way to not to add n number of repository as service here?
You can extend the IServiceCollection as follows in a separate file.
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddCustomServices(this IServiceCollection services,
IConfiguration configuration)
{
services.AddSingleton<IUnitOfWork, UnitOfWork>();
services.AddSingleton<IOrderRepository, OrderRepository>();
services.AddSingleton<ICustomerRepository, CustomerRepository>();
return services;
}
}
Then in the startup class as follows:
services.AddCustomServices(Configuration);
The constructor for HomeController takes an IUnitOfWork, so ASP.NET Core needs to know what instance to give it, that's why you specify it in ConfigureServices. But, your UnitOfWork class' constructor takes an IOrderRepository and an ICustomerRepository, and ASP.NET Core needs to know what instances of those to supply, so you have to specify those in ConfigureServices as well.
I think the configuration you've ended up at is correct, as far as it goes, but it doesn't address your next question...
There's already a problem with your pattern without the ASP.NET Core dependency injection issues. Your constructor for UnitOfWork takes 2 distinct parameters, one for each repository. If you want to have N different repositories, that constructor no longer works. Instead, maybe you need to introduce a "repository manager" class and just inject that into the constructor (add it in ConfigureServices too). Then you need to devise a relationship between UnitOfWork and RepositoryManager that allows UnitOfWork to work with any specific repository.
Well, the error message is quite meaningful. The DI container has to resolve the instance of IUnitOfWork which has two dependencies that are injected into its ctor. So DI container has to resolve these two as well.
There is no built-in functionality in asp.net-core that allows you to register all your repositories using pattern matching or something like that. You could register all dependencies one by one or use 3rd party libraries.
With Scrutor you can do something like this:
services.Scan(scan => scan
.FromAssemblyOf<OrderRepository>()
.AddClasses(classes => classes.AssignableTo<IRepository>())
.AsImplementedInterfaces()
.WithSingletonLifetime());
Note that for it to work all repositories must implement IRepository interface (which can be empty)
Conclusion:
If it's only a few dependencies I'd probably register them one by one however if you plan to add N repositories later - use 3rd party libs.
There is no service registered in the IoC container for IUnitOfWork/IOrderRepository. You solved this by registering these services using AddSingleton method.
Not sure what you mean by correct configuration, but using AddSingleton/AddTransient/AddScoped you are registering some classes as services in the IoC container. So when you inject something (for example into your HomeController), then you are using the interface mapped to some concrete implementation.
You have to register the service somehow, that is what you are doing with methods mentioned before. If you won't register it, it won't be resolved and you will get exceptions when trying to activate some other dependent services. If you want to register some services without doing it explicitely, you will have to scan the assembly and look for types that you want to register.
We have a project where we need to use DI and ASP Core.
I'm very new to this and have a question.
I have a controller named HomeController like this:
public class HomeController : BaseController {
private IOrderService _orderService;
public HomeController(IOrderService orderService) {
_orderService = orderService;
}
public IActionResult Index() {
var orders = _orderService.GetMyOrders();
return View(orders);
}
}
The code looks like this:
public class OrderService : BaseService, IOrderService {
public OrderService(IDataContextService dataContextService) {
_dataContextService = dataContextService;
}
public List<Orders> GetMyOrders() {
var orders = // do my code here which works fine!;
// here i need some code do check orders for delivery so
DeliveryService deliveryService = new DeliveryService(_dataContextService);
// update my orders and return these orders
return orders;
}
}
public class DeliveryService : BaseService, IDeliveryService {
public DeliveryService(IDataContextService dataContextService) {
_dataContextService = dataContextService;
}
public void MyMethod() {
}
}
public class BaseService {
protected IDataContextService _dataContextService;
}
Almost all my services have a constructor like the OrderService and DeliveryService. My question is, do I have to pass the _dataContextService every time, or is there a solution within the dependency pattern?
You should keep it the way you have it and asp.net core IoC will inject it for you, but make sure it is injected per request, this will help to insantiate only one context for each request and dispose it after the request is served.
You can register the context and services in the ConfigureServices method inside the Startup class as below
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
// Add application services.
services.AddTransient<HomeController>();
services.AddTransient<IOrderService , OrderService >();
services.AddTransient<IDeliveryService, DeliveryService>();
services.AddScoped<IDataContextService , YourDataContextService >();
}
The AddScoped method will create only one instance of the object for each HTTP request
If I understand correctly what you are asking, you are looking for an IoC container. .NET Core has built in support for dependency injection. Basically, you just indicate which implementation should be provided when an interface is requested. Then the container will instantiate the types for you. See for example https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/dependency-injection.
Hope that helps