Should Containers Be Initialized In Service Classes C#
TL;DR: Dependency Injection is i of the most known techniques that assist yous to create more maintainable code. .Cyberspace Core provides you lot with extensive back up to Dependency Injection, simply it may not e'er be clear how to apply it. This tutorial will try to clarify the various Dependency Injection concepts and will innovate you lot to the support provided by .Net Core.
The Dependency Problem
Have you ever had to modify a lot of code because of a new uncomplicated requirement? Take yous ever had a hard time trying to refactor role of an application? Accept you always been in trouble writing unit tests because of components that required other components?
If you answered yes to any of these questions, perchance your codebase suffers from dependency. Information technology's a typical disease of the code of an application when its components are too coupled. In other words, when a component depends on another i in a also-tight way. The principal effect of component dependency is the maintenance difficulty of the lawmaking, which, of class, implies a college cost.
A dependency instance
Accept a look at a typical example of code affected past dependency. Offset by analyzing these C# classes:
using System ; using System.Collections.Generic ; namespace OrderManagement { public course Order { public cord CustomerId { get ; gear up ; } public DateTime Date { go ; set ; } public decimal TotalAmount { go ; prepare ; } public List<OrderItem> Items { get ; fix ; } public Lodge ( ) { Items = new Listing<OrderItem> ( ) ; } } public grade OrderItem { public cord ItemId { get ; set ; } public decimal Quantity { become ; set ; } public decimal Price { become ; set ; } } }
This code defines 2 classes, Order
and OrderItem
, that represent the order of a customer. The orders are managed past the OrderManager
course implemented as follows:
using Organization.Threading.Tasks ; namespace OrderManagement { public form OrderManager { public async Job< string > Transmit ( Order order) { var orderSender = new OrderSender ( ) ; return wait orderSender. Send (club) ; } } }
The OrderManager
class implements the Transmit ( )
method, which sends the order to some other service to process. Information technology relies on the OrderSender
class to really send the order received equally an argument.
This is the lawmaking implementing the OrderSender
class:
using Arrangement.Net.Http ; using System.Text ; using Organization.Text.Json ; using System.Threading.Tasks ; namespace OrderManagement { public class OrderSender { private static readonly HttpClient httpClient = new HttpClient ( ) ; public async Job< string > Send ( Social club club) { var jsonOrder = JsonSerializer. Serialize <Lodge> (society) ; var stringContent = new StringContent (jsonOrder, UnicodeEncoding.UTF8, "application/json" ) ; //This statement calls a not existing URL. This is just an example... var response = look httpClient. PostAsync ( "https://mymicroservice/myendpoint" , stringContent) ; return response.Content. ReadAsStringAsync ( ) .Outcome; } } }
As you tin see, the Send ( )
method of this class serializes the gild and send it via HTTP Mail to a hypothetical microservice that volition process it.
The code shown hither is non meant to be realistic. It is just a rough example.
What happens if yous need to modify the way of sending an gild? For example, suppose you also desire to send orders via electronic mail or to send them to another microservice that uses gRPC instead of HTTP. Also, how comfortable exercise you feel to create a unit of measurement test for the OrderManager
class?
Since OrderManager
depends on OrderSender
, you will be forced to alter in some way both classes to back up multiple sender types. Changes in the lower-level component (OrderSender
) may touch on the higher-level component (OrderManager
). Even worse, it will be almost impossible to automatically test the OrderManager
course without risking to mess your code.
This is just a simple study example. Call up of the impact that dependency may have in a more than complex scenario with many dependent components. That could really become a huge mess.
The Dependency Inversion Principle
The last of the SOLID principles proposes a way to mitigate the dependency problem and make information technology more than manageable. This principle is known every bit the Dependency Inversion Principle and states that:
- High-level modules should not depend on depression-level modules. Both should depend on abstractions
- Abstractions should not depend on details. Details should depend on abstractions.
You can translate the two formal recommendations as follows: in the typical layered compages of an application, a high-level component should non directly depend on a lower-level component. You should create an abstraction (for example, an interface) and make both components depend on this abstraction.
Translated in a graphical fashion, information technology appears equally shown past the post-obit motion-picture show:
Of course, all of this may seem too abstract. Well, this article will provide you lot with examples to clarify the concepts near dependency and the techniques to mitigate it. While the full general concepts are valid for whatever programming language and framework, this commodity will focus on the .Internet Core framework and will illustrate the infrastructure it provides yous to assist in reducing component dependency.
"Learn what Dependency Injection is and how to use it to improve your code maintenance in .NET Core."
Tweet This
A trip in the dependency lingo
Before exploring what .NET provides yous to fight the dependency disease of your code, it's necessary to put some order in the terminology. You may have heard many terms and concepts about code dependency, and some of them seem to be very similar and may have been confusing. Well, here is an endeavour to give a proper definition of the nearly common ones:
-
Dependency Inversion Principle: information technology's a software design principle; information technology suggests a solution to the dependency problem but does not say how to implement it or which technique to utilise.
-
Inversion of Control (IoC): this is a way to use the Dependency Inversion Principle. Inversion of Control is the actual mechanism that allows your higher-level components to depend on abstraction rather than the concrete implementation of lower-level components.
Inversion of Command is besides known every bit the Hollywood Principle. This name comes from the Hollywood cinema industry, where, after an audition for an histrion role, usually the director says, don't phone call us, we'll phone call you.
-
Dependency Injection: this is a design pattern to implement Inversion of Control. It allows you to inject the physical implementation of a low-level component into a loftier-level component.
-
IoC Container: also known as Dependency Injection (DI) Container, it is a programming framework that provides you with an automated Dependency Injection of your components.
Dependency Injection approaches
Dependency Injection is possibly the most known technique to solve the dependency problem.
You tin use other design patterns, such as the Factory or Publisher/Subscriber patterns, to reduce the dependency betwixt components. Nevertheless, information technology mostly derives on the type of problem your code is trying to solve.
As said above, it is a technique to providing a component with its dependencies, preventing the component itself from instantiating by themselves. You can implement Dependency Injection on your own by creating instances of the lower-level components and passing them to the higher-level ones. Yous tin exercise information technology using three common approaches:
- Constructor Injection: with this approach, you create an instance of your dependency and pass it as an statement to the constructor of the dependent class.
- Method Injection: in this case, you create an case of your dependency and pass it to a specific method of the dependent class.
- Property Injection: this approach allows you to assign the instance of your dependency to a specific property of the dependent class.
.NET Cadre and the Dependency Injection
Y'all can implement Dependency Injection manually past using one or more of the three approaches discussed before. All the same, .NET Core comes with a built-in IoC Container that simplifies Dependency Injection management.
The IoC Container is responsible for supporting automatic Dependency Injection. Its basic features include:
- Registration: the IoC Container needs to know which type of object to create for a specific dependency; then, it provides a mode to map a type to a course so that information technology tin create the correct dependency instance.
- Resolution: this characteristic allows the IoC Container to resolve a dependency by creating an object and injecting it into the requesting grade. Thanks to this feature, you lot don't have to instantiate objects manually to manage dependencies.
- Disposition: the IoC Container manages the lifetime of the dependencies post-obit specific criteria.
You will see these features in action in a while. But before this, some bones information is needed.
The .Internet Core built-in IoC Container implements the IServiceProvider
interface. So if for some reason, you desire to create your own IoC Container, you lot should implement this interface. In .Net Core, the dependencies managed past the container are chosen services. You accept two types of services:
- Framework services: these services are part of the .Net Core framework; some examples of framework services are
IApplicationBuilder
,IConfiguration
,ILoggerFactory
, etc. - Application services: these are the services that you create in your application; since the IoC doesn't know them, y'all need to register them explicitly.
Dealing with Framework Services
As a .Net Cadre programmer, you've already used the born IoC Container to inject framework services. Indeed, .Net Cadre heavily relies on information technology. For example, the Startup
grade in an ASP.Cyberspace awarding uses Dependency Injection extensively:
public form Startup { public Startup ( IConfiguration configuration) { // ... lawmaking ... } public void Configure ( IApplicationBuilder app, IWebHostEnvironment env) { // ... code ... } // ... code ... }
In this example, the Startup ( )
constructor requires a configuration
parameter implementing the IConfiguration
type. Since IConfiguration
is one of the framework service types, the IoC Container knows how to create an example of it and inject information technology into the Startup
class applying the Constructor Injection arroyo. The same applies to the Configure ( )
method. Keep in mind, however, that only the following framework service types can be injected in the Startup ( )
constructor and in the Configure ( )
method of a standard ASP.Cyberspace application: IWebHostEnvironment
, IHostEnvironment
, and IConfiguration
. This is a special case for framework services considering you don't need to register them.
Registering framework services
In full general, you lot have to register services needed by your ASP.Net application in the ConfigureServices ( )
method of the Startup
form. This method has an IServiceCollection
parameter representing the list of services your awarding depends on. Practically, the collection represented past this parameter allows you to register a service in the IoC Container. Consider the following case:
public class Startup { // ... code ... public void ConfigureServices ( IServiceCollection services) { services. AddAuthentication (options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; } ) ; } // ... lawmaking ... }
Here, you are registering a dependency for managing authentication for your awarding. In this specific instance, you are using the extension method AddAuthentication ( )
with the parameters describing the hallmark blazon you desire to use. The framework provides extension methods to annals and configure dependencies for the near common services. It also provides the Add ( )
method to register generic dependencies, as in the post-obit example:
public form Startup { // ... code ... public void ConfigureServices ( IServiceCollection services) { services. Add ( new ServiceDescriptor ( typeof ( ILog ) , new MyLogger ( ) ) ) ; } // ... lawmaking ... }
In this case, you are registering a log service implementing the ILog
interface. The second parameter of the Add ( )
method is an case of the MyLogger
class you take implemented in your project. As you lot may guess, this registration creates a singleton service, i.e., a single instance of the MyLogger
class that will fulfill whatever requests coming from your application.
Service lifetimes
This single instance of your dependency will live for the unabridged lifetime of your application. This may be suitable for a service similar a logger, but information technology is unacceptable for other services. The IoC Container allows you to control the lifetime of a registered service. When you register a service specifying a lifetime, the container will automatically dispose of information technology accordingly. You take three service lifetimes:
- Singleton: this lifetime creates one case of the service. The service case may be created at the registration time by using the
Add ( )
method, as y'all saw in the example above. Alternatively, the service instance tin can be created the first time information technology is requested by using theAddSingleton ( )
method. - Transient: by using this lifetime, your service volition be created each time it will be requested. This means, for case, that a service injected in the constructor of a class will terminal as long as that class instance exists. To create a service with the transient lifetime, yous have to use the
AddTransient ( )
method. - Scoped: the scoped lifetime allows you to create an instance of a service for each customer request. This is particularly useful in the ASP.NET context since it allows you to share the same service instance for the duration of an HTTP request processing. To enable the scoped lifetime, you lot need to use the
AddScoped ( )
method.
Choosing the correct lifetime for the service you lot want to use is crucial both for the correct beliefs of your awarding and for better resource management.
Managing Awarding Services
Most of the concepts yous learned from framework services are still valid for your awarding services. Yet, framework services are already designed to exist injectable. The classes you lot define in your application need to be adjusted to leverage Dependency Injection and integrate with the IoC Container.
To see how to apply the Dependency Injection technique, think the order management example shown early in this commodity and assume that those classes are within an ASP.NET application. Y'all can download the lawmaking of that application from this GitHub repository by typing the following command in a concluding window:
git clone -b starting-indicate --single-branch https: / /github.com/auth0-blog/dependency-injection-dotnet-core
This control volition clone in your machine just the branch starting-point
of the repository. Afterwards cloning the repository, you will observe the dependency-injection-dotnet-core
folder on your machine. In this folder, yous have the OrderManagementWeb
subfolder containing the ASP.NET Core projection with the classes shown at the starting time of this commodity. In this section, you lot are going to modify this projection to accept advantage of the Dependency Injection and the built-in IoC Container.
Defining the abstractions
As the Dependency Inversion Principle suggests, modules should depend on abstractions. So, to ascertain those abstractions, you can rely on interfaces.
Add a subfolder to the OrderManagementWeb
folder and call it Interfaces
. In the Interfaces
folder, add the IOrderSender.cs
file with the following content:
// OrderManagementWeb/Interfaces/IOrderSender.cs using System.Threading.Tasks ; using OrderManagement.Models ; namespace OrderManagement.Interfaces { public interface IOrderSender { Task< cord > Send ( Order lodge) ; } }
This code defines the IOrderSender
interface with just one method, Send ( )
. Too, in the same folder, add together the IOrderManager.cs
file with the following interface definition:
// OrderManagementWeb/Interfaces/IOrderManager.cs using Organisation.Threading.Tasks ; using OrderManagement.Models ; namespace OrderManagement.Interfaces { public interface IOrderManager { public Chore< string > Transmit ( Order order) ; } }
The above lawmaking defines the IOrderManager
interface with the Transmit ( )
method.
Depending on the abstractions
Once defined the abstractions, y'all take to make your classes depending on them instead of the concrete class instances. As a first footstep, you lot demand to redefine the OrderSender
course so that it implements the IOrderSender
interface. Y'all also rename the class into HttpOrderSender
to indicate out that this implementation sends the guild via HTTP. So, open the OrderSender.cs
file in the Managers
binder and replace its content with the following:
// OrderManagementWeb/Managers/OrderSender.cs using System.Net.Http ; using System.Text ; using System.Text.Json ; using System.Threading.Tasks ; using OrderManagement.Interfaces ; using OrderManagement.Models ; namespace OrderManagement { public class HttpOrderSender : IOrderSender { individual static readonly HttpClient httpClient = new HttpClient ( ) ; public async Task< string > Send ( Order guild) { var jsonOrder = JsonSerializer. Serialize <Order> (order) ; var stringContent = new StringContent (jsonOrder, UnicodeEncoding.UTF8, "awarding/json" ) ; //This argument calls a non existing URL. This is but an example... var response = wait httpClient. PostAsync ( "https://mymicroservice.lan/myendpoint" , stringContent) ; return response.Content. ReadAsStringAsync ( ) .Result; } } }
Now, yous need to redefine also the OrderManager
grade and so that it implements the IOrderManager
interface. Open the OrderManager.cs
file in the Managers
binder and supervene upon its content with the post-obit:
// OrderManagementWeb/Managers/OrderManager.cs using Organisation.Threading.Tasks ; using OrderManagement.Interfaces ; using OrderManagement.Models ; namespace OrderManagement { public form OrderManager : IOrderManager { private IOrderSender orderSender; public OrderManager ( IOrderSender sender) { orderSender = sender; } public async Task< string > Transmit ( Order order) { return await orderSender. Send (order) ; } } }
You may discover that, differently from the previous version of the form, the Transmit ( )
method no longer creates an example of the OrderSender
grade. The dependency is now accessed via the course constructor.
With these changes, y'all broke the dependency between the OrderManager
and the HttpOrderSender
classes. Now, the OrderManager
depends on the IOrderSender
interface, i.eastward., its abstraction.
Registering the dependencies
At present you need to tell the IoC Container how to manage the dependencies based on the brainchild you accept divers. In other words, you need to register the dependencies. Equally said, this registration happens in the Startup
class of the project. So, open up the Startup.cs
file and supersede the ConfigureServices ( )
method with the following:
// OrderManagementWeb/Startup.cs public class Startup { // ...code ... public void ConfigureServices ( IServiceCollection services) { services. AddControllers ( ) ; services. AddScoped <Interfaces.IOrderSender, HttpOrderSender> ( ) ; services. AddScoped <Interfaces.IOrderManager, OrderManager> ( ) ; } // ...code ... }
As you lot can run across, you lot registered the dependencies past using the generic version of the AddScoped ( )
method. Here you are request the IoC Container to create an instance of the HttpOrderSender
class whenever a request for the IOrderSender
type is detected. Similarly, information technology should create an instance of the OrderManager
class when theIOrderManager
type is requested.
Note that fifty-fifty the
AddController ( )
method is an extension method provided pastMicrosoft.Extensions.DependencyInjection
library that registers services for the Web API controllers.
Injecting the dependencies
Now it's time to actually utilize all these dependencies. And then, open up the OrderController.cs
file in the Controllers
folder and replace its content with the following code:
// OrderManagementWeb/Controllers/OrderController.cs using Microsoft.AspNetCore.Mvc ; using OrderManagement.Interfaces ; using OrderManagement.Models ; namespace OrderManagement.Controllers { [ Route ( "api/[controller]" ) ] [ ApiController ] public course OrderController : ControllerBase { private IOrderManager orderManager; public OrderController ( IOrderManager orderMngr) { orderManager = orderMngr; } [ HttpPost ] public ActionResult< string > Mail service ( Social club order) { render Ok (orderManager. Transmit (order) ) ; } } }
The new version of the Spider web API controller defines a private orderManager
variable. Its value represents the instance of a service implementing the IOrderManager
interface. The value of this variable is assigned in the constructor, which has an IOrderManager
type parameter. With this parameter, the constructor is requesting the IoC Container a service instance of that type. The IoC Container searches in its service collection a registration that can satisfy this request and passes such an example to the controller. But that'southward not all. The IoC Controller likewise looks for other indirect dependencies and resolve them. This means that while creating the instance of the OrderManager
class, the IoC Container also resolves its internal dependency on the IOrderSender
abstraction. So you don't have to worry about indirect dependencies: they are all resolved under the hood simply because the Web API controller declared to demand a registered service.
As you noticed in these examples, the Constructor Injection is the default approach used by the IoC Container to inject dependencies in a class. If y'all want to utilize the Method Injection arroyo, you should request it explicitly by using the FromServices
attribute. This example shows how you could apply this aspect:
[ HttpPost ] public ActionResult< string > Post ( [ FromServices ] IOrderManager orderManager) { render Ok (orderManager. Transmit (order) ) ; }
The tertiary injection arroyo, the Property Injection, is not supported by the born IoC Container.
"Notice how to leverage the congenital-in IoC Container in your .NET Core panel application."
Tweet This
Dependency Injection in a Console Application
In the previous section, you implemented Dependency Injection for an ASP.Internet Web application. Y'all followed a few steps to register and get your dependencies automatically resolved by the built-in IoC Container. However, you may still feel something magic in the way it happens since the ASP.NET Core infrastructure provides you with some implicit behaviors. Withal, the Dependency Injection mechanism provided by the IoC Container is not only for Spider web applications. You can use it in any blazon of awarding.
In this section, you lot will learn how to leverage the IoC infrastructure in a console awarding, and some behaviors that may even so await obscure will be hopefully clarified. Then, movement in the dependency-injection-dotnet-core
folder yous created when cloned the ASP.NET application and run the following control in a terminal window:
dotnet new panel -o OrderManagementConsole
This command creates an OrderManagementConsole
folder with a basic console application project. Now, copy in the OrderManagementConsole
binder the Interfaces
, Managers
, and Models
folders from the ASP.NET project you modified in the previous section. Basically, y'all are reusing the aforementioned classes related to the society management procedure you defined in the ASP.Cyberspace project.
Later this preparation, the first step to enable your panel awarding to have advantage of the IoC Container is to add together the Microsoft.Extensions.DependencyInjection bundle to your projection with the following command:
dotnet add package Microsoft.Extensions.DependencyInjection
Then, you accept to write some code to set up the IoC Container in your console awarding. Open the Program.cs
file and supplant its content with the following:
// OrderManagementConsole/Plan.cs using Arrangement ; using Microsoft.Extensions.DependencyInjection ; using OrderManagement ; using OrderManagement.Interfaces ; using OrderManagement.Models ; namespace OrderManagementConsole { class Programme { private static IServiceProvider serviceProvider; static void Primary ( string [ ] args) { ConfigureServices ( ) ; var orderManager = serviceProvider. GetService <IOrderManager> ( ) ; var order = CreateOrder ( ) ; orderManager. Transmit (lodge) ; } individual static void ConfigureServices ( ) { var services = new ServiceCollection ( ) ; services. AddTransient <IOrderSender, HttpOrderSender> ( ) ; services. AddTransient <IOrderManager, OrderManager> ( ) ; serviceProvider = services. BuildServiceProvider ( ) ; } private static Order CreateOrder ( ) { return new Order { CustomerId = "12345" , Engagement = new DateTime ( ) , TotalAmount = 145 , Items = new System.Collections.Generic.List<OrderItem> { new OrderItem { ItemId = "99999" , Quantity = 1 , Cost = 145 } } } ; } } }
You run across that the first operation in the Main ( )
method of the Program
course is configuring the services. Taking a look at the ConfigureServices ( )
method, you see that this time y'all have to create the services
drove explicitly. And then, you annals your services with the transient lifetime: the dependencies should alive just the time needed to manage the society. Finally, you get the service provider by using the BuildServiceProvider ( )
method of the services
drove and assign it to the serviceProvider
private variable.
You lot use this service provider to get an example of a registered service. For example, in the Main ( )
method, yous get an instance of the IOrderManager
service blazon by using the GetService<IOrderManager> ( )
method. From now on, you tin can use this instance to manage an order. Of grade, equally in the ASP.Net case, the orderManager
instance has any indirect dependency resolved.
Finally, the CreateOrder ( )
method just creates a mock order to exist passed to the Transmit ( )
method of the order manager.
Injecting Third-Political party Library Services
Using an injectable service provided by a tertiary-party library is pretty similar to using a Framework service. In fact, the library's developer should have defined all the needed abstractions to make their classes injectable. Then, from your perspective, the service should be ready to be used in your awarding, simply like any Framework service.
Consider a practical case that uses the Auth0 Management API client provided by the Auth0.Cyberspace SDK. The Auth0 Management API allows you to perform the administrative tasks you tin can interactively exercise on your tenant through the Auth0 Dashboard. For example, you can programmatically manage clients, users, roles, etc.
To use the Auth0 Direction API, you need to sign upwardly for a free Auth0 business relationship and get an access token. Meet Access Token for the Direction API to have more details.
To add together the Management API SDK to your project, blazon the post-obit command in a terminal window:
dotnet add package Auth0.ManagementApi
The ManagementApiClient
course provided by the SDK allows you to hit the Management API endpoints. The course is thread-prophylactic and each instance creates an HttpClientManagementConnection
case that, in plough, uses an HttpClient
object. To optimize HTTP connection use, you should limit the number of times you create and destroy HttpClient
instances. As a best practise, you should share one single case of HttpClient
every bit much as possible. You can achieve this goal by leveraging the Dependency Injection.
In the Startup
class of your application, register the HttpClientManagementConnection
service equally follows:
// ... usings ... using Auth0.ManagementApi ; public class Startup { // ... code ... public void ConfigureServices ( IServiceCollection services) { // ... code ... services. AddSingleton <IManagementConnection, HttpClientManagementConnection> ( ) ; } // ... code ... }
You registered the HttpClientManagementConnection
class as the service that implements the IManagementConnection
interface. Yous registered information technology as a singleton by using the AddSingleton ( )
method. This ensures you have only 1 instance for the whole application lifetime.
Recollect: using the
AddSingleton ( )
method is similar to registering a service withAdd together ( )
and a service example equally an argument. Nevertheless, theAddSingleton ( )
method grants you that the instance creation of the service will exist delayed until the starting time time information technology volition be requested in the application.
When yous need an instance of the ManagementApiClient
class, you lot don't as well need to create a new instance of the HttpClientManagementConnection
form. You can employ the case provided by the IoC Container.
The following example shows how you tin can get this in a Spider web API controller:
[ HttpGet ] public async Task<IActionResult> Get (FromServices] IManagementConnection managementConnection) { // ... code ... var managementApiClient = new ManagementApiClient ( "YOUR_MANAGEMENT_TOKEN" , "YOUR_AUTH0_DOMAIN" , managementConnection) ; // ... lawmaking ... }
You require the dependency in the Go ( )
method via the FromServices
aspect and pass information technology to the ManagementApiClient ( )
constructor along with the access token and domain.
Summary
This article tried to analyze some concepts related to the Dependency Injection design pattern and focused on the infrastructure provided by .Net Cadre to support it. After introducing a few general definitions, yous learned how to configure an ASP.NET Core awarding to use the built-in IoC container and register framework services. Then, you adapted the classes of an ASP.Net application to utilise the Dependency Inversion Principle and discovered how to register your dependency in the IoC Container specifying the desired lifetime. Finally, you saw how to use the IoC Container even in a console awarding.
You tin observe the full source code discussed in this article in this GitHub repository.
Should Containers Be Initialized In Service Classes C#,
Source: https://auth0.com/blog/dependency-injection-in-dotnet-core/
Posted by: gardinanday1996.blogspot.com
0 Response to "Should Containers Be Initialized In Service Classes C#"
Post a Comment