ASP.NET MVC开发:依赖注入

什么是依赖注入

依赖注入(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许可证

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容