ASP.NET MVC开发:依赖注入

C#

浏览数:155

2019-4-3

AD:资源代下载服务

什么是依赖注入

依赖注入(Dependency injection,DI)是一种实现对象及其合作者或依赖项之间松散耦合的技术。将类用来执行其操作(Action)的这些对象以某种方式提供给该类,而不是直接实例化合作者或使用静态引用。通常,类会通过它们的构造函数声明其依赖关系,允许它们遵循 显示依赖原则 (Explicit Dependencies Principle) 。这种方法被称为 “构造函数注入(constructor injection)”。

当类的设计使用 DI 思想,它们耦合更加松散,因为它们没有对它们的合作者直接硬编码的依赖。这遵循 依赖倒置原则(Dependency Inversion Principle),其中指出 “高层模块不应该依赖于低层模块;两者都应该依赖于抽象。” 类要求在它们构造时向其提供抽象(通常是 interfaces ),而不是引用特定的实现。提取接口的依赖关系和提供这些接口的实现作为参数也是 策略设计模式(Strategy design pattern) 的一个示例。

当系统被设计使用 DI ,很多类通过它们的构造函数(或属性)请求其依赖关系,有一个类被用来创建这些类及其相关的依赖关系是很有帮助的。这些类被称为 容器(containers) ,或者更具体地,控制反转(Inversion of Control,IoC) 容器或者依赖注入(Dependency injection,DI)容器。容器本质上是一个工厂,负责提供向它请求的类型实例。如果一个给定类型声明它具有依赖关系,并且容器已经被配置为提供依赖类型,它将把创建依赖关系作为创建请求实例的一部分。通过这种方式,可以向类型提供复杂的依赖关系而不需要任何硬编码的类型构造。除了创建对象的依赖关系,容器通常还会管理应用程序中对象的生命周期。

ASP.NET Core 包含了一个默认支持构造函数注入的简单内置容器(由 IServiceProvider 接口表示),并且 ASP.NET 使某些服务可以通过 DI 获取。ASP.NET 的容器指的是它管理的类型为 services。在这篇文章里面,我们将主要讨论软件的设计模式,从而简单的了解一下ASP.NET MVC的依赖注入。

设计模式-控制反转模式

我们知道,当一个组件依赖于其它组件时,我们称其为耦合。一个类知道与其交互的类的大量信息,我们就称之为高耦合(或者紧耦合)。

我们新建一个类,取名为EmailService.cs 代码如下:

 public class EmailService
    {
        public void SendMessage()
        {
            ..//省略代码
            JavaScript.ConsoleLog("EMail发送测试");
        }
    }

再新建一个NotificationSystem.cs,代码如下:

public class NotificationSystem
    {
        private EmailService svc;
        public NotificationSystem()
        {
            svc = new EmailService();
        }
        public void Notificationsend()
        {
            svc.SendMessage();
        }
    }

这个例子中,NotificationSystem类依赖于EmailService类,这种耦合我们就视之为高耦合。

下一步,我们新建一个imessageingService.cs类,做为接口使用。

 public interface imessageingService
    {
        void SenMessage();
    }

修改一下EmailService类。

public class EmailService:imessageingService
    {
        public void SendMessage()
        {
            JavaScript.ConsoleLog("EMail发送测试");
        }
    }

最后修改NotificationSystem类。

 public class NotificationSystem
    {
        private imessageingService svc;
        public NotificationSystem()
        {
            svc = new EmailService();
        }
        public void Notificationsend()
        {
            svc.SendMessage();
        }
    }

运行测试成功,在本例中,笔者使用一个JavaScript的静态类进行测试,通过JavaScript.ConsoleLog将实现console.log的写入。(其实没有什么意义,单纯就看看类、接口之间是不是能够正确的运行)

using System.Web;

public static class Javascript{
    static string scriptTag = "<script type=\"\" language=\"\">{0}</script>";
    public static void ConsoleLog(string message)
    {     
        string function = "console.log('{0}');";
        string log = string.Format(GenerateCodeFromFunction(function), message);
        HttpContext.Current.Response.Write(log);
    }

    public static void Alert(string message)
    {
        string function = "alert('{0}');";
        string log = string.Format(GenerateCodeFromFunction(function), message);
        HttpContext.Current.Response.Write(log);
    }

    static string GenerateCodeFromFunction(string function)
    {
        return string.Format(scriptTag, function);
    }}

(静态类的使用可是相当的不DI的~~!)

下面我们将代码可视化一点,做一个产品类,我们来看看如何做到降低类和依赖类之间的耦合程度。

运行Visual Studio,创建一个名为“DI”项目,在选择模板窗口中,选择为Empty模板,在为以下项添加文件夹和核心引用中选择MVC。

然后在项目的Models文件夹里面添加一个新类:Product.cs,代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace IDTest.Models
{
    public class Product
    {
        public int ProductID { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public decimal Price { get; set; }
        public string Category { get; set; }
    }
}

我是想建一个简单版的购物车程序,那么首先我们需要建一个Price字段SUM汇总计算的类,和一个计算产品数量的类。这两个类我分别命名为:LinqCalcCount.cs和LinqCalcSum.cs,下面是代码:

 public class LinqCalcSum
    {
        public decimal ValueProducts(IEnumerable<Product> products)
        {
            return products.Sum(p => p.Price);
        }
    }

public class LinqCalcCount
    {
        public int ValueProducts(IEnumerable<Product> products)
        {
            int count = products.Count();
            return count;
        }
    }

下一步,我们来建立购物车的类,取名为ShoppingCart.cs:

public class ShoppingCare
    {
        private LinqCalcSum c_sum;
        private LingCalcCount c_count;
        public ShoppingCare(LinqCalcSum Csum)
        {
            c_sum = Csum;
        }
        public ShoppingCare(LinqCalcCount Ccount)
        {
            c_count = Ccount;
        }
        public IEnumerable<Product> products { get; set; }
        public decimal CalculateProductSum()//汇总计算
        {
            return c_sum.ValueProducts(products);
        }

        public int CalculateProductCount()//总数计算
        {
            return c_count.ValueProducts(products);
        }

接着,我们需要一个控制器,新建控制器取名为:HomeController

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using DI.Models;
namespace DI.Controllers
{
    public class HomeController : Controller
    {
        // GET: Home

        private Product[] products = {
                new Product {Name="蛋糕",Category="食品",Price=100M},
                new Product {Name="曲奇",Category="食品",Price=98M},
                new Product {Name="烤箱",Category="电器",Price=580M},
                new Product {Name="电吹风",Category="电器",Price=199M},
            };

        public ActionResult Index()
        {
           
            return View(products);
        }

        public ActionResult SUM()
        {
            LinqCalcSum ProductSum = new LinqCalcSum();
            ShoppingCare SC = new ShoppingCare(ProductSum)
            {
                products = products
            };
            decimal totalValue = SC.CalculateProductSum();
            return View(totalValue);
        }

        public ActionResult Count()
        {
            LinqCalcCount Productcount = new LinqCalcCount();
            ShoppingCare SC = new ShoppingCare(Productcount)
            {
                products = products
            };
            int totalINT = SC.CalculateProductCount();
            return View(totalINT);
        }
    }
}

最后一步,我们建一个视图,将所有的四笔数据显示出来,右击Index()新建视图:

@model IEnumerable<DI.Models.Product>
@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <table width="80%" border="0" align="center" cellpadding="0" cellspacing="0">
        <thead>
            <tr>
                <th>名称:</th>
                <th>类别:</th>
                <th>价格:</th>
            </tr>
        </thead>
        <tbody>

            
                @{
                    foreach (DI.Models.Product item in Model)
                    {
                        <tr>
                            <td>@item.Name</td>
                            <td>@item.Category</td>
                            <td> ¥:@item.Price</td>
                        </tr>
                    }
                }
           
        </tbody>
    </table>

</body>
</html>

我们再建两个视图,一个SUM,一个Count,其实都很简单

@model decimal
总数为:@Model
  
model int
一共有 @Model 笔数据。

最终输出结果是:

总数为:¥977,一共有4笔数据。

(如果单纯只是想将数据计算出来,不用这么复杂,直接在Index()视图里面加入LINQ函数即可,下面代码看一下)

<tr>
                <td colspan="2" style="text-align: right">一共有 @Model.Count() 笔数据
               </td>
                <td>总数为 ¥:@Model.Sum(p => p.Price)</td>
                
</tr>

(但这个例子不是单纯想实现这个功能,而是想通过这个实例去讨论控制反转模式)

从这个例程可以看出,无论是Shopping、CartLinqCalcCount.cs和LinqCalcSum.cs,还是控制器和ShoppingCart、LinqCalcCount.cs、LinqCalcSum.cs之间都是高耦合类,下面我们将运用接口,希望能解决部分问题。在Modles文件夹里面新建一个类,取名为ICalculatorSUM.cs和ICalculatorCount.cs,代码如下:

public interface ICalculatorSUM
    {
        decimal valueproducts(IEnumerable<Product> products);
        
    }

public interface ICalculatorCount
    {
        int valueproducts(IEnumerable<Product> products);
    }

然后在LinqCalcSum、LinqCalcCount类里面实现这个接口,修改这两个文件:

 public class LingCalcCount:ICalculatorCount
    {
        
        public int valueproducts(IEnumerable<Product> products)
        {
            int count = products.Count();
            return count;
        }
    }



 public class LinqCalcSum:ICalculatorSUM
    {
        public decimal valueproducts(IEnumerable<Product> products)
        {
            return products.Sum(p => p.Price);
        }
    }

下面将修改ShoppingCart类,用接口打断ShoppingCart与LinqValueCalculator之间的紧耦合关系:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace DI.Models
{
    public class ShoppingCare
    {
        private ICalculatorSUM c_sum;
        private ICalculatorCount c_count;
        public ShoppingCare(ICalculatorSUM Csum)
        {
            c_sum = Csum;
        }
        public ShoppingCare(ICalculatorCount Ccount)
        {
            c_count = Ccount;
        }
        public IEnumerable<Product> products { get; set; }
        public decimal CalculateProductSum()//汇总计算
        {
            return c_sum.valueproducts(products);
        }

        public int CalculateProductCount()
        {
            return c_count.valueproducts(products);
        }
    }
}

在控制器里面也修改其中SUM()和Count()的代码:

public ActionResult SUM()
        {
            ICalculatorSUM ProductSum = new LinqCalcSum();
            ShoppingCare SC = new ShoppingCare(ProductSum)
            {
                products = products
            };
            decimal totalValue = SC.CalculateProductSum();
            return View(totalValue);
        }

       public ActionResult Count()
        {
            ICalculatorCount Productcount = new LingCalcCount();
            ShoppingCare SC = new ShoppingCare(Productcount)
            {
                products = products
            };
            int totalINT = SC.CalculateProductCount();
            return View(totalINT);
        }

现在看起来已经解除了ShoppingCart与LinqValueCalculator之间的紧耦合。

上面实现的部分松耦合显然并不是我们所需要的。我们所需要的是,在一个类内部,不通过创建对象的实例而能够获得某个实现了公开接口的对象的引用。这种“需要”,就称为DI(依赖注入,Dependency Injection),和所谓的IoC(控制反转,Inversion of Control )是一个意思。

DI是一种通过接口实现松耦合的设计模式。初学者可能会好奇网上为什么有那么多技术文章对DI这个东西大兴其笔,是因为DI对于基于几乎所有框架下,要高效开发应用程序,它都是开发者必须要有的一个重要的理念,包括MVC开发。它是解耦的一个重要手段。

由于经常会在编程时使用到DI,所以出现了一些DI的辅助工具(或叫DI容器),如Unity和Ninject等。

本文参考书籍有《ASP.NET MVC 5高级编程(第5版)》、《精通ASP.NET MVC5》等。

谢谢您的支持。转帖的时候请把凉风有兴或者AlexZeng.net进行署名。本文版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证