.Net Core MVC 网站开发(Ninesky) 2.3、项目架构调整(续)-使用配置文件动态注入
上次实现了依赖注入,但是web项目必须要引用业务逻辑层和数据存储层的实现,项目解耦并不完全;另一方面,要同时注入业务逻辑层和数据访问层,注入的服务直接写在Startup中显得非常臃肿。理想的方式是,web项目近引用接口而不引用实现,在配置文件中进行配置实现程序集合类,注入业务逻辑层而不必注入数据访问层。
一、数据访问层
在项目中摒弃数据访问层或者使用EntityFramework作为数据访问层。
在项目中数据访问层主要实现数据的存储,仔细看一下EntityFramework发现DbContext的功能完全实现了查、增、删、改等各种操作,并且有缓存等功能,本身就实现了仓储模式,并且比自己封装的数据存储层的功能还强大,干脆在项目中用EntityFramework作为数据存储层。删除掉Ninesky.InterfaceDataLibrary项目和Ninesky.DataLibrary项目。
注:项目结构调整的确实太频繁了,以后一段时间内绝不再调整了。
二、实现业务逻辑层。
添加业务逻辑层接口项目Ninesky.InterfaceBase
1、添加接口基类接口InterfaceBaseService,添加基本的查、增、删、改方法
using Ninesky.Models;
using System;
using System.Linq;
using System.Linq.Expressions; namespace Ninesky.InterfaceBase
{
/// <summary>
/// 服务基础接口
/// </summary>
public interface InterfaceBaseService<T> where T:class
{ /// <summary>
/// 添加
/// </summary>
/// <param name="entity">实体</param>
/// <param name="isSave">是否立即保存</param>
/// <returns>添加的记录数</returns>
int Add(T entity, bool isSave = true); /// <summary>
/// 添加[批量]
/// </summary>
/// <param name="entities">实体</param>
/// <param name="isSave">是否立即保存</param>
/// <returns>添加的记录数</returns>
int AddRange(T[] entities, bool isSave = true); /// <summary>
/// 查询记录数
/// </summary>
/// <param name="predicate">查询条件</param>
/// <returns>记录数</returns>
int Count(Expression<Func<T, bool>> predicate); /// <summary>
/// 查询是否存在
/// </summary>
/// <param name="predicate">查询条件</param>
/// <returns>是否存在</returns>
bool Exists(Expression<Func<T, bool>> predicate); /// <summary>
/// 查找
/// </summary>
/// <param name="Id">主键</param>
/// <returns></returns>
T Find(int Id); /// <summary>
/// 查找
/// </summary>
/// <param name="keyValues">主键</param>
/// <returns></returns>
T Find(object[] keyValues); /// <summary>
/// 查找
/// </summary>
/// <param name="predicate">查询条件</param>
/// <returns></returns>
T Find(Expression<Func<T, bool>> predicate); IQueryable<T> FindList<TKey>(int number, Expression<Func<T, bool>> predicate); /// <summary>
/// 查询
/// </summary>
/// <typeparam name="TKey">排序属性</typeparam>
/// <param name="number">显示数量[小于等于0-不启用]</param>
/// <param name="predicate">查询条件</param>
/// <param name="keySelector">排序</param>
/// <param name="isAsc">正序</param>
/// <returns></returns>
IQueryable<T> FindList<TKey>(int number, Expression<Func<T, bool>> predicate, Expression<Func<T, TKey>> keySelector, bool isAsc); /// <summary>
/// 查询[分页]
/// </summary>
/// <typeparam name="TKey">排序属性</typeparam>
/// <param name="predicate">查询条件</param>
/// <param name="keySelector">排序</param>
/// <param name="isAsc">是否正序</param>
/// <param name="paging">分页数据</param>
/// <returns></returns>
Paging<T> FindList<TKey>(Expression<Func<T, bool>> predicate, Expression<Func<T, TKey>> keySelector, bool isAsc, Paging<T> paging); /// <summary>
/// 查询[分页]
/// </summary>
/// <typeparam name="TKey">排序属性</typeparam>
/// <param name="predicate">查询条件</param>
/// <param name="keySelector">排序</param>
/// <param name="isAsc">是否正序</param>
/// <param name="pageIndex">当前页</param>
/// <param name="pageSize">每页记录数</param>
/// <returns></returns>
Paging<T> FindList<TKey>(Expression<Func<T, bool>> predicate, Expression<Func<T, TKey>> keySelector, bool isAsc, int pageIndex, int pageSize); /// <summary>
/// 删除
/// </summary>
/// <param name="entity">实体</param>
/// <param name="isSave">是否立即保存</param>
/// <returns>是否删除成功</returns>
bool Remove(T entity, bool isSave = true); /// <summary>
/// 删除[批量]
/// </summary>
/// <param name="entities">实体数组</param>
/// <param name="isSave">是否立即保存</param>
/// <returns>成功删除的记录数</returns>
int RemoveRange(T[] entities, bool isSave = true); /// <summary>
/// 保存到数据库
/// </summary>
/// <returns>更改的记录数</returns>
int SaveChanges(); /// <summary>
/// 更新
/// </summary>
/// <param name="entity">实体</param>
/// <param name="isSave">是否立即保存</param>
/// <returns>是否保存成功</returns>
bool Update(T entity, bool isSave = true); /// <summary>
/// 更新[批量]
/// </summary>
/// <param name="entities">实体数组</param>
/// <param name="isSave">是否立即保存</param>
/// <returns>更新成功的记录数</returns>
int UpdateRange(T[] entities, bool isSave = true); }
}
2、在Ninesky.Base中添加,接口InterfaceBaseService的实现类BaseService.cs
using Microsoft.EntityFrameworkCore;
using Ninesky.InterfaceBase;
using Ninesky.Models;
using System;
using System.Linq;
using System.Linq.Expressions; namespace Ninesky.Base
{
/// <summary>
/// 服务基类
/// </summary>
public class BaseService<T>:InterfaceBaseService<T> where T:class
{
protected DbContext _dbContext;
public BaseService(DbContext dbContext)
{
_dbContext = dbContext;
} public virtual int Add(T entity, bool isSave = true)
{
_dbContext.Set<T>().Add(entity);
if (isSave) return _dbContext.SaveChanges();
else return 0;
} public virtual int AddRange(T[] entities, bool isSave = true)
{
_dbContext.Set<T>().AddRange(entities);
if (isSave) return _dbContext.SaveChanges();
else return 0;
} /// <summary>
/// 查询记录数
/// </summary>
/// <param name="predicate">查询条件</param>
/// <returns>记录数</returns>
public virtual int Count(Expression<Func<T, bool>> predicate)
{
return _dbContext.Set<T>().Count(predicate);
} /// <summary>
/// 查询是否存在
/// </summary>
/// <param name="predicate">查询条件</param>
/// <returns>是否存在</returns>
public virtual bool Exists(Expression<Func<T, bool>> predicate)
{
return Count(predicate) > 0;
} /// <summary>
/// 查找
/// </summary>
/// <param name="Id">主键</param>
/// <returns></returns>
public virtual T Find(int Id)
{
return _dbContext.Set<T>().Find(Id);
} public virtual T Find(object[] keyValues)
{
return _dbContext.Set<T>().Find(keyValues);
} public virtual T Find(Expression<Func<T, bool>> predicate)
{
return _dbContext.Set<T>().SingleOrDefault(predicate);
} public virtual IQueryable<T> FindList<TKey>(int number, Expression<Func<T, bool>> predicate)
{
var entityList = _dbContext.Set<T>().Where(predicate);
if (number > 0) return entityList.Take(number);
else return entityList;
} public virtual IQueryable<T> FindList<TKey>(int number, Expression<Func<T, bool>> predicate, Expression<Func<T, TKey>> keySelector, bool isAsc)
{
var entityList = _dbContext.Set<T>().Where(predicate);
if (isAsc) entityList = entityList.OrderBy(keySelector);
else entityList.OrderByDescending(keySelector);
if (number > 0) return entityList.Take(number);
else return entityList;
} public virtual Paging<T> FindList<TKey>(Expression<Func<T, bool>> predicate, Expression<Func<T, TKey>> keySelector, bool isAsc, Paging<T> paging)
{
var entityList = _dbContext.Set<T>().Where(predicate);
paging.Total = entityList.Count();
if (isAsc) entityList = entityList.OrderBy(keySelector);
else entityList.OrderByDescending(keySelector);
paging.Entities = entityList.Skip((paging.PageIndex - 1) * paging.PageSize).Take(paging.PageSize).ToList();
return paging;
} public virtual Paging<T> FindList<TKey>(Expression<Func<T, bool>> predicate, Expression<Func<T, TKey>> keySelector, bool isAsc, int pageIndex, int pageSize)
{
Paging<T> paging = new Paging<T> { PageIndex = pageIndex, PageSize = pageSize };
return FindList(predicate, keySelector, isAsc, paging);
} /// <summary>
/// 删除
/// </summary>
/// <param name="entity">实体</param>
/// <param name="isSave">是否立即保存</param>
/// <returns>是否删除成功</returns>
public virtual bool Remove(T entity, bool isSave = true)
{
_dbContext.Set<T>().Remove(entity);
if (isSave) return _dbContext.SaveChanges() > 0;
else return false;
} /// <summary>
/// 删除[批量]
/// </summary>
/// <param name="entities">实体数组</param>
/// <param name="isSave">是否立即保存</param>
/// <returns>成功删除的记录数</returns>
public virtual int RemoveRange(T[] entities, bool isSave = true)
{
_dbContext.Set<T>().RemoveRange(entities);
if (isSave) return _dbContext.SaveChanges();
else return 0;
} public virtual int SaveChanges()
{
return _dbContext.SaveChanges();
} /// <summary>
/// 更新
/// </summary>
/// <param name="entity">实体</param>
/// <param name="isSave">是否立即保存</param>
/// <returns>是否保存成功</returns>
public virtual bool Update(T entity, bool isSave = true)
{
_dbContext.Set<T>().Update(entity);
if (isSave) return _dbContext.SaveChanges() > 0;
else return false;
} /// <summary>
/// 更新[批量]
/// </summary>
/// <param name="entities">实体数组</param>
/// <param name="isSave">是否立即保存</param>
/// <returns>更新成功的记录数</returns>
public virtual int UpdateRange(T[] entities, bool isSave = true)
{
_dbContext.Set<T>().UpdateRange(entities);
if (isSave) return _dbContext.SaveChanges();
else return 0;
}
}
}
3、在Ninesky.InterfaceBase项目中添加栏目接口InterfaceCategoryService.cs,新增了一个Findtree的方法
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Ninesky.Models; namespace Ninesky.InterfaceBase
{
/// <summary>
/// 栏目服务接口
/// </summary>
public interface InterfaceCategoryService:InterfaceBaseService<Category>
{
/// <summary>
/// 查找树形菜单
/// </summary>
/// <param name="categoryType">栏目类型,可以为空</param>
/// <returns></returns>
List<Category> FindTree(CategoryType? categoryType);
}
}
4、在Ninesky.Base中添加栏目接口的实现类CategoryService.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ninesky.Models;
using Ninesky.InterfaceBase; namespace Ninesky.Base
{
/// <summary>
/// 栏目服务类
/// </summary>
public class CategoryService:BaseService<Category>,InterfaceCategoryService
{
public CategoryService(DbContext dbContext):base(dbContext)
{
}
/// <summary>
/// 查找
/// </summary>
/// <param name="Id">栏目ID</param>
/// <returns></returns>
public override Category Find(int Id)
{
return _dbContext.Set<Category>().Include("General").Include("Page").Include("Link").SingleOrDefault(c => c.CategoryId == Id);
} /// <summary>
/// 查找树形菜单
/// </summary>
/// <param name="categoryType">栏目类型,可以为空</param>
/// <returns></returns>
public List<Category> FindTree(CategoryType? categoryType)
{
var categories = _dbContext.Set<Category>().AsQueryable();
//根据栏目类型分类处理
switch (categoryType)
{
case null:
break;
case CategoryType.General:
categories = categories.Where(c => c.Type == categoryType);
break;
//默认-Page或Link类型
default:
//Id数组-含本栏目及父栏目
List<int> idArray = new List<int>();
//查找栏目id及父栏目路径
var categoryArray = categories.Where(c => c.Type == categoryType).Select(c => new { CategoryId = c.CategoryId, ParentPath = c.ParentPath });
if(categoryArray != null)
{
//添加栏目ID到
idArray.AddRange(categoryArray.Select(c => c.CategoryId));
foreach (var parentPath in categoryArray.Select(c=>c.ParentPath))
{
var parentIdArray = parentPath.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
if (parentIdArray != null)
{
int parseId = 0;
foreach(var parentId in parentIdArray)
{
if (int.TryParse(parentId, out parseId)) idArray.Add(parseId);
}
}
}
}
categories = categories.Where(c => idArray.Contains(c.CategoryId));
break;
}
return categories.OrderBy(c => c.ParentPath).ThenBy(C => C.Order).ToList();
}
}
}
三、实现dll动态加载和注入
要在web项目中对实现类进行解耦和注入,那么项目只能对接口进行依赖,解除对实现的依赖,然后在配置文件中配置实现的程序集和注入的服务,在Startup类中读取配置文件并加载程序集,然后实现接口的注入。
1、解除实现类依赖
在Web项目中添加对Ninesky.InterfaceBase项目的引用,解除对Ninesky.Base项目的引用。
2、实现注入的配置文件
首先在Models项目中实现注入服务类型配置项ServiceItem
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters; namespace Ninesky.Models
{
/// <summary>
/// 注入服务配置
/// </summary>
public class ServiceItem
{
/// <summary>
/// 服务类型[含命名空间]
/// </summary>
public string ServiceType { get; set; } /// <summary>
/// 实现类类型[含命名空间]
/// </summary>
public string ImplementationType { get; set; } /// <summary>
/// 生命周期
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public ServiceLifetime LifeTime { get; set; }
}
}
然后在Models项目中实现注入需要加载的程序集配置项 AssemblyItem
using System.Collections.Generic; namespace Ninesky.Models
{
/// <summary>
/// 程序集注入项目
/// </summary>
public class AssemblyItem
{
/// <summary>
/// 服务的程序集名称[不含后缀]
/// </summary>
public string ServiceAssembly { get; set; }
/// <summary>
/// 实现程序集名称[含后缀.dll]
/// </summary>
public string ImplementationAssembly { get; set; } /// <summary>
/// 注入服务集合
/// </summary>
public List<ServiceItem> DICollections { get; set; }
}
}
添加配置文件
在Web项目中添加配置文件service.json
{
"AssemblyCollections": [
{
"ServiceAssembly": "Ninesky.InterfaceBase",
"ImplementationAssembly": "Ninesky.Base.dll",
"DICollections": [
{
"ServiceType": "Ninesky.InterfaceBase.InterfaceCategoryService",
"ImplementationType": "Ninesky.Base.CategoryService",
"LifeTime": "Scoped"
}
]
}
]
}
可以看到配置文件的键值对于AssemblyItem类和ServiceItem类对应。集合的服务程序集为Ninesky.InterfaceBase,实现程序集为Ninesky.Base.dll,注入的服务为Ninesky.InterfaceBase.InterfaceCategoryService,实现类是Ninesky.Base.CategoryService。
读取配置文件并绑定到类型
在Startup只需要一行到即可绑定配置到类型。读取配置文件并绑定的详细操作见《Asp.Net Core自定义配置并绑定》
var assemblyCollections = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("service.json").Build().GetSection("AssemblyCollections").Get<List<AssemblyItem>>();
3、进行注入
在assemblyCollections变量加载了配置文件后使用如下代码即可实现注入
foreach(var assembly in assemblyCollections)
{
var serviceAssembly = Assembly.Load(new AssemblyName(assembly.ServiceAssembly));
var implementationAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(AppContext.BaseDirectory + "//" + assembly.ImplementationAssembly);
foreach(var service in assembly.DICollections)
{
services.Add(new ServiceDescriptor(serviceAssembly.GetType(service.ServiceType), implementationAssembly.GetType(service.ImplementationType), service.LifeTime));
}
}
代码中可以看到加载接口程序集使用的方法是Assembly.Load(new AssemblyName(assembly.ServiceAssembly)),这是因为项目引用了接口程序集的项目,加载程序集的时候只需要提供程序集的名称就可以。
加载实现类所在程序集的时候使用的是AssemblyLoadContext.Default.LoadFromAssemblyPath(AppContext.BaseDirectory + "//" + assembly.ImplementationAssembly)。在.Net Core中Assembly没有了LoadFrom方法,仅有一个Load方法加载已引用的程序集。多方搜索资料才找到AssemblyLoadContext中有一个方法可以不需要引用项目可以动态加载Dll,但必须包含Dll的完整路径。
到这里就完整实现了解耦,现在项目结构看起来是这样子

解耦后有些麻烦的是修改Base项目的代码后运行项目会出错,必须生成项目后将Base项目生成的Ninesky.Base.dll和Ninesky.Base.pdb复制到Web项目的bin\Debug\netcoreapp1.1目录下才能正常运行。
F5运行一下可以看到正常读出了数据。

四、其他
代码托管地址:https://git.oschina.net/ninesky/Ninesky
文章发布地址:http://www.ninesky.cn
代码包下载:Ninesky2.3、项目架构调整(续)-使用配置文件动态注入.rar
.Net Core MVC 网站开发(Ninesky) 2.3、项目架构调整(续)-使用配置文件动态注入的更多相关文章
- .Net Core MVC 网站开发(Ninesky) 2.3、项目架构调整-控制反转和依赖注入的使用
再次调整项目架构是因为和群友dezhou的一次聊天,我原来的想法是项目尽量做简单点别搞太复杂了,仅使用了DbContext的注入,其他的也没有写接口耦合度很高.和dezhou聊过之后我仔细考虑了一下, ...
- .Net Core MVC 网站开发(Ninesky) 2.2、栏目管理功能-System区域添加
在asp或asp.net中为了方便网站的结构清晰,通常把具有类似功能的页面放到一个文件夹中,用户管理功能都放在Admin文件夹下,用户功能都放在Member文件夹下,在MVC中,通常使用区域(Area ...
- .Net Core MVC 网站开发(Ninesky) 2.4、添加栏目与异步方法
在2.3中完成依赖注入后,这次主要实现栏目的添加功能.按照前面思路栏目有三种类型,常规栏目即可以添加子栏目也可以选择是否添加内容,内容又可以分文章或其他类型,所以还要添加一个模块功能.这次主要实现栏目 ...
- ASP.NET MVC 网站开发总结(三) ——图片截图上传
本着简洁直接,我们就直奔主题吧,这里需要使用到一个网页在线截图插件imgareaselect(请自行下载). 前台页面: <!DOCTYPE html> <html> < ...
- centos7+nginx部署asp.net core mvc网站
1. 安装 .net core 可参见官网安装教程. 选择Linux发行版本为Centos/Oracle 添加dotnet的yum仓库配置 $ sudo rpm -Uvh https://packag ...
- ASP.NET Core MVC 网站学习笔记
ASP.NET Core MVC 网站学习笔记 魏刘宏 2020 年 2 月 17 日 最近因为” 新冠” 疫情在家办公,学习了 ASP.NET Core MVC 网站的一些知识,记录如下. 一.新建 ...
- MVC5 网站开发之二 创建项目
昨天对项目的思路大致理了一下,今天先把解决方案建立起来.整个解决包含Ninesky.Web.Ninesky.Core,Ninesky.DataLibrary等3个项目.Ninesky.Web是web应 ...
- ASP.NET MVC 网站开发总结(一)
历经一个多月的努力,工作室的门户网站(http://www.wingstudio.org)终于结束了内部公测. 仔细算一下,大概把网站开发出1.0版本大概用了一个月的时间(主要是利用课余时间),其后大 ...
- ASP.NET MVC 网站开发总结(六)——简谈Json的序列化与反序列化
首先,先简单的谈一下什么是序列化与反序列化,序列化 (Serialization)将对象的状态信息转换为可以存储或传输的形式的过程.在序列化期间,对象将其当前状态写入到临时或持久性存储区.以后,可以通 ...
随机推荐
- 【造轮子】打造一个简单的万能Excel读写工具
大家工作或者平时是不是经常遇到要读写一些简单格式的Excel? shit!~很蛋疼,因为之前吹牛,就搞了个这东西,还算是挺实用,和大家分享下. 厌烦了每次搞简单类型的Excel读写?不怕~来,喜欢流式 ...
- 阿里云直播 C# SDK 如何使用
阿里云直播SDK的坑 1.直播云没有单独的SDK,直播部分被封装在CDN的相关SDK当中. 2.针对SDK,没有相关Demo. 3.针对SDK,没有相关的文档说明. 4.针对SDK的说明,官网上的说明 ...
- 使用ServiceStack构建Web服务
提到构建WebService服务,大家肯定第一个想到的是使用WCF,因为简单快捷嘛.首先要说明的是,本人对WCF不太了解,但是想快速建立一个WebService,于是看到了MSDN上的这一篇文章 Bu ...
- setAttribute()
●节点分为不同的类型:元素节点.属性节点和文本节点等. ●getElementById()方法将返回一个对象,该对象对应着文档里的一个特定的元素节点. ●getElementsByTagNam ...
- ABP文档 - 导航
文档目录 本节内容: 创建菜单 注册导航供应器 显示菜单 每个web应用都有一些菜单用来在页面/屏幕之间导航,ABP提供了一个通用的基础框架创建并显示菜单给用户. 创建菜单 一个应用可能由不同模块组成 ...
- Docker笔记一:基于Docker容器构建并运行 nginx + php + mysql ( mariadb ) 服务环境
首先为什么要自己编写Dockerfile来构建 nginx.php.mariadb这三个镜像呢?一是希望更深入了解Dockerfile的使用,也就能初步了解docker镜像是如何被构建的:二是希望将来 ...
- 运行执行sql文件脚本的例子
sqlcmd -s -d db_test -r -i G:\test.sql 黑色字体为关键命令,其他颜色(从左至右):服务器名称,用户名,密码,数据库,文件路径 通过select @@servern ...
- Hawk 4.7 单步调试
单步调试的意义 已经编写的工作流,可能会因为某些外界环境的变化而出错,此时需要排除错误,我们可以使用单步调试. 单步调试的本质,相当于只使用前n个模块,这样就能看到每个步骤下,流的改变. 例子 还是上 ...
- angular 源码分析 1 - angularInit()
angularjs 是个神奇的框架,由于我的好奇,想了解她的内部工作原理,只能一步一步的走进她,靠近她,和她深入的交流. angularjs 的入口是什么样子的呢?一起掀起她的盖头吧. 在这里我只讲方 ...
- 玩转spring boot——结合jQuery和AngularJs
在上篇的基础上 准备工作: 修改pom.xml <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi=&q ...