ASP.NET Core MVC依赖注入理解(极简个人版)

依赖注入

文献来源:《Pro ASP.NET Core MVC》 Adam Freeman 第18章 依赖注入

1 依赖注入原理

  • 所有可能变化的地方都用接口
  • 在使用接口的地方用什么实体类通过在ConfigureService中注册解决
  • 注册的实体类需要指定在何种生命周期中有效
    • Transient
    • Scoped
    • Singleton

2 接口

接口只定义契约,不定义实现。

ASP.NET Core MVC依赖注入理解(极简个人版)_第1张图片

//IRepository.cs
using System.Collections.Generic;
namespace DependencyInjection.Models{

	public interface IRepository{
	
		IEnumerable<Product> Products{get;}
		
		Product this[string name]{get;}
		
		void AddProduct(Product product);
		
		void DeleteProduct(Product product);
	}
}

//MemoryRepository.cs
using System.Collections.Generic;

namespace DependencyInjection.Models{
    public class MemoryRepository:IRepository{
		private Dictionary<string, Product> products;
	
		public MemoryRepository(){
			products = new Dictionary<string, Product>();
			new List<Product{
				new Product{Name="Kayak", Price=275M},
				new Product{Name="Lifejacket", Price=48.95M},
				new Product{Name="Soccer ball", Price=19.50M},
			}.ForEach(p=>AddProduct(p));
		}
    
    	public IEnumerable<Product> Products => products.Values;
    	public Product this[string name] => products[name];
        
        public void AddProduct(Product product) => products[product.Name] = product;
        
        public void DeleteProduct(Product product) => products.Remove(product.Name);
    }
}

3 数据绑定页面

页面绑定代码使用@model和ViewBag实现。

//Index.cshtml
@using DependencyInjection.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@model IEnumerable<Product>
@{layout=null;}

DOCTYPE html>
<html>
    <head>
        <meta name="viewport" content="width=device-width" />
        <title>Dependency Injectiontitle>
        <link rel="stylesheet" asp-href-include="lib/bootstrap/dist/css/*.min.css" />
    head>
    <body class="panel-body">
        @if(ViewData.Count>0){
            <table class="table table-bordered table-condense table-striped">
                @foreach(var kvp in ViewData){
                    <tr>
                        <td>@kvp.Keytd>
                        <td>@kvp.Valuetd>
                    tr>
                }
            table>
        }
        <table class="table table-bordered table-condense table-striped">
            <thead>
                <tr>
                    <th>Nameth>
                    <th>Priceth>
                tr>
            thead>
            <tbody>
                @if(Model==null){
                    <tr><td colspan="3" class="text-center">No Model Datatd>tr>
                }
                else{
                    @foreach(var p in Model){
                        <tr>
                            <td>@p.Nametd>
                            <td>@string.Format("{0:C2}",p.Price)td>
                        tr>
                    }
                }
            tbody>
        table>
    body>
html>

该页面绑定了2组数据集

  • ViewData集合
  • @model IEnumerable

后台绑定

//HomeController.cs
//使用实体类的Products属性绑定页面
using Microsoft.AspNetCore.Mvc;
using DependencyInjection.Models;

public class HomeController:Controller{
    public ViewResult Index() => View(new MemoryRepository().Products);
}

4 ASP.NET MVC定义依赖注入

4.1 接口依赖注入

  • 单实例依赖注入

首先将后台处理由指定的实体类换成接口

//HomeController.cs
using Microsoft.AspNetCore.Mvc;
using DependencyInjection.Models;
using DependencyInjection.Infrastructure;

namespace DependencyInjection.Controllers{
    //增加接口私有变量
	private IRepository repository;
    
    //通过构造函数传参至该私有变量
	public HomeController(IRepository repo){
		repository = repo;
	}
	
    //绑定接口的Products属性
    //运行时由ASP.NET MVC框架确定使用何种实体类
	public ViewResult Index()=>View(repository.Products);
}

然后配置接口与实体类之间的关系

//Startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using DependencyInjection.Infrastructure;
using DependencyInjection.Model;

namespace DependencyInjection{
    public void ConfigureServices(IServicesCollection services){
        services.AddTransient<IRepository, MemoryRepository>();
        ...
    }
    ...
}

services.AddTransient告诉service provider如何处理依赖。这里要注意一点,service provider是整个MVC框架服务的总提供者。

  • 链式依赖

    某实体类依赖于某接口,且该实体类中的成员变量又依赖于另一接口。

ASP.NET Core MVC依赖注入理解(极简个人版)_第2张图片

//MemoryRepository.cs
using DependencyInjection.Models{
	public class MemoryRepository:IRepository{
		private IModelStorage storage;//新增私有变量用于指定所属的仓库
		public MemoryStorage(IModelStorage storage){
			storage = modelStorage;
			new List<Product>{
				new Product{Name="Kayak",Price=275M},
				new Product{Name="Lifejacket", Price=48.95M},
				new Product{Name="Soccer ball", Price=19.50M}
			}.ForEach(p=>AddProduct(p));
		}
		
		public IEnumerable<Product> Products=>storage.Items;
		
		public Product this[string name] => storage[name];
		
		public void AddProduct(Product product){
			storage[product.Name] = product;
		}
		
		public void DeleteProduct(Product product){
			storage.RemoveItem(product.Name); 
		}
	}
}

ConfigureServices中注册。

//Startup.cs
...
namespace DependencyInjection{
    public class Startup{
        public void ConfigureServices(IServiceCollection services){
            services.AddTransient<IRepository, MemoryRepository>();
            services.AddTransient<IModelStorage, DictionaryStorage>();
            ...
        }
        ...
    }
}

4.2 实体类依赖注入

假设存在实体类ProductTotalizer用于计算总价。

using System.Linq;
namespace DependencyInjection.Models{
	public class ProductTotalizer{
		public ProductTotalizer(IRepository repo){
			Repository = repo;
		}
		
        //所述仓库用接口表示
		public IRepository Repository {get;set;}
		
        //总价
		public decimal Total => Repository.Products.Sum(p=>p.Price);
	}
}

ASP.NET Core MVC依赖注入理解(极简个人版)_第3张图片
HomeController中加入ProductTotalizer变量。

...
public class HomeController:Controller{
	private IRepository repository;
	private ProductTotalizer totalizer;
	
	public HomeController(IRepository repo, ProductTotalizer total){
		repository = repo;
		totalizer = total;
	}
	
	public viewResult Index(){
		ViewBag.Total = totalizer.Total;
		return View(repository.Products);
	}
}

Index.cshtml中包含了对ViewBag的显示,这里实际绑定了2个数据,第一个是ViewBag,第二个是repository

为了明确在Controller中使用何种ProductTotalizer(可能在后面的开发中,ProductTotalizer是父类),可以对其进行强行指定。

//Startup.cs
...
public void ConfigureServices(IServicesCollection services){
	...
	services.AddTransient<ProductTotalizer>();
	...
}

5 服务生命周期

类别 生命周期
Transient 只要有调用就创建新的对象。就算类型一样,但是对象不一样
Scoped 在一个Controller之中,只要类型一样,对象就一样。刷新Controller之后对象变了,但是多个同一类型的对象还是同一个。
Singleton 只要初始化了,就一直是这个对象,不管是否刷新Controller

5.1 Transient

首先在service provider中注册IRepository的调用使用MemoryRepository

...
public void ConfigureServices(IServiceCollection services){
	services.AddTransient<IRepository, MemoryRepository>();
	...
}
...

repository中加入ToString()函数,该函数的实现中包含了GUID的生成。

//MemoryRepository.cs
using System.Collections.Generic;
namespace DependencyInjection.Models{
	public class MemoryRepository:IRepository{
        private IModelStorage storage;
        private string guid = System.Guid.NewGuid().ToString();
        
        public MemoryRepository(IModelStorage modelStore){
            storage = modelStore;
            ...
        }
        
        ...
            
        public override string ToString(){
            return guid;
        }
    }
}

在下图中可以看到,当HomeController调用repository时,service provider会针对每一次调用都生成一个新的对象。
ASP.NET Core MVC依赖注入理解(极简个人版)_第4张图片

5.2 Scoped

其他的调用都一样,除了在service中注册使用AddScoped之外。

//Startup.cs
...
public void ConfigureServices(IServiceCollection services){
	service.AddScoped<IRepository, MemoryRepository>();
	...
}

ASP.NET Core MVC依赖注入理解(极简个人版)_第5张图片

5.3 Singleton

其他的调用都一样,除了在service中注册使用AddSingleton之外。

//Startup.cs
...
public void ConfigureServices(IServiceCollection services){
	services.AddSingleton<IRepository, MemoryRepository>();
	...
}
...

ASP.NET Core MVC依赖注入理解(极简个人版)_第6张图片

6 其他依赖注入

6.1 Action依赖注入

上述的依赖注入是针对整个Controller,但有的时候只想针对某一个Action进行依赖注入。此时只需要使用[FromServices]即可。

using Microsoft.AspNetCore.Mvc;
using DependencyInjection.Models;
using DependencyInjection.Infrastructure;

namespace DependencyInjection.Controllers{
	public class HomeController:Controller{
		private IRepository repository;
		
		public HomeController(IRepository repo){
			repository = repo;
		}
		
		public ViewResult Index([FromServices]ProductTotalizer totalizer){
			ViewBag.HomeController = repository.ToString();
			ViewBag.Totalizer = totalizer.Repository.ToString();
			return View(repository.Products);
		}
	}
}

上述代码表示在Index中需要使用ProductTotalizer时,从service provider处获得。

6.2 手工进行依赖注入

除了上述的注册方式外,还有一些其他的注册方式。比如,当没有依赖出现时,而你又想获得一个接口的实例,该实例依赖于接口。在这种情况下,你可以直接通过service provider来实现。

...
public class HomeController:Controller{
	public ViewResult Index([FromServices]ProductTotalizer totalizer){
		IRepository repository=
			HttpContext.RequestServices.GetService<IRepository>();
			...
	}
}
...

你可能感兴趣的:(.NET技术,C#之接口,asp.net,mvc,xhtml)