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

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

一、默认项目结构

我们看一下,vs2015默认生成的项目结构。

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

二、三层架构

我们再看看三层架构:

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

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

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

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

三、高耦合

我们看向Ninesky现在的项目结构,如下图:

包含四个项目:

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

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

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

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

从以上可以看到项目按照三层的思想进行了分层。PS:有群友问为什么项目名称叫DataLibrary、Base,不叫DAL,BLL?这可能是强迫症的原因,我反正看着DAL,BLL的项目名称特别不舒服,改了个自己喜欢的名字,其实功能都一样的。

再看一下项目的调用

看一下Ninesky.Base的CategoryService类。

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

四、依赖接口

我们添加一个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 }

修改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

在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 }

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

现在Ninesky.Base项目依然Ninesky.DataLibrary项目进行了依赖,并没有进行解耦,如果要想解除多Ninesky.DataLibrary的依赖就要想办法把接口的实例化转移到项目之外去。

五、控制反转

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

构造函数传递了一个接口类型的参数,现在类中完全和Ninesky.DataLibrary没有了关系,可以删除对Ninesky.DataLibrary项目的引用了。

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

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

六、服务定位器

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

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

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

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

七、依赖注入。

以前.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 }

然后我们进入,Web的启动类Startup进行注入。如下图:

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

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

第三个红框内注入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浏览器中查看一下,可以看到取出了数据,只是因为数据存储层的代码没有包含导航属性所以数据不完整。

八、其他

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

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

http://mzwhj.cnblogs.com/

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

返回目录

.Net Core MVC 网站开发(Ninesky) 2.3、项目架构调整-控制反转和依赖注入的使用的更多相关文章

  1. .Net Core MVC 网站开发(Ninesky) 2.3、项目架构调整(续)-使用配置文件动态注入

    上次实现了依赖注入,但是web项目必须要引用业务逻辑层和数据存储层的实现,项目解耦并不完全:另一方面,要同时注入业务逻辑层和数据访问层,注入的服务直接写在Startup中显得非常臃肿.理想的方式是,w ...

  2. .NET Core ASP.NET Core Basic 1-2 控制反转与依赖注入

    .NET Core ASP.NET Core Basic 1-2 本节内容为控制反转与依赖注入 简介 控制反转IOC 这个内容事实上在我们的C#高级篇就已经有所讲解,控制反转是一种设计模式,你可以这样 ...

  3. .Net Core MVC 网站开发(Ninesky) 2.2、栏目管理功能-System区域添加

    在asp或asp.net中为了方便网站的结构清晰,通常把具有类似功能的页面放到一个文件夹中,用户管理功能都放在Admin文件夹下,用户功能都放在Member文件夹下,在MVC中,通常使用区域(Area ...

  4. .Net Core MVC 网站开发(Ninesky) 2.4、添加栏目与异步方法

    在2.3中完成依赖注入后,这次主要实现栏目的添加功能.按照前面思路栏目有三种类型,常规栏目即可以添加子栏目也可以选择是否添加内容,内容又可以分文章或其他类型,所以还要添加一个模块功能.这次主要实现栏目 ...

  5. 搞定.NET MVC IOC控制反转,依赖注入

    一直听说IOC,但是一直没接触过,只看例子好像很高达上的样子,今天抽了点时间实现了下,当然也是借助博客园里面很多前辈的文章来搞的!现在做个笔记,防止自己以后忘记! 1.首先创建MVC项目 2.然后新建 ...

  6. ASP.NET MVC 网站开发总结(三) ——图片截图上传

    本着简洁直接,我们就直奔主题吧,这里需要使用到一个网页在线截图插件imgareaselect(请自行下载). 前台页面: <!DOCTYPE html> <html> < ...

  7. centos7+nginx部署asp.net core mvc网站

    1. 安装 .net core 可参见官网安装教程. 选择Linux发行版本为Centos/Oracle 添加dotnet的yum仓库配置 $ sudo rpm -Uvh https://packag ...

  8. ASP.NET Core MVC 网站学习笔记

    ASP.NET Core MVC 网站学习笔记 魏刘宏 2020 年 2 月 17 日 最近因为” 新冠” 疫情在家办公,学习了 ASP.NET Core MVC 网站的一些知识,记录如下. 一.新建 ...

  9. MVC5 网站开发之二 创建项目

    昨天对项目的思路大致理了一下,今天先把解决方案建立起来.整个解决包含Ninesky.Web.Ninesky.Core,Ninesky.DataLibrary等3个项目.Ninesky.Web是web应 ...

随机推荐

  1. windows系统下安装MySQL

    可以运行在本地windows版本的MySQL数据库程 序自从3.21版以后已经可以从MySQL AB公司获得,而且 MYSQL每日的下载百分比非常大.这部分描述在windows上安装MySQL的过程. ...

  2. R(八): R分词统计-老九门

    分析文本内容基本的步骤:提取文本中的词语 -> 统计词语频率 -> 词频属性可视化.词频:能反映词语在文本中的重要性,一般越重要的词语,在文本中出现的次数就会越多.词云:让词语的频率属性可 ...

  3. LA 6042 Bee Tower 记忆化搜索

    一开始读漏了很多细节,用递推写死活跑不出样例. 把题目中的细节列一下吧,状态方程很好推,改成记忆化搜索之后代码也很清晰. 1.蜜蜂需要到最高的塔去,最高的塔可能不止一个,抵达任意一个即可. 2.蜜蜂每 ...

  4. python小练习,打出1-100之间的所有偶数,设计一个函数,在桌面上创建10个文件,并以数字命名,复利计算函数

    练习一:打出1-100之间的所有偶数 def even_print(): for i in range(1,101): if i % 2 == 0: print (i) even_print() #列 ...

  5. BZOJ 2843: 极地旅行社( LCT )

    LCT.. ------------------------------------------------------------------------ #include<cstdio> ...

  6. C# 开发系列(一)

    1.开发环境搭建: win7 32位系统,然后安装 visual studio 2012. 2.将 .net_framework 加入到windows 的环境变量当中. C:\Windows\Micr ...

  7. linux 两个查找工具 locate,find详解

    linux 中有很多查找工具,今天主要讲解locate,find两个工具. 1.locate (1)查询系统上预建的文件索引数据库 /var/lib/mlocate/mlocate.db 注意:如果这 ...

  8. Python rsa公私钥生成 rsa公钥加解密(分段加解密)-私钥加签验签实战

    一般现在的SAAS服务提供现在的sdk或api对接服务都涉及到一个身份验证和数据加密的问题.一般现在普遍的做法就是配置使用非对称加密的方式来解决这个问题,你持有SAAS公司的公钥,SAAS公司持有你的 ...

  9. 《Linux内核分析》第六周笔记 进程的描述和进程的创建

    进程的描述和进程的创建 一.进程的描述 1.进程描述符task_struct数据结构(一) 操作系统的三大功能:进程管理(核心).内存管理.文件系统. 进程控制块PCB——task_struct(进程 ...

  10. IDEA14添加SVN

    今天升级IDEA14,发现SVN变了,折腾了好一会才搞好.记录一下,希望其他人能少走弯路.主要是IDEA14的svn是用命令行工具进行操作,这样对以后SVN的兼容性也会好很多,这个在他们官方的博客上也 ...