Better strategy than constructor injection or not? [closed] - asp.net-core

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 3 years ago.
Improve this question
I am new to .NET Core, as far as I learned from Microsoft docs, we register services in Startup.ConfigureServices and inject them into classes through the constructors.
Something like the code below
Startup
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<ICar, Car>();
services.AddScoped<IPeople, People>();
}
}
Controller
public class CarController : ControllerBase
{
private readonly ICar _car;
private readonly IPeople _people;
public CarController(ICar car, IPeople people)
{
_car = car;
_people = people;
}
[HttpGet]
public ActionResult Get()
{
var result = _people.Talk() + _car.Run();
return Ok(result);
}
}
But one of my colleagues uses another strategy to achieve the injection, according to what he said, we could write less lines and the injection only happens when we invoke the properties.
(Sorry, I can't tell what is the name of this strategy.)
Startup (same as above)
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<ICar, Car>();
services.AddScoped<IPeople, People>();
}
}
Controller
public class CarController : ControllerBase
{
private readonly IServiceProvider _serviceProvider;
private ICar Car => _serviceProvider.GetRequiredService<ICar>();
private IPeople People => _serviceProvider.GetRequiredService<IPeople>();
public CarController(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
[HttpGet]
public ActionResult Get()
{
var result = People.Talk() + Car.Run();
return Ok(result);
}
}
I am not sure is this correct or not.
Can someone point out the pros and cons between these two strategies?
Thank you.

This is way too close to a service locator , for which there are many (good) reasons not to use.
I can see some specific problems with the second solution. The first being that if the resource is registered as transient (services.AddTransient<ICar, Car>();), then each call of the property will return a new instance. This might be too surprising a behavior to expect from a property.
Next problem is a performance. Calling GetRequiredService is not free. And calling it each time a property is accessed might cause performance issues.
Both could be solved by getting the services in the constructor, like this :
private ICar Car;
private IPeople People;
public CarController(IServiceProvider serviceProvider)
{
Car = serviceProvider.GetRequiredService<ICar>();
People = serviceProvider.GetRequiredService<IPeople>();
}
But that still doesn't solve the problem of service locator, like it not being clear what dependencies a class is using and tying the class to the specific IoC provider. And really, you can save yourself the work and just declare the dependencies in the constructor at this point.

Related

Passing DI object to constructor of new class

So, in Razor Pages I know how I can use Dependency Injection to inject my DbContext (for example) in the constructor to access it in the whole class by creating a global private readonly variable.
However, let's say I have a DbManager class that makes all the calls to the DB (to avoid making them from every Razor Page in the application), then I have to pass the context to that class, even though I'm using (at least to my knowledge) dependency injection there as well.
Shouldn't it be able to find it without actually passing it to the constructor, and isn't that the whole point of dependency injection, or am I missing something?
What is the best practice here? Just feels wrong to pass the context as a parameter! I suspect I'm doing something wrong... Am I?
public class IndexModel : PageModel
{
private readonly AppDbContext _context;
public IndexModel(AppDbContext context)
{
_context = context;
}
public void OnGet()
{
var result = new DbManager(_context).GetStuffFromDb(); // Feels weird to pass the context as a parameter here!
}
}
If you are not making any explicit reference calls to the context within IndexModel then only inject the DbManager.
private readonly IDbManager manager;
public IndexModel(IDbManager manager) {
this.manager = manager;
}
public void OnGet() {
manager.GetStuffFromDb();
//...
}
The context will be injected into the manager when being resolved, provided it (the context) was also registered in the composition root
//...
builder.Services.AddScoped<IDbManager, DbManager>();
builder.Services.AddDbContext<AppDbContext>(....);
//...
Reference Explicit Dependencies Principle

NSubstitute for the EF's Core DbContext - how to use it?

I'm trying to create the unit test for a class which uses my EF Core DbContext:
public class MyContext : DbContext
{
public MyContext(DbContextOptions<MyContext> options) : base(options)
{
}
public DbSet<SomeTable> SomeTables { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
...
}
}
the class which is consuming that context is simple:
public class MyClass
{
public MyClass(MyContext db)
{
}
}
so, when I try to create
var fakeContext = Substitute.For<MyContext>();
which ends with the error:
Castle.DynamicProxy.InvalidProxyConstructorArgumentsException : Can not instantiate proxy of class: MyContext.
Could not find a parameterless constructor.
which is being raised by base(options) constructor. So, the net approach was to extend the code:
var dbContextOptions = Substitute.For<DbContextOptions<MyContext>>();
dbContextOptions.ContextType.Returns(typeof(MyContext));
var dbContextOptionsExtension = Substitute.For<IEnumerable<IDbContextOptionsExtension>>();
dbContextOptions.Extensions.Returns(dbContextOptionsExtension);
var myFakeContext = Substitute.For<MyContext>(dbContextOptions);
but it generates more and errors. So, how to fix it ?
You haven't elaborated on the additional errors so I can't provide a specific answer, however trying to mock a DbContext properly is difficult. IMO there are two options.
1) Use the Microsoft in-memory provider.
2) If you want to use a mocked DbContext using NSubstitute, use a pre-existing library that does the mocking for you. There are a few around such as EntityFrameworkCore.Testing.NSubstitute (disclaimer - I am the author).
The in-memory provider is not a complete implementation, it can't do relational operations and there are a swag of LINQ operations it doesn't support, so depending on your use cases the latter may be a better solution.
If you have a specific error/use case in mind pop it up and I may be able to provide a better answer.
Ok, I've used the InMemory provider:
var options = new DbContextOptionsBuilder<AgreementContext>()
.UseInMemoryDatabase("fakeDb")
.Options;
var agreementContext = Substitute.For<MyContext>(options);

ASP.NET Core Data Access Layer Custom Class AddSingleton

I have my EntityFrameworkCore DBContext in ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyDBContext>(Options => ... );
...
}
I also have my data access layer factory class which receives DBContext in constructor
public partial class DataAccessFactory
{
public readonly ProductsDataAccess Products;
public readonly CategoriesDataAccess Categories;
public DataAccessFactory(MyDBContext db)
{
Products = new ProductsDataAccess(db);
Categories = new CategoriesDataAccess(db);
}
}
In order to work with Data Access Layer, I have to create new instance of DataAccessFactory per each request.
My question is, does it make sence and is there any way to create one instance of DataAccessFactory and add it as a Singleton?
You can register your service inside ConfigureServices in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSingleton<DataAccessFactory>();
}
and inject it where you needed:
public class MyController
{
public readonly DataAccessFactory Factory;
public MyController(DataAccessFactory factory)
{
Factory = factory;
}
}
It will be created only once per application life. If you instead will want to change it scope for example per request, just change AddSingleton to AddScoped.
Update:
But be careful when mixing different-scoped services. You cannot inject short living object into long living, because it will cause exceptions. In your situation you will need to change DbContext scope to singleton (sic) or consider to change Factory lifetime to Scoped. Here is example how to change DbContext scope.

Simple Injector Property Injection

How do you perform property injection with Simple Injector.
The with Ninject you do is as per bellow:
[Inject]
public IUnitOfWork UnitOfWork { get; set; }
How can I do the equivalent to this with Simple Injector. I tried finding a solution online but had no luck.
Why do I want to use Property Injection?
I want to use property injection to set up unit of work in my base controller so that it will create a new unit of work OnActionExecuting and commit the changes OnResultExecuted. It also means I don't have to pass in the UoW with each new controller I create through the constructor.
Another option is to use the RegisterInitializer method:
container.RegisterInitializer<BaseControllerType>(controller =>
{
controller.UnitOfWork = container.GetInstance<IUnitOfWork>();
}
It keeps all configuration in your composition root and does not pollute your code base with all kinds of attributes.
Update: (as promised)
While this is a direct answer to your question I have to provide you with a better option, because the usage of a base class for this is a IMO not the correct design, for multiple reasons.
Abstract classes can become real PITA classes as they tend to grow towards a god class which has all kinds of cross cutting concerns
An abstract class, especially when used with property injection, hides the needed dependencies.
With focus on point 2. When you want to unit test a controller which inherits from the base controller, you have no way of knowing that this controller is dependent on IUnitOfWork. This you could solve by using constructor injection instead of property injection:
protected abstract class BaseController : Controller
{
protected readonly IUnitOfWork uoW;
protected BaseController (IUnitOfWork uoW)
{
this.uoW = uoW;
}
}
public class SomeController : BaseController
{
public SomeController(IUnitOfWork uoW) : base(uoW) { }
}
While this solves point 2, point 1 is still lurking. The main reason you're wanting this, as you say, is because you do not want to commit your changes in every Action method. Changes must just be saved by the context when the request is done. And thinking about design in this way is a good thing, because Saving changes is, or can be seen as a cross cutting concern and the way you're implementing this is more or less known as AOP.
If it's comes to AOP, especially if you're working with atomic actions in the action methods of your controllers, there is a far better, more SOLID and more flexible design possible which deals with this very nicely.
I'm referring to the Command/Handler pattern which is described in great detail here (also read this for the query part of your application).
With this patterns you don't inject a generic IUnitOfWork abstraction, but inject the specific needed ICommandHandler<TCommand> abstractions.
The action methods would fire the responsible commandhandler for this specific action. All commandhandlers can simple be decorated by a single open-generic SaveChangesCommandHandlerDecorator, 'ValidationDecorator', 'CheckPermissionsDecorator', etc...
A quick example:
public class MoveCustomerCommand
{
public int CustomerId;
public Address NewAddress;
}
public class MoveCustomerCommandHandler : ICommandHandler<MoveCustomerCommand>
{
public void Handle(MoveCustomerCommand command)
{
// retrieve customer from database
// change address
}
}
public class SaveChangesCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand>
{
private readonly ICommandHandler<TCommand> decoratee;
private readonly DbContext db;
public SaveChangesCommandHandlerDecorator(
ICommandHandler<TCommand> decoratee, DbContext db)
{
this.decoratee = decoratee;
this.db = db;
}
public void Handle(TCommand command)
{
this.decoratee.Handle(command);
this.db.SaveChanges();
}
}
// Register as
container.Register(typeof(ICommandHandler<>), new []{Assembly.GetExecutingAssembly() });
container.RegisterDecorator(typeof(ICommandHandler<>),
typeof(SaveChangesCommandHandlerDecorator<>));
// And use in controller as
public ActionResult MoveCustomer(int customerId, Address address)
{
var command = new MoveCustomerCommand
{ CustomerId = customerId, Address = address };
this.commandHandler.Handle(command);
return View(new ResultModel());
}
This keeps your controllers clean and let it do what it must do, namely be the layer between the business logic (the commandhandler implementation in this case) and the view.
Need to create the following:
First create the attribute class
[System.AttributeUsage(System.AttributeTargets.Property]
public class Inject : Attribute
{
}
Then create a custom property behavior
class PropertySelectionBehavior<TAttribute> : IPropertySelectionBehavior
where TAttribute : Attribute
{
public bool SelectProperty(Type type, PropertyInfo prop)
{
return prop.GetCustomAttributes(typeof(TAttribute)).Any();
}
}
Finally tell the container to use custom behavior
container.Options.PropertySelectionBehavior = new PropertySelectionBehavior<Inject>();
All that is left to do is decorate the property with the attribute
[Inject]
public IUnitOfWork UnitOfWork { get; set; }

How to resolve EF7 current database context in ASP NET 5 out of the controller?

I want to get one context per request in ASP NET 5/EF 7 app for use it in some methods (not in controller).
Unfortunately I did not find the answer in the documentation
ASP.NET vNext template and examples aspnet/MusicStore
You may use some methods for achieving this purpose.
Using .AddDbContext<ApplicationDbContext>(); method for registering ApplicationDbContext in Dependency Injection system (in ConfigureServices() method), leads to the fact that it registered as Scoped dependence(or in another words "per request"). Thereby you only need get it from Dependency Injection system.
Add your dbContext as parameter of constructor method your class (in which you will use dbContext). Then you have to get this class using Dependency Injection system, e.g added it as parameter of controller's constructor.
public class HabitsController : Controller
{
public HabitsController(HabitService habitService)
{
}
}
public class HabitService
{
private GetHabitsContext _dbContext;
public HabitService(GetHabitsContext dbContext)
{
_dbContext = dbContext;
}
}
But if you don't want to use constructor injection for getting context, you can get necessary dependenses using GetService() method (but you need in ServiceProvider instance for that, in example below, i'am getting it through constructor injection too).
using Microsoft.Framework.DependencyInjection; // for beta 6 and below
using Microsoft.Extensions.DependencyInjection; // for beta 7 and above
public class HabitService
{
private IServiceProvider _serviceProvider;
public HabitService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public GetHabit()
{
var dbcontext = _serviceProvider.GetService<ApplicationDbContext>();
}
}
In first method, we can get HabitService through GetService() method too (not through the constructor injection).
using Microsoft.Framework.DependencyInjection; // for beta 6 and below
using Microsoft.Extensions.DependencyInjection; // for beta 7 and above
public class HabitsController : Controller
{
public HabitsController(IServiceProvider serviceProvider)
{
var habitService= serviceProvider.GetService<HabitService>();
}
}
public class HabitService
{
private GetHabitsContext _dbContext;
public HabitService(GetHabitsContext dbContext)
{
_dbContext = dbContext;
}
}
Thanks Tseng for remark:
I should be noted, that it's a pretty bad practice to inject the container into your objects. The container should only be referenced from the composition root and certain type of factories (which are implemented on application level, and not in the domain/business layer)
dbContext in HabitsController and _dbContext in HabitService are different contexts!
I checked, this is the same context.