神刀安全网

Dependency Injection – SOLID Principles

Finally, we are at the end of a long journey on SOLID principles. As a quick review, you can find links to the previous articles at http://www.dotnetcurry.com/software-gardening/967/software-is-not-building . In this episode, we tackle Dependency Inversion or as it is commonly called, Dependency Injection (DI).

As is always required when talking about SOLID, here’s how Uncle Bob defines it in his book “Agile Principles, Patterns, and Practice in C#”:

“A. High-level modules should not depend on low-level modules. Both should depend on abstractions.
B. Abstractions should not depend upon details. Details should not depend upon abstractions.”

In other words, don’t have a lot of dependencies in your code. Dependencies make code more fragile and harder to test and maintain. The idea is to create an instance of the dependency, then inject that instance into the module.

This article is published from the DNC Magazine for Developers and Architects . Download this magazine from here [Zip PDF] or Subscribe to this magazine for FREE and download all previous and current editions

If you use the new keyword to create an instance of a class, you may have a good candidate for dependency injection.

The most common way, and generally the most recommended way to implement DI, is with “constructor injection”. Let’s look at a simple explanation of this using what’s called “poor man’s dependency injection”. This code shows a common scenario of new-ing up things that you need, in this case it’s a logger.

class Program {     static void Main(string[] args)     {         var processor = new GizmoProcessor();     } }  public class GizmoProcessor {     public void Process()     {         // do something         var logger = new TextLogger();         logger.WriteLogMessage("Something happened");     } }  public class TextLogger {     public void WriteLogMessage(string Message)     { } }

Constructor Injection

Here’s the modified code using Constructor Injection.

class Program {     static void Main(string[] args)     {         var processor = new GizmoProcessor(new TextLogger());     } }  public class GizmoProcessor {     private readonly ILogger _logger;     public GizmoProcessor(ILogger logger)     {             _logger = logger;         }      public void Process()     {         // do something         _logger.WriteLogMessage("Something happened");     } }  public class TextLogger : ILogger {     public void WriteLogMessage(string Message)     { } }  public interface ILogger {     void WriteLogMessage(string Message); }

The difference is TextLogger is instantiated in the calling program, then passed in as a parameter. This makes it easy to change out the ILogger implementation and unit test GizmoProcessor because the dependency is removed. It’s called poor-man’s because there is no DI framework nor Inversion of Control (IoC) container to handle the injection for you automatically.

Let’s look at a more real-world example of Dependency Injection. I will build slowly from an out-of-the-box ASP.NET MVC 5 solution to using DI and an IoC container to improve on the code. An IoC container is a framework that handles the dependency injection details for you. I will not fully build out this application, but only look at a small part of it.

The example starts with CustomersController. I include the entire class here so you can compare it to changes we’ll make to it later.

public class CustomersController : Controller {     private InjectionContext db = new InjectionContext();      public ActionResult Index()     {         return View(db.Customers.ToList());     }      public ActionResult Details(int? id)     {         if (id == null)         {             return new HttpStatusCodeResult(HttpStatusCode.BadRequest);         }         Customer customer = db.Customers.Find(id);         if (customer == null)         {             return HttpNotFound();         }         return View(customer);     }      public ActionResult Create()     {         return View();     }      [HttpPost]     [ValidateAntiForgeryToken]     public ActionResult Create([Bind(Include = "Id,FirstName,LastName,Street1,Street2,City,State,PostalCode,Country,Phone")] Customer customer)     {         if (ModelState.IsValid)         {             db.Customers.Add(customer);             db.SaveChanges();             return RedirectToAction("Index");         }          return View(customer);     }      public ActionResult Edit(int? id)     {         if (id == null)         {             return new HttpStatusCodeResult(HttpStatusCode.BadRequest);         }         Customer customer = db.Customers.Find(id);         if (customer == null)         {             return HttpNotFound();         }         return View(customer);     }      [HttpPost]     [ValidateAntiForgeryToken]     public ActionResult Edit([Bind(Include = "Id,FirstName,LastName,Street1,Street2,City,State,PostalCode,Country,Phone")] Customer customer)     {         if (ModelState.IsValid)         {             db.Entry(customer).State = EntityState.Modified;             db.SaveChanges();             return RedirectToAction("Index");         }         return View(customer);     }      public ActionResult Delete(int? id)     {         if (id == null)         {             return new HttpStatusCodeResult(HttpStatusCode.BadRequest);         }         Customer customer = db.Customers.Find(id);         if (customer == null)         {             return HttpNotFound();         }         return View(customer);     }      [HttpPost, ActionName("Delete")]     [ValidateAntiForgeryToken]     public ActionResult DeleteConfirmed(int id)     {         Customer customer = db.Customers.Find(id);         db.Customers.Remove(customer);         db.SaveChanges();         return RedirectToAction("Index");     }      protected override void Dispose(bool disposing)     {         if (disposing)         {             db.Dispose();         }         base.Dispose(disposing);     } }

Let’s learn what we can from this bit of code. First, we see the data context gets instantiated. This binds the context to the controller in a strong way. The context is a dependency and what if it changes? The controller knows a lot about the context. In fact, too much. Further, how will you unit test this? You can’t easily remove this hard dependency on the database.

The next thing is related and goes to the very definition of MVC: Model, View, Controller and how it is related to the standard three-tier application with UI, Business, and Data layers. The View is not the UI. It is the information that is displayed. The Model is not the database. It is the representation of the data needed by the View. The Controller is the thing that hooks up the View and the Model and should be a very thin layer, meaning it does very little. Taking this an additional step, the Data layer is not the database. It handles the data access, creating SQL statements, etc. In a standard MVC application, this is where Entity Framework sits. So, instead of three layers, we have many.

Dependency Injection - SOLID Principles

public class CustomerService : ICustomerService {     private InjectionContext db = new InjectionContext();      public IList<Customer> GetModelForIndex()     {         return db.Customers.ToList();     }      public Customer GetModelForDetails(int? id)     {         return db.Customers.Find(id);     }      public Customer GetModelForEdit(int? id)     {         return db.Customers.Find(id);     }      public Customer GetModelForDelete(int? id)     {         return db.Customers.Find(id);     }      public void Delete(int id)     {         Customer customer = db.Customers.Find(id);         db.Customers.Remove(customer);         db.SaveChanges();     }      public bool CanSaveCustomer(Customer model)     {         if (IsValid(model))         {             db.Customers.Add(model);             db.SaveChanges();             return true;         }         return false;     }      private bool IsValid(Customer model)     {         return true;     } }

Here’s the complete Controller we now have.

public class CustomersController : Controller {     private ICustomerService service = new CustomerService();      public ActionResult Index()     {         return View(service.GetModelForIndex());     }      public ActionResult Details(int? id)     {         if (id == null)         {             return new HttpStatusCodeResult(HttpStatusCode.BadRequest);         }         var model = service.GetModelForDetails(id);         if (model == null)         {             return HttpNotFound();         }         return View(model);     }      public ActionResult Create()     {         return View();     }      [HttpPost]     [ValidateAntiForgeryToken]     public ActionResult Create([Bind(Include = "Id,FirstName,LastName,Street1,Street2,City,State,PostalCode,Country,Phone")] Customer model)     {         if (service.CanSaveCustomer(model))         {             return RedirectToAction("Index");         }         return View(model);     }      public ActionResult Edit(int? id)     {         if (id == null)         {             return new HttpStatusCodeResult(HttpStatusCode.BadRequest);         }         var model = service.GetModelForEdit(id);         if (model == null)         {             return HttpNotFound();         }         return View(model);     }      [HttpPost]     [ValidateAntiForgeryToken]     public ActionResult Edit([Bind(Include = "Id,FirstName,LastName,Street1,Street2,City,State,PostalCode,Country,Phone")] Customer model)     {         if (service.CanSaveCustomer(model))         {             return RedirectToAction("Index");         }         return View(model);     }      public ActionResult Delete(int? id)     {         if (id == null)         {             return new HttpStatusCodeResult(HttpStatusCode.BadRequest);         }         var model = service.GetModelForDelete(id);         if (model == null)         {             return HttpNotFound();         }         return View(model);     }      [HttpPost, ActionName("Delete")]     [ValidateAntiForgeryToken]     public ActionResult DeleteConfirmed(int id)     {         service.Delete(id);         return RedirectToAction("Index");     } }

If you’re now saying, “That’s great, but you’ve swapped out the dependency on InjectionContext with a dependency on CustomerService ”, you’re correct. As with any refactoring, do this in small steps. The next step is to hook up dependency injection so that whenever this controller gets instantiated, an instance of the CustomerService class will be injected into the controller. The key to this is the use of the ICustomerService interface.

The first thing to add is a constructor to the controller and a field to hold the CustomerService instance.

private readonly ICustomerService service;  public CustomersController(ICustomerService svc) {     service = svc; }

Using a Dependency Injection Library – Ninject

Now, add a Dependency Injection library. I’m going to use Ninject and the Ninject.MVC5 extension. Install the Ninject.MVC5 package from NuGet. It will install everything you need. You can learn about Ninject at http://www.ninject.org/ and the MVC5 extension at https://github.com/ninject/ninject.web.mvc/wiki .

After installing the Ninject assemblies, you need to tell Ninject how to resolve the dependency. In the App_Start folder for the solution, you’ll find a new file named NinjectWebCommon.cs. Open it and navigate to the RegisterServices method. Add the CustomerService .

private static void RegisterServices(IKernel kernel) {     kernel.Bind<ICustomerService>().To<CustomerService>(); }

You can now run your application and Ninject will automatically inject an instance of the CustomerService class. When you unit test, you’ll create a mock instance of ICustomerService and pass it in. It will look something like this:

ICustomerService mockCustSvc = new MockCustomerService(); var CustomersController = new CustomersController(mockCustSvc);

The next step you should take is to remove the dependency on InjectionContext that exists in CustomerService . The best way to do this is using the RepositoryPattern then inject an IRepository implementation. I leave that for you to figure out.

So, I’ve shown you how to get started with dependency injection. A word of warning: it can get very, very complicated and can make it harder to debug because you don’t always know what’s happening due to the class that gets injected, can change. I know several top-notch, well-known .NET developers who don’t like IoC containers. Like any solution to a problem, your mileage may vary.

However, using dependency injection does solve the issue of tightly coupling. It also makes your code more testable and easier to change out implementations. Now that you’ve got a good basic understanding of SOLID, I highly recommend the book “Adaptive Code via C#” by Gary McLean Hall and published by Microsoft Press.

And as always, by using Dependency Injection and SOLID, you’ll help ensure that your software is green, lush, and growing.

About Software Gardening

Comparing software development to constructing a building says that software is solid and difficult to change. Instead, we should compare software development to gardening as a garden changes all the time. Software Gardening embraces practices and tools that help you create the best possible garden for your software, allowing it to grow and change with less effort.

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Dependency Injection – SOLID Principles

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址