.Net Core MVC 网站开发(Ninesky) 2.三、项目架构调整-控制反转和依赖注入的使用

再次调整项目架构是由于和群友dezhou的一次聊天,我原来的想法是项目尽可能作简单点别搞太复杂了,仅使用了DbContext的注入,其余的也没有写接口耦合度很高。和dezhou聊过以后我仔细考虑了一下,仍是解耦吧,原本按照软件设计模式就应该是高内聚低耦合的,低耦合使项目的模块独立于其余模块,增长了可维护性和移植性!html

:前面写的博客详细记录没项目操做的每一步,其实写起博客来很费时间,并且整片博文里不少无用的信息。对MVC来讲会添加控制器,添加视图,添加类这些都最基本的要求了,而且前面博文里都写了,后面也就再也不详细写这些东西了,主要写一些思路和关键代码,具体内容以源代码的形式放在博客后面提供下载。git

 

1、默认项目结构

咱们看一下,vs2015默认生成的项目结构。数据库

image

项目中模型、数据访问、业务逻辑和视图相关的内容都在一个项目中,视图、业务逻辑和显示牢牢耦合,前期看着还没什么,到了内容多了项目变大之后,尤为是隔一段时间再更新项目,在看的话一片混乱,有时候一个小的改动形成整个项目导出报错,头痛之极。设计模式

2、三层架构

咱们再看看三层架构:浏览器

  • 用户界面表示层(USL)
  • 业务逻辑层(BLL)
  • 数据访问层(DAL)

三层架构主要是使项目结构更清楚,分工更明确,有利于后期的维护和升级。它未必会提高性能,由于当子程序模块未执行结束时,主程序模块只能处于等待状态。这说明将应用程序划分层次,会带来其执行速度上的一些损失。但从团队开发效率角和维护性上来讲易于进行任务分配,可维护性高。架构

按照三层的思想,MVC中的控制器(C)和视图(V)都是处理界面显示相关的内容属于用户界面表示层(USL) ,模型(M)是控制器和视图间交换的数据,因此MVC框架应该都属于三层中的用户界面表示层。 框架

数据访问层(DAL)和业务逻辑层(BLL) 、业务逻辑层和用户界面表示层(USL) 也要交换数据,干脆把模型(M)独立出来,做为控制器和视图,及三个层次之间交换的数据。 ide

3、高耦合

咱们看向Ninesky如今的项目结构,以下图: 函数

image

包含四个项目: post

Ninesky.DataLibrary是数据访问层,提供数据库访问的支持。

Ninesky.Base 是业务逻辑层,负责业务逻辑的处理。

Ninesky.Web 用户界面表示层(USL),负责显示页面和显示项目的逻辑处理。

Ninesky.Models 就是各层之间交换的数据实体。

从以上能够看到项目按照三层的思想进行了分层。PS:有群友问为何项目名称叫DataLibrary、Base,不叫DAL,BLL?这多是强迫症的缘由,我反正看着DAL,BLL的项目名称特别不舒服,改了个本身喜欢的名字,其实功能都同样的。

再看一下项目的调用

看一下Ninesky.Base的CategoryService类。

image

代码中位置1声明了类CategoryRepository,这个类是 Ninesky.DataLibrary中的一个类。位置2将这个项目实例化了,在位置3处咱们直接调用了这个类的Find方法。从上面能够看出CategoryService类是依赖CategoryRepository类的;Ninesky.Base项目是依赖于Ninesky.DataLibrary项目的。一个项目的类精确的调用了另外一个项目类的方法那么他们之间就是高耦合。发生高耦合就是软件设计有问题,就要解耦,把依赖实现代码转换成依赖逻辑,这时候就要引入抽象层(一般是接口)。

4、依赖接口

咱们添加一个dll项目Ninesky.InterfaceDataLibrary,给Ninesky.DataLibrary添加对Ninesky.InterfaceDataLibrary项目的引用。

在Ninesky.InterfaceDataLibrary项目添加InterfaceBaseRepository接口

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq.Expressions;
  4 using System.Threading.Tasks;
  5 
  6 namespace Ninesky.InterfaceDataLibrary
  7 {
  8     /// <summary>
  9     /// 仓储基类接口
 10     /// </summary>
 11     /// <typeparam name="T"></typeparam>
 12     public interface InterfaceBaseRepository<T> where T : class
 13     {
 14         /// <summary>
 15         /// 查询[不含导航属性]
 16         /// </summary>
 17         /// <param name="predicate">查询表达式</param>
 18         /// <returns>实体</returns>
 19         T Find(Expression<Func<T, bool>> predicate);
 20     }
 21 }
View Code
修改BaseRepository代码,让BaseRepository继承InterfaceBaseRepository
  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Linq.Expressions;
  5 using Microsoft.EntityFrameworkCore;
  6 using Ninesky.InterfaceDataLibrary;
  7 
  8 namespace Ninesky.DataLibrary
  9 {
 10     /// <summary>
 11     /// 仓储基类
 12     /// </summary>
 13     public class BaseRepository<T> :InterfaceBaseRepository<T> where T : class
 14     {
 15         protected DbContext _dbContext;
 16         public BaseRepository(DbContext dbContext)
 17         {
 18             _dbContext = dbContext;
 19         }
 20 
 21         /// <summary>
 22         /// 查询[不含导航属性]
 23         /// </summary>
 24         /// <param name="predicate">查询表达式</param>
 25         /// <returns>实体</returns>
 26         public virtual T Find(Expression<Func<T, bool>> predicate)
 27         {
 28             return _dbContext.Set<T>().SingleOrDefault(predicate);
 29         }
 30     }
 31 }
 32 
View Code

在Ninesky.Base项目中引用Ninesky.InterfaceDataLibrary,咱们在修改CategoryService代码

  1     public class CategoryService
  2     {
  3         private InterfaceBaseRepository<Category> _categoryRepository;
  4         public CategoryService(DbContext dbContext)
  5         {
  6             _categoryRepository = new BaseRepository<Category>(dbContext);
  7         }
  8 
  9         /// <summary>
 10         /// 查找
 11         /// </summary>
 12         /// <param name="Id">栏目Id</param>
 13         /// <returns></returns>
 14         public Category Find(int Id)
 15         {
 16             return _categoryRepository.Find(c => c.CategoryId == Id);
 17         }
 18     }
View Code

在代码开始处声明了变量类型为InterfaceBaseRepository的变量,在构造函数中将InterfaceBaseRepository实例化为BaseRepository类型。

如今Ninesky.Base项目依然Ninesky.DataLibrary项目进行了依赖,并无进行解耦,若是要想解除多Ninesky.DataLibrary的依赖就要想办法把接口的实例化转移到项目以外去。

5、控制反转

控制反转就是把依赖的建立移到类的外部。那么咱们修改CategoryService类的构造函数。

image

构造函数传递了一个接口类型的参数,如今类中彻底和Ninesky.DataLibrary没有了关系,能够删除对Ninesky.DataLibrary项目的引用了。

那如今又有了一个新的问题:控制反转如何实现,怎么进行接口的实例化?

经常使用的解决方法有服务定位器和依赖注入。

6、服务定位器

服务定位器就是在类中集中进行实例化。

单首创建一个项目,添加对项目的引用,而后再工厂类中集中进行实例化。

  1 public class Factory
  2 {
  3     public InterfaceBaseRepository<Category> GetBaseRepository()
  4     {
  5         return new BaseRepository<Category>();
  6     }
  7 }
View Code

服务定位器的好处是实现比较简单,能够建立一个全局的服务定位器,缺点就是组件需求不透明。Ninesky采用另外一种控制反转的实现:依赖注入。

7、依赖注入。

之前.Net MVC中注入挺麻烦的,幸亏.Net Core MVC中内建了依赖注入的支持。

修改CategoryController代码,使用构造函数注入。这里为了例子的简单在控制器中直接使用数据存储层的类进行注入,而没有使用业务逻辑层的类。

控制器中采用构造函数注入,构造函数中传递CategoryService参数。

  1     public class CategoryController : Controller
  2     {
  3         /// <summary>
  4         /// 数据上下文
  5         /// </summary>
  6         private NineskyDbContext _dbContext;
  7 
  8         /// <summary>
  9         /// 栏目服务
 10         /// </summary>
 11         private CategoryService _categoryService;
 12 
 13         public CategoryController(CategoryService categoryService)
 14         {
 15             _categoryService = categoryService;
 16         }
 17 
 18         /// <summary>
 19         /// 查看栏目
 20         /// </summary>
 21         /// <param name="id">栏目Id</param>
 22         /// <returns></returns>
 23         [Route("/Category/{id:int}")]
 24         public IActionResult Index(int id)
 25         {
 26             var category = _categoryService.Find(id);
 27             if (category == null) return View("Error", new Models.Error { Title = "错误消息", Name="栏目不存在", Description="访问ID为【"+id+"】的栏目时发生错误,该栏目不存在。" });
 28             switch (category.Type)
 29             {
 30                 case CategoryType.General:
 31                     if (category.General == null) return View("Error",new Models.Error { Title="错误消息", Name="栏目数据不完整",Description="找不到栏目【"+category.Name+"】的详细数据。" });
 32                     return View(category.General.View, category);
 33                 case CategoryType.Page:
 34                     if (category.Page == null) return View("Error", new Models.Error { Title = "错误消息", Name = "栏目数据不完整", Description = "找不到栏目【" + category.Name + "】的详细数据。" });
 35                     return View(category.Page.View, category);
 36                 case CategoryType.Link:
 37                     if (category.Link == null) return View("Error", new Models.Error { Title = "错误消息", Name = "栏目数据不完整", Description = "找不到栏目【" + category.Name + "】的详细数据。" });
 38                     return Redirect(category.Link.Url);
 39                 default:
 40                     return View("Error", new Models.Error { Title = "错误消息", Name = "栏目数据错误", Description = "栏目【" + category.Name + "】的类型错误。" });
 41 
 42             }
 43         }
 44     }
View Code

而后咱们进入,Web的启动类Startup进行注入。以下图:

image

 

第一个红框内是在《2.一、栏目的前台显示》中注入的上下文;

第二个红框到第四个红框内是今天添加的内容。

第三个红框内注入InterfaceBaseRepository接口,使用BaseRepository进行实例化。

有第二个红框的内容是由于BaseRepository实例化时有一个DbContext类型的参数。在注入的时候要求用到的参数必需要在前面注入,而且系统并不会自动吧NineskyDbContext转换为DbContext。因此必须注入一个DbContext类型的参数。

第四个红框是注入CategoryService。这里CategoryService一样可使用接口,时间缘由没写。

至此能够看到,CategoryService解除了对BaseRepository的依赖,在Ninesky.Base项目中没有对Ninesky.DataLibrary进行任何的依赖,类的实例化是在Web项目中进行注入的,Web项目对Ninesky.DataLibrary进行了依赖。一样的方法也能够实现Web项目对Ninesky.Base项目的解耦。

若是要彻底解除Ninesky.Web项目对Ninesky.DataLibrary和Ninesky.Base项目的依赖,可使用配置文件加载,此次先不写了。

F5浏览器中查看一下,能够看到取出了数据,只是由于数据存储层的代码没有包含导航属性因此数据不完整。

image

8、其余

代码托管地址:https://git.oschina.net/ninesky/Ninesky

文章发布地址:http://www.ninesky.cn

                 http://mzwhj.cnblogs.com/

代码包下载:Ninesky2.3项目架构调整-控制反转和依赖注入的使用.rar

 

返回目录

相关文章
相关标签/搜索