Reputation: 9362
I followed a blog post for 'Building Your First Web API with ASP.NET Core and Visual Studio Code'.
http://www.codingflow.net/building-your-first-web-api-with-asp-net-core-and-visual-studio-code/
In this scenario, data are not saved in database but rather in memory like this:
services.AddDbContext<TodoContext>(options => options.UseInMemoryDatabase());
services.AddSingleton<ITodoRepository, TodoRepository>();
You will notice:
(1) UseInMemoryDatabase
on DbContext
(2) AddSingleton
on TodoRepository
This works pretty well. Now I updated the code to save data inside a real database. So main changes are:
services.AddDbContext<TodoContext> (options => options.UseSqlite("Data Source=blogging.db"));
services.AddSingleton<ITodoRepository, TodoRepository>();
I would like to notify that I had to migrate AspNetCore
from 1.0 to 2.2.
Now at runtime, when targetting the controller, I got the error: Cannot consume scoped service 'Models.TodoContext' from singleton 'Models.ITodoRepository'.
I understand that in this situation:
My TodoContext
is a Scoped object: the same within a request, but different across different requests.
My TodoRepository
is a Singleton object: the same for every object and every request.
So I finally changed AddSingleton
to AddScoped
which works pretty fine:
services.AddDbContext<TodoContext> (options => options.UseSqlite("Data Source=blogging.db"));
services.AddScoped<ITodoRepository, TodoRepository>();
My question is: to know whether of not this is an acceptable approach ?
PS: I know there are other questions on this issue on SO but I didn't read clear response.
Upvotes: 5
Views: 9838
Reputation: 7348
ASP.NET core built in dependency injection container is protecting you against a dependency injection anti pattern called "captive dependencies" (you can read more about it and dependency injection in general here).
The basic idea is that a class having a certain lifetime can only depend on objects having a lifetime equal to or longer than its own lifetime. This is because when you do dependency injection you provide a class with all its dependencies (usually via constructor injection) and that class save a reference to the dependency, so that it will be able to use it later when it needs.
Because the class you are designing saves a reference to the injected object, then the injected object is kept alive (at least) for as long as your class' instance it's alive. Maybe an example could help you to understand.
public interface IFoo {}
public class Foo: IFoo {}
public class Bar
{
private readonly IFoo foo;
public Bar(IFoo foo)
{
this.foo = foo ?? throw new ArgumentNullException(nameof(foo));
}
}
var foo = new Foo();
var bar = new Bar(foo); // the object referenced by foo variable is alive as long as the Bar instance is alive, because a reference to it is saved inside the private field of Bar instance
This kind of scenario could put you in trouble if the intended lifetime of Foo instances is shorter than the intended lifetime of Bar instances.
For instance, imagine of being in the context of a web application and suppose that the class Foo is not thread safe, so that accessing an instance of it concurrently from different threads could lead to the corruption of its private state. In this scenario you can decide to register the Foo class as a scoped dependency so that each time the application receives an HTTP request a new instance of Foo is created and that instance will be reused for the entire lifetime of the HTTP request. Doing so is fine, even if handling the HTTP request implies the use of some async operations. Supposing that you await on each involved async operation there can only be at most one thread accessing your Foo instance concurrently and the internal state of the instance is preserved against corruption.
Given this scenario, if you register the Bar class as a singleton then there could be only one instance of Bar for the entire lifetime of the application and so different threads will access the Bar instance concurrently (remember that a web application is able to serve multiple requests concurrently by using a pool of threads). Your singleton instance of Bar has a reference to an instance of Foo which can potentially be used concurrently from multiple threads and this will lead to the corruption of its internal state and to unpredictable results. You have a captive dependency because you have a singleton (the Bar class) which depends on a class having a shorter lifetime (the scoped Foo class).
Going back to you question your solution is fine: you can't register your repository as a singleton, because it must use a scoped dependency, so registering it as a scoped service is the fine approach in my opinion.
Upvotes: 3
Reputation: 974
For your question, yes it is the right way to do the things.
I wanted to summarize the answer in a better way, but then I did some research on this topic and I found an article that deals with questions like you asked.
I would suggest having a look at the article since it has "Best Practices" sections on how to approach certain problems.
Upvotes: -1