📜 ⬆️ ⬇️

ASP.NET MVC Lesson 2. Dependency Injection

The purpose of the lesson : Study DI (Dependency Injection). Example on Ninject, Unity, Autofac and Winsor.

In many cases, the same class instance is used in your application in different modules. A simple way to implement is to use the Singleton pattern.

But consider this situation from the other side. Since this object is created when you first access it, we cannot control its lifetime. In unit-test, there is no need to use this object (or it may not be possible). To avoid this, we do not directly call the object, but through the interface. Both a real class instance and a stub instance for testing will implement this interface. And we assign the logic of creation to the DI-container.
')

For example, before using the service. We describe a pair of classes, an IWeapon interface with the Kill method, two Bazuka and Sword implementation classes, and a Warrior class that uses weapons:
public interface IWeapon { void Kill(); } public class Bazuka : IWeapon { public void Kill() { Console.WriteLine("BIG BADABUM!"); } } public class Sword : IWeapon { public void Kill() { Console.WriteLine("Chuk-chuck"); } } public class Warrior { readonly IWeapon Weapon; public Warrior(IWeapon weapon) { this.Weapon = weapon; } public void Kill() { Weapon.Kill(); } } 

We use this:
  class Program { static void Main(string[] args) { Warrior warrior = new Warrior(new Bazuka()); warrior.Kill(); Console.ReadLine(); } } 

We read between the lines. Create a warrior and give him a bazooka, he goes and kills. In the console we get:
 BIG BADABUM! 

Note that we do not have a check for null in the string
 Weapon.Kill(); 


What is wrong here? The warrior does not know whether he has a weapon, and the issue is not a separate module, but the main program.
The essence of DI is to entrust the issue of weapons to another module.

We connect Ninject:

 Install-Package Ninject 


Create a module that deals with the issuance of weapons:

  public class WeaponNinjectModule : NinjectModule { public override void Load() { this.Bind<IWeapon>().To<Sword>(); } } 


Which literally means: “if they ask for a weapon, then give out swords”.
We create a “service locator” and use weapons:
  class Program { public static IKernel AppKernel; static void Main(string[] args) { AppKernel = new StandardKernel(new WeaponNinjectModule()); var warrior = AppKernel.Get<Warrior>(); warrior.Kill(); Console.ReadLine(); } } 

As you can see, we create the warrior object not using the new construct, but via AppKernel.Get<>() . When creating AppKernel, we pass the module responsible for issuing weapons (in this case, the sword) as a constructor. Any object that we are trying to get via AppKernel.Get will (if possible) be initialized if there are modules that know how to do it.

Another point of application is when the Warrior object does not take a weapon with it every time, and if it is not detected, it calls on the service to the locator and gets it:

  public class OtherWarrior { private IWeapon _weapon; public IWeapon Weapon { get { if (_weapon == null) { _weapon = Program.AppKernel.Get<IWeapon>(); } return _weapon; } } public void Kill() { Weapon.Kill(); } } 

We execute:
  var otherWarrior = new OtherWarrior(); otherWarrior.Kill(); 

Our warrior gets weapons for direct deliveries - super!

Ninject has another very good detail. If the property ( public property ) is marked with [Inject] , then when creating a class via AppKernel.Get<>() , the field is initialized by the locator service:
  public class AnotherWarrior { [Inject] public IWeapon Weapon { get; set; } public void Kill() { Weapon.Kill(); } } var anotherWarrior = AppKernel.Get<AnotherWarrior>(); anotherWarrior.Kill(); 


Unity

Absolutely all the same:


Autofac

Also, in fact, everything happens:

Castle windsor


Small subtotal

In fact, the implementation of Dependency Injection is not much, but still different. Some support initialization in Web.config (App.config) files. Some set the rules for initialization, as we now look at the Ninject extension for asp.net mvc - this concerns the initialization of the service locator as a shared object generator, and separately for each stream or web request.

Area Objects (Ninject)

In Ninject, you can specify several ways to initialize getting an object from a class. If we work in different contexts (for example, in different threads (Thread)), then the objects should be used different. Thereby, the scalability and flexibility of the application is supported.
RegionBinding methodExplanation
Temporary.InTransientScope()The class object will be created upon each request (default method).
Single mother.InSingletonScope()The class object will be created once and will be reused.
Flow.InThreadScope()One object per stream.
Request.InRequestScope()One object will be for each web request


Lifetime Manager in Unity

In Unity, for the task of initialization rules, the implementation of the LifetimeManager abstract class is used.
It happens like this:
  _container.RegisterType<DbContext, SavecashTravelContext>(new PerRequestLifetimeManager()); 

Where PerRequestLifetimeManager is an implementation of LifetimeManager:
 public class PerRequestLifetimeManager : LifetimeManager { /// <summary> /// Key to store data /// </summary> private readonly string _key = String.Format("SingletonPerRequest{0}", Guid.NewGuid()); /// <summary> /// Retrieve a value from the backing store associated with this Lifetime policy. /// </summary> /// <returns> /// the object desired, or null if no such object is currently stored. /// </returns> public override object GetValue() { if (HttpContext.Current != null && HttpContext.Current.Items.Contains(_key)) return HttpContext.Current.Items[_key]; return null; } /// <summary> /// Stores the given value into backing store for retrieval later. /// </summary> /// <param name="newValue">The object being stored.</param> public override void SetValue(object newValue) { if (HttpContext.Current != null) HttpContext.Current.Items[_key] = newValue; } /// <summary> /// Remove the given object from backing store. /// </summary> public override void RemoveValue() { if (HttpContext.Current != null && HttpContext.Current.Items.Contains(_key)) HttpContext.Current.Items.Remove(_key); } } 

The bottom line. All objects are stored in HttpContext.Current.Items[_key] and are issued only if they are already in the same context ( HttpContext.Current ). Otherwise, a new object is created. If the current context ( HttpContext.Current ) in the code area does not exist (we use such LifetimeManager in the console application or in a separate thread), then this container will not work.

Using Ninject in asp.net mvc

Install Ninject on asp.net mvc. We separately create our own LessonProject project, create a HomeController with the method and view Index. (/Contollers/HomeController.cs):
 public class HomeController : Controller { public ActionResult Index() { return View(); } } 

And (/Views/Home/Index.cshtml):

 @{ ViewBag.Title = "LessonProject"; Layout = "~/Views/Shared/_Layout.cshtml"; } <h2>LessonProject</h2> 


We start - it works.

Note: In the future, we will transfer this project to future lessons.

Now we install the Ninject module and Ninject.MVC3 for this project.
 Install-Package Ninject.MVC3 

Add a class to the App_Start folder (/App_Start/NinjectWebCommon.cs):
 [assembly: WebActivator.PreApplicationStartMethod(typeof(LessonProject.App_Start.NinjectWebCommon), "Start")] [assembly: WebActivator.ApplicationShutdownMethodAttribute(typeof(LessonProject.App_Start.NinjectWebCommon), "Stop")] namespace LessonProject.App_Start { using System; using System.Web; using Microsoft.Web.Infrastructure.DynamicModuleHelper; using Ninject; using Ninject.Web.Common; public static class NinjectWebCommon { private static readonly Bootstrapper bootstrapper = new Bootstrapper(); /// <summary> /// Starts the application /// </summary> public static void Start() { DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule)); DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule)); bootstrapper.Initialize(CreateKernel); } /// <summary> /// Stops the application. /// </summary> public static void Stop() { bootstrapper.ShutDown(); } /// <summary> /// Creates the kernel that will manage your application. /// </summary> /// <returns>The created kernel.</returns> private static IKernel CreateKernel() { var kernel = new StandardKernel(); kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel); kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>(); RegisterServices(kernel); return kernel; } /// <summary> /// Load your modules or register your services here! /// </summary> /// <param name="kernel">The kernel.</param> private static void RegisterServices(IKernel kernel) { } } } 


In RegisterServices we add initialization of our services. To begin with, we add a playful IWeapon, and later we will return to this method to register other services:
  public interface IWeapon { string Kill(); } … public class Bazuka : IWeapon { public string Kill() { return "BIG BADABUM!"; } } … private static void RegisterServices(IKernel kernel) { kernel.Bind<IWeapon>().To<Bazuka>(); } 


In the controller use the attribute [Inject] :
  public class HomeController : Controller { [Inject] public IWeapon weapon { get; set; } public ActionResult Index() { return View(weapon); } } 


Change the View:
 @model LessonProject.Models.IWeapon @{ ViewBag.Title = "LessonProject"; Layout = "~/Views/Shared/_Layout.cshtml"; } <h2>LessonProject</h2> <p> @Model.Kill() </p> 

At the output we get:


Ninject uses WebActivator:


DependencyResolver


In asp.net mvc3, the DependencyResolver class appeared. This class provides an instance of the service. We can also get our registered services (and even the used DI container) through this class.
  public class HomeController : Controller { private IWeapon weapon { get; set; } public HomeController() { weapon = DependencyResolver.Current.GetService<IWeapon>(); } public ActionResult Index() { return View(weapon); } } 

Total

The use of DI containers in modern applications is necessary to get rid of strong code connectivity, and for easy access from any part of it to services. Also, it is necessary for writing Unit tests.

All sources are located at https://bitbucket.org/chernikov/lessons

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


All Articles