📜 ⬆️ ⬇️

ASP.NET MVC: Conversion or injection? That is the question

Classes should have dependencies only on abstract, not concrete, classes. To solve this problem, you can choose between 2 practices Service Locator and Dependency Injection. ASP.NET MVC uses “Dependency Resolvers", which are Service Locators. When developing ASP.NET MVC applications, you yourself have to decide what to use (resolve) or injects, so what are the pros and cons?

I understand that Dependency Injection has a very great value in solving the dependency problem, but I do not believe that it is the only true one. Dependency Injection is only a detail of the implementation that really matters the principle underlying it - the principle of inversion (Dependency Inversion) dependencies. If you are familiar with the SOLID abbreviation, the principle of dependency inversion is simply “D” in the abbreviation. By the way, solid results from the initials are five design principles that are considered necessary in object-oriented software development. These five principles are:

Almost all of these principles are only vectors in the sense that they show you the direction, but do not give specific instructions on how to solve your tasks. Let's take the principle of the sole duty as an example. All of this suggests that you should try to write classes so that you only have one reason to change later. The idea of ​​the principle is that classes should be much more cohesive than they often are not. The methods they contain should be logically connected and form a single chain of responsibility. As you can see, the principle portends a clear and general idea, but it does not give you instructions on how to achieve this.

The interiors of the principle of dependency inversion

The principle of dependency inversion, on the contrary, has a rather unclear formulation, but can be translated into a detailed set of implementation steps. The principle of dependency inversion states that classes should not have dependencies on specific classes, but only in abstraction. In a more understandable language, this means that you must use interfaces to abstract all critical dependencies within a class. If, for example, your class uses a logging component, then the best thing you can do is to declare the ILogger interface, and not the Logger class. Thus, you can change the implementation of the logging class at any time (and how many times you want) without breaking your main code.
The meaning of the algorithm for implementing the principle of dependency inversion is that you transfer the list of dependencies to the required place in the code. Let's look at this with an example:
public class MyComponent { public MyComponent() { : } public void DoSomeWork() { var logger = new Logger(); : } } 

Obviously, the MyComponent class has a dependency on the Logger class. If we decide to change it to ILogger, how can we get a link to the actual class that implements the interface?
 public class MyComponent { public MyComponent() { : } public void DoSomeWork() { ILogger logger = ...; // who's going to provide this? : } } 

To implement the principle of dependency inversion, you have a choice of two main models: Service Locator and Dependency Injection. The first method solves the problem inside the class, the second allows you to remove the dependency from the class. The following listing illustrates the Service Locator method:
 public class MyComponent { public MyComponent() { : } public void DoSomeWork() { ILogger logger = ServiceLocator.GetService(); : } } 

You have a dependency resolution component that the type usually takes (usually an interface) and returns an instance of a particular type that implements this interface. The type negotiation that is transmitted and the specific instance of the type are hidden in the implementation of the locator component. This model is known as the Service Locator pattern.
Here is another approach:
 public class MyComponent { private ILogger _logger; public MyComponent(ILogger logger) { _logger = logger; } public void DoSomeWork() { // Use the logger component here _logger.Log(); : } } 

In this case, the MyComponent class gets an ILogger component for use with the outside world. Your environment will take care of initializing the registrar before passing it to MyComponent. This is the essence of the dependency injection model.
What is the difference (if any) between dependency injection (Dependency Injection) and Service Locator? Both models are good at implementing the principle of dependency inversion. The Service Locator model is easier to use in existing code, it makes the overall design weaker, without forcing changes to the public interface. For the same reason, the code based on the Service Locator model is worse to read than the equivalent code based on Dependency Injection.
In Dependency Injection, you can clearly see what type will be before and after the injection into the class (or method). For this reason, as a result, the code becomes cleaner and more readable. What about ASP.NET MVC?

Dependency Injection in ASP.NET MVC

ASP.NET MVC is designed for multiple extension points, but in general it does not provide comprehensive support for dependency injection. Service Locator is perhaps the most effective way to make the existing system more loosely connected with the addition of new expansion points, since this is the least intrusive solution. The Service Locator acts as a black box, which you install at a certain point and give it rules, what contracts are required, and how to get them. ASP.NET MVC has a number of extension points, which are components of the system, but which can also be replaced by user ones. The table shows the known extension points on ASP.NET MVC 3.
')
ProviderDescription
Action Invoker// In the controller constructor class controller.ActionInvoker = new YourActionInvoker ();
Controller factory// In global.asax, Application_Start
var factory = new YourControllerFactory (); ControllerBuilder.Current.SetControllerFactory (factory);
Dictionary values// In global.asax,
Application_Start var providerFactory = new YourValueProviderFactory (); ValueProviderFactories.Factories.Add (providerFactory);
Model binder// In global.asax, Application_Start ModelBinders.Binders.Add (typeof (YourType), new YourTypeBinder ());
Model binder provider// In global.asax, Application_Start var provider = new YourModelBinderProvider (); ModelBinderProviders.BinderProviders.Add (provider);
Model metadata// In global.asax, Application_Start ModelMetadataProviders.Current = new YourModelMetadataProvider ();
Model validator// In global.asax, Application_Start var validator = new YourModelValidatorProvider (); ModelValidatorProviders.Providers.Add (validator);
Tempdata// In the controller class constructor
controller.TempDataProvider = new YourTempDataProvider ();
View engine// In global.asax, Application_Start ViewEngines.Engines.Clear (); ViewEngines.Engines.Add (new YourViewEngine ());


View engine // In global.asax, Application_Start ViewEngines.Engines.Clear (); ViewEngines.Engines.Add (new YourViewEngine ());
Before ASP.NET MVC 3, there was no standard way to register custom components. Each of the components listed in Table 1 requires its own API for integration into custom applications. Starting from version 3, ASP.NET MVC introduces new (additional) models based on arbitration dependencies. To replace the system components, you can follow the path described in Table 1 or register an injection (Dependency Injection) for this type. The ASP.NET runtime will detect dependencies and reference it when necessary.
Dependency resolution is just a service locator integrated with ASP.NET MVC code. Arbitrators are one way to add the implementation of the principle of dependency inversion in existing (large) code. For the size and complexity of the code, using dependencies is less appropriate, as this would require changes at various levels in the public API. This is just not an option for frameworks such as ASP.NET MVC. Let's find out more detailed information about the implementation of dependency arbitrators in ASP.NET MVC.

Determining Your Dependency Resolver

In ASP.NET MVC, Dependency Resolver is an object that implements the following interface
 { Object GetService(Type serviceType); IEnumerable<Object> GetServices(Type serviceType); } 

The logic you write to the converter is entirely up to you. It can be as simple as logic, which checks the type description and returns a newly created instance of a particular type. This can be done by a more complex implementation of creating instances, reading information from a configuration file, or using reflection. Finally, it can be based on unity or any other IoC framework. Here is a very simple, but functional, conversion example:
 public class SampleDependencyResolver : IDependencyResolver { public object GetService(Type serviceType) { if (serviceType == typeof(ISomeClass)) return new SomeClass(); : } public IEnumerable<object> GetServices(Type serviceType) { return Enumerable.Empty<Object>(); } } 

The following code shows how the converter uses Unity (and its section in the file configuration) to resolve dependencies.
 public class UnityDependencyResolver : IDependencyResolver { private readonly IUnityContainer _container; public UnityDependencyResolver() : this(new UnityContainer().LoadConfiguration()) { } public UnityDependencyResolver(IUnityContainer container) { _container = container; } public Object GetService(Type serviceType) { return _container.Resolve(serviceType); } public IEnumerable<Object> GetServices(Type serviceType) { return _container.ResolveAll(serviceType); } } 

You register your permissions with an ASP.NET MVC structure using the DependencyResolver class and the SetResolver method.
 protected void Application_Start() { // Prepare and configure the IoC container var container = new UnityContainer(); : // Create and register the resolver var resolver = new UnityDependencyResolver(container); DependencyResolver.SetResolver(resolver); } 

If you are using IoC framework from permission, then you need to find the best way to provide it with a list of registered types. If you prefer to pass this information through free code, then you need to fully configure the IoC container before creating names. If you want to configure IoC using the web.config file, according to Unity rules, then you can use the default constructor for resolving which is required to load configuration data. Note, however, that you may need to change this code if you are targeting a different IoC framework.
In general, resolution dependency is an internal tool that developers can additionally use to deploy their own individual components, rather than system components. The beauty of dependency converter is limited to using ASP.NET MVC. Transformers are called in known places to achieve clear targets. In other words, if ASP.NET MVC does not cause a conversion, before creating, say, a cache controller, then we unfortunately can do so much to replace the embedded cache with our own.

Using Transformations in Applications

It turned out that dependency conversion is nothing more than a name that ASP.NET MVC uses to discover a service. But how can you still use it in real applications? The answer is very simple: you should have only one transducer in which you have to make all your clodification, as you like. Here is just one more variant of dependency conversion, an example of which was slightly higher.
 public class SampleDependencyResolver : IDependencyResolver { public object GetService(Type serviceType) { try { return serviceType == typeof(ModelMetadataProvider) ? new ExtendedAnnotationsMetadataProvider() : Activator.CreateInstance(serviceType); } catch { return null; } } public IEnumerable<object> GetServices(Type serviceType) { return Enumerable.Empty<object>(); } } 

The GetService method gets the type, and checks it with a list of known types. For some known interface types, it can simply return a manually created instance of a known type. For other types, it can return anything, this means that the arbitrator is not able to work with this type.
The dependency converter in ASP.NET MVC cannot convert the same type in any way during the life cycle of an application. While this may be a significant limitation in implementing a shared service locator, it is not a big problem in the specific context of ASP.NET MVC. dependency conversion is an intrinsic feature of ASP.NET MVC, and only ASP.NET MVC decides when and how to call and use registered converters. In the end, your converter will be called only in a limited number of cases, and the interface will become more than understandable.

So what to use transformations or injections?

If you look at this question from the point of view of design efficiency, then Dependency Injection is preferred because it leads the design to a clean and crystal clear distribution of responsibilities. To use Dependency Injection, you may have to dare to change your API interface. This may not be acceptable depending on the context, it would not be acceptable, for example, when moving from ASP.NET MVC 2 to ASP.NET MVC 3. For this reason, Microsoft opted to use dependency converters, a fancy name for classic Service Locator. A more realistic example, a locator service is the only option when you need to refactor a large existing application.

ps This is my first translation thanks for your understanding.

Source: https://habr.com/ru/post/142800/


All Articles