2

How does one implement property and method dependency injection? How do I add the services? And finally, how good of an impact can that have compared to using constructor injection?

Is it possible to use property/method injection instead of constructor injection?

.Net beginner here and all the help would be appreciated <3

In my wrapper class where I am using constructor DI:

private ICustomerRepository _customerRepository; //For customer
private ICountryRepository _countryRepository; //For country
private IRegionRepository _regionRepository; //for region
private ICityRepository _cityRepository; //for city

// Constructor 
public RepositoryWrapper(OmniConnectDB context, ICustomerRepository customerRepository, IMapper mapper, ICountryRepository countryRepository, IRegionRepository regionRepository, ICityRepository cityRepository)
{
    _context = context;
    _mapper = mapper;

    _customerRepository = customerRepository;
    _countryRepository = countryRepository;
    _regionRepository = regionRepository;
    _cityRepository = cityRepository;   
}

In services:

// configure DI for Location Repositories
services.AddScoped<ICountryRepository, CountryRepository>();
services.AddScoped<IRegionRepository, RegionRepository>();
services.AddScoped<ICityRepository, CityRepository>();

// Configure DI for Customer Service
services.AddScoped<ICustomerService, CustomerService>();
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
  • 1
    There's no property and method injection in .NET Core. What are you actually asking? And what are all those "repository" classes? EF Core's DbContext already is a high level multi-entity Repository and Unit-of-Work, not a database connection. It doesn't need to be wrapped in *low* level CRUD classes. To improve testability or allow you to switch from eg relational databases to MongoDB it makes sense to add a higher level domain repository class that retrieves/persists entire object graphs and covers the entire use case/business scenario/DDD bounded context – Panagiotis Kanavos Mar 09 '23 at 09:56
  • 1
    In ASP.NET Core 6 and later a kind of method injection was added to enable minimal APIs. That's a feature of Minimal APIs though, not DI. The Minimal API middleware inspects the API methods, identifies any injected services and asks the DI container for instances. I think that's done using source generators at compile time. – Panagiotis Kanavos Mar 09 '23 at 09:59
  • _"How does one implement property and method dependency injection?"_ - you don't, or use a third-party DI/IOC framework. _"how good of an impact can that have compared to using constructor injection?"_ - **zero**. Don't go micro-optimizing stuff that doesn't matter and will only complicate matters. – CodeCaster Mar 09 '23 at 10:05
  • Regarding micro-optimizations - that `RepositoryWrapper` looks like a way to recreate `OmniConnectDB` and its entities. An "abstaction" ends up requiring more code than the original isn't a good abstraction. – Panagiotis Kanavos Mar 09 '23 at 10:19
  • @PanagiotisKanavos I am following the repository design pattern in which the norm is to create "repository" classes. And the reason for the wrapper is so that I don't have to make objects of all my different user classes in every API. but like I said, a beginner at this so I could be doing something dumb. I'll link the article I'm following if you want to follow up on repository design patterns. https://code-maze.com/net-core-web-development-part4/#:~:text=What%20is%20a%20Repository%20pattern,our%20data%20from%20the%20database – Ibrahim Bhatty Mar 09 '23 at 10:33
  • @PanagiotisKanavos The context class (OmniConnectDB) and the wrapper class are different. The only thing I am doing in the wrapper is injecting the different user classes (or Repository classes) as objects through the constructor so I do not have to inject them one by one in my API's – Ibrahim Bhatty Mar 09 '23 at 10:34
  • I already explained that DbContext is a Repository. An actual one, not a Data Access Object with CRUD methods. ORMs, like NHibernate or Entity Framework, attempt to give the impression of working with in-memory objects instead of database tables. Changing from SQL Server to MySQL or Oracle only requires a config change. The rest of your code remains unchanged. In fact, through LINQ, your code won't even know an ORM is involved until `SaveChanges` persists all pending changes in a single transaction – Panagiotis Kanavos Mar 09 '23 at 10:50
  • BTW that article is a good example of the *bad* articles that push CRUD classes as "repositories'. None of the Create, Update or Delete methods do what they say. They *mark* objects in the Modified, Added or Deleted state so `SaveChanges` can persist them. Most of the time there's no need to call any of them. They're there only to attach untracked (detached) objects in a specific state. Modifying a tracked object automatically marks it as `Modified`. Adding a new related object tracks it as `Added`, etc. – Panagiotis Kanavos Mar 09 '23 at 11:03
  • DI are the same, but we can have different expression, if we don't use constructor injection, we can use [service injection](https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/dependency-injection?view=aspnetcore-6.0#action-injection-with-fromservices) like `public IActionResult About([FromServices] IDateTime dateTime)` – Tiny Wang Mar 10 '23 at 02:58

2 Answers2

4

As explained in Property Injection in ASP.NET Core, you don't get property injection out of the box, but you can add it through extensions such as Quickwire or by using a different DI/IoC container altogether (such as Autofac).

But:

how good of an impact can that have compared to using constructor injection?

None whatsoever, but you get all of the drawbacks: a class's dependencies are not obvious (you can't get around a constructor), you can leave dependencies null, causing later errors and hindering compile-time safety.

I am following the repository design pattern in which the norm is to create "repository" classes. And the reason for the wrapper is so that I don't have to make objects of all my different user classes in every API [...] https://code-maze.com/net-core-web-development-part4/

This is an antipattern of antipatterns. It is kind of like a service locator, wrapping all the functionality that Entity Framework offers in more limited, equal services with less functionality and less documentation.

So instead of injecting an YourAppDbContext and accessing its members DbSet<Foo> Foos { get; set; }, you inject a RepositoryWrapper (which is the same thing as the DbContext, but can do less) and obtain ... its Foos property.

Skip this tutorial.

CodeCaster
  • 147,647
  • 23
  • 218
  • 272
  • 1
    That article is particularly bad, in a twisted way. It's not quite as bad as the articles that call `SaveChanges` after every CUD call, but none of the methods do what they say they do (create, update, delete). It won't create the immediate concurrency problems those articles do, but once coding gets slightly more complicated than a single-entity CRUD controller, the errors will make no sense to a beginner – Panagiotis Kanavos Mar 09 '23 at 11:09
  • With those repository tutorials, I always wonder whether the author has written anything more complex than a TODO-app, with any more complex queries than a "GetById". How does one load related entities through `FindByCondition(Expression expression)`? Do grouping? Projection (SELECT)? Sorting? Paging? It just. Doesn't. Work. Yeah then you call `FindAll()` (which doesn't do what its name says) and call your Linq methods on that, but then why not skip the middle man (repository) entirely? – CodeCaster Mar 09 '23 at 11:12
  • Like I mentioned, I am new to .NET and currently learning the ropes. The justification for using the wrapper given to me by my peers at work was that it would help in keep all the different modules of the app separate. This would be particularly helpful when we would shift to microservices and would look to containerize said app. What are your thoughts with respect to that knowing microservices will have to be implemented down the line? – Ibrahim Bhatty Mar 14 '23 at 09:39
  • @Ibrahim usually when people start about "microservices" and "containerization" I zone out, because then they probably also start about how amazing the blockchain is and that you should buy Bed Bath and Beyond, i.e. they follow trends without understanding anything. You can perfectly have microservices running in Docker or k8s without wrapping Entity Framework in Yet Another Repository Layer and still have your code nicely separated in domains. – CodeCaster Mar 14 '23 at 10:25
  • I understand that but still, Microservices fit the use case for our product so well, so planning for that does no harm in my mind. But I get your point, I will discuss getting rid of the wrapper with my team. Again, Thank you for you feedback and critique. – Ibrahim Bhatty Mar 16 '23 at 05:40
0

There's no property and method injection in .NET Core itself. The DI middleware supports only constructor injection. The ASP.NET Core middleware does offer a form of action injection, but only for action parameters.

app.MapPost("/todoitems", async (Todo todo, TodoDb db) =>
{
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todo.Id}", todo);
});

In this example the TodoDb DbContext is injected. This was added to enable Minimal API support but benefits controller actions as well.

public IActionResult About([FromServices] IDateTime dateTime)
{
    return Content( $"Current server time: {dateTime.Now}");
}

This isn't a DI middleware feature though and can't be used for other classes. The ASP.NET Core middleware inspects the API methods, identifies any injected services and asks the DI container for instances. I think that's done using source generators at compile time.

Logically, the middleware will:

  • Ask the DI middleware to create a new controller instance
  • Inspect the action for parameters to inject and
  • Finally call the action method with the bound and injected parameters.

The FromServices attribute can be inferred in some cases. This is explained in the section Binding Source Parameter Inference. The ASP.NET Core middleware will check whether any of the parameter types are registered with DI and use DI automatically.

If you want to use method injection for other code you'll have to either use a different DI container or write code that does what ASP.NET Core does itself.

Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236