Asp.Net Core 项目实战之权限管理系统(4) 依赖注入、仓储、服务的多项目分层实现
0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有
1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端
2 Asp.Net Core 项目实战之权限管理系统(2) 功能及实体设计
3 Asp.Net Core 项目实战之权限管理系统(3) 通过EntityFramework Core使用PostgreSQL
4 Asp.Net Core 项目实战之权限管理系统(4) 依赖注入、仓储、服务的多项目分层实现
5 Asp.Net Core 项目实战之权限管理系统(5) 用户登录
6 Asp.Net Core 项目实战之权限管理系统(6) 功能管理
7 Asp.Net Core 项目实战之权限管理系统(7) 组织机构、角色、用户权限
8 Asp.Net Core 项目实战之权限管理系统(8) 功能菜单的动态加载
0 项目结构
写这个系列的最初目的其实只是为了自己能更好的学习Asp.Net Core,用一个小的系统作为练习,也督促自己。短期内不见得在实际项目中真的会运用,但至少通过学习,大致的对Asp.Net Core有个了解,也是为以后可能的应用做一下技术储备。起初的设想很简单,就是在一个Web项目中完成所有工作,大致了解Asp.Net Core的知识体系。
在实践的过程中不自觉的对这个练习的项目进行了一下分层。目前项目整体机构如下:

项目说明:
- Fonour.MVC
Asp.Net Core MVC网站项目。
- Fonour.Application
应用服务项目,定义应用服务接口及实现,供Fonour.MVC控制器调用;同时定义接收及返回数据对象(Dto,这里我有可能会省去,直接拿实体往表现层传了……)
- Fonour.Domain
主要定义实体、仓储接口等。
- Fonour.EntityFrameworkCore
主要是仓储接口的EF Core具体实现
- Fonour.Utility
通用项目,定义项目无关的一些公共类库。
1 仓储接口定义
1.0 基本接口定义
仓储接口定义使用泛型接口,主要定义实体基本的增、删、改、查操作。在Fonour.Domain项目中新建一个名称为“IRepositories”的文件夹,在该文件夹中新建一个名称为“IRepository”的接口文件。暂时定义以下几个基本的接口,日后针对批量插入、删除、分页等操作再做进一步的完善。
/// <summary>
/// 仓储接口定义
/// </summary>
public interface IRepository
{ }
/// <summary>
/// 定义泛型仓储接口
/// </summary>
/// <typeparam name="TEntity">实体类型</typeparam>
/// <typeparam name="TPrimaryKey">主键类型</typeparam>
public interface IRepository<TEntity, TPrimaryKey> : IRepository where TEntity : Entity<TPrimaryKey>
{
/// <summary>
/// 获取实体集合
/// </summary>
/// <returns></returns>
List<TEntity> GetAllList(); /// <summary>
/// 根据lambda表达式条件获取实体集合
/// </summary>
/// <param name="predicate">lambda表达式条件</param>
/// <returns></returns>
List<TEntity> GetAllList(Expression<Func<TEntity, bool>> predicate); /// <summary>
/// 根据主键获取实体
/// </summary>
/// <param name="id">实体主键</param>
/// <returns></returns>
TEntity Get(TPrimaryKey id); /// <summary>
/// 根据lambda表达式条件获取单个实体
/// </summary>
/// <param name="predicate">lambda表达式条件</param>
/// <returns></returns>
TEntity FirstOrDefault(Expression<Func<TEntity, bool>> predicate); /// <summary>
/// 新增实体
/// </summary>
/// <param name="entity">实体</param>
/// <returns></returns>
TEntity Insert(TEntity entity); /// <summary>
/// 更新实体
/// </summary>
/// <param name="entity">实体</param>
TEntity Update(TEntity entity); /// <summary>
/// 新增或更新实体
/// </summary>
/// <param name="entity">实体</param>
TEntity InsertOrUpdate(TEntity entity); /// <summary>
/// 删除实体
/// </summary>
/// <param name="entity">要删除的实体</param>
bool Delete(TEntity entity); /// <summary>
/// 删除实体
/// </summary>
/// <param name="id">实体主键</param>
bool Delete(TPrimaryKey id);
}
这个练习项目使用的是Guid类型的主键,为方便使用,再继承定义一个主键类型为Guid的接口。
/// <summary>
/// 默认Guid主键类型仓储
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public interface IRepository<TEntity> : IRepository<TEntity, Guid> where TEntity : Entity
{ }
1.1 用户管理仓储接口定义
如无特殊操作需要,我们的基础接口基本上能够满足各类实体共性的增删改查操作。对于某种实体特有的操作,就需要单独进行操作接口的定义。比如后面我们要实现用户登录的验证功能,即提供用户名、密码,验证该用户是否存在,以及用户名密码是否正确。对于此类特定需求,我们针对用户实体定义一个用户管理的仓储接口。
在“IRepositories”文件夹下新建一个名称为“IUserRepository”的接口,里面暂时只定义一个检查用户是否存在的方法,做为我们最后的测试接口。
/// <summary>
/// 用户管理仓储接口
/// </summary>
public interface IUserRepository : IRepository<User>
{
/// <summary>
/// 检查用户是存在
/// </summary>
/// <param name="userName">用户名</param>
/// <param name="password">密码</param>
/// <returns>存在返回用户实体,否则返回NULL</returns>
User CheckUser(string userName, string password);
}

2 仓储接口实现
仓储接口的实现全部放在Fonour.EntityFrameworkCore项目中,通过EF Core使用PostgresSQL数据库实现。
2.0 基本接口实现
在Fonour.EntityFrameworkCore项目中新建一个名称为“Repositories”的文件夹,在文件夹中添加一个名称为“FonourRepositoryBase”的抽象类。
该抽象类除了实现基本仓储接口定义的方法外,还有2个需要注意的地方。、
1 定义了数据访问上下文对象
该数据访问上下文对象通过构造函数进行依赖注入,Asp.Net Core已经默认对数据访问上下文对象进行了构造函数依赖注入的实现,具体应用后面会说到。
2 定义了一个Save操作方法
目的为了在应用服务层调用多个仓储后,统一进行数据上下文的SaveChanges操作,保证数据存储的事务性。
/// <summary>
/// 仓储基类
/// </summary>
/// <typeparam name="TEntity">实体类型</typeparam>
/// <typeparam name="TPrimaryKey">主键类型</typeparam>
public abstract class FonourRepositoryBase<TEntity, TPrimaryKey> : IRepository<TEntity, TPrimaryKey> where TEntity : Entity<TPrimaryKey>
{
//定义数据访问上下文对象
protected readonly FonourDbContext _dbContext; /// <summary>
/// 通过构造函数注入得到数据上下文对象实例
/// </summary>
/// <param name="dbContext"></param>
public FonourRepositoryBase(FonourDbContext dbContext)
{
_dbContext = dbContext;
} /// <summary>
/// 获取实体集合
/// </summary>
/// <returns></returns>
public List<TEntity> GetAllList()
{
return _dbContext.Set<TEntity>().ToList();
} /// <summary>
/// 根据lambda表达式条件获取实体集合
/// </summary>
/// <param name="predicate">lambda表达式条件</param>
/// <returns></returns>
public List<TEntity> GetAllList(Expression<Func<TEntity, bool>> predicate)
{
return _dbContext.Set<TEntity>().Where(predicate).ToList();
} /// <summary>
/// 根据主键获取实体
/// </summary>
/// <param name="id">实体主键</param>
/// <returns></returns>
public TEntity Get(TPrimaryKey id)
{
return _dbContext.Set<TEntity>().FirstOrDefault(CreateEqualityExpressionForId(id));
} /// <summary>
/// 根据lambda表达式条件获取单个实体
/// </summary>
/// <param name="predicate">lambda表达式条件</param>
/// <returns></returns>
public TEntity FirstOrDefault(Expression<Func<TEntity, bool>> predicate)
{
return _dbContext.Set<TEntity>().FirstOrDefault(predicate);
} /// <summary>
/// 新增实体
/// </summary>
/// <param name="entity">实体</param>
/// <returns></returns>
public TEntity Insert(TEntity entity)
{
_dbContext.Set<TEntity>().Add(entity);
return entity;
} /// <summary>
/// 更新实体
/// </summary>
/// <param name="entity">实体</param>
public TEntity Update(TEntity entity)
{
_dbContext.Set<TEntity>().Attach(entity);
_dbContext.Entry(entity).State = EntityState.Modified;
return entity;
} /// <summary>
/// 新增或更新实体
/// </summary>
/// <param name="entity">实体</param>
public TEntity InsertOrUpdate(TEntity entity)
{
if (Get(entity.Id) != null)
return Update(entity);
return Insert(entity);
} /// <summary>
/// 删除实体
/// </summary>
/// <param name="entity">要删除的实体</param>
public void Delete(TEntity entity)
{
_dbContext.Set<TEntity>().Remove(entity);
} /// <summary>
/// 删除实体
/// </summary>
/// <param name="id">实体主键</param>
public void Delete(TPrimaryKey id)
{
_dbContext.Set<TEntity>().Remove(Get(id));
} /// <summary>
/// 事务性保存
/// </summary>
public void Save()
{
_dbContext.SaveChanges();
} /// <summary>
/// 根据主键构建判断表达式
/// </summary>
/// <param name="id">主键</param>
/// <returns></returns>
protected static Expression<Func<TEntity, bool>> CreateEqualityExpressionForId(TPrimaryKey id)
{
var lambdaParam = Expression.Parameter(typeof(TEntity));
var lambdaBody = Expression.Equal(
Expression.PropertyOrField(lambdaParam, "Id"),
Expression.Constant(id, typeof(TPrimaryKey))
); return Expression.Lambda<Func<TEntity, bool>>(lambdaBody, lambdaParam);
}
}
同样的实现一个主键类型为Guid的仓储操作基类。
/// <summary>
/// 主键为Guid类型的仓储基类
/// </summary>
/// <typeparam name="TEntity">实体类型</typeparam>
public abstract class FonourRepositoryBase<TEntity> : FonourRepositoryBase<TEntity, Guid> where TEntity : Entity
{
public FonourRepositoryBase(FonourDbContext dbContext) : base(dbContext)
{
}
}
2.1 用户管理仓储接口实现
在Fonour.EntityFrameworkCore项目的“Repositories”文件夹中新建一个用户管理仓储接口的实现类“UserRepository”,实现接口中定义的用户检查方法。
/// <summary>
/// 用户管理仓储实现
/// </summary>
public class UserRepository : FonourRepositoryBase<User>, IUserRepository
{
public UserRepository(FonourDbContext dbcontext) : base(dbcontext)
{ }
/// <summary>
/// 检查用户是存在
/// </summary>
/// <param name="userName">用户名</param>
/// <param name="password">密码</param>
/// <returns>存在返回用户实体,否则返回NULL</returns>
public User CheckUser(string userName, string password)
{
return _dbContext.Set<User>().FirstOrDefault(it => it.UserName == userName && it.Password == password);
}
}

3 应用服务层接口定义及实现
在Fonour.Application项目中新建一个名称为“UserApp”的文件夹,文件夹中新建一个名称为“IUserAppService”的服务接口,及服务接口的具体实现“UserAppService”,在服务层中调用对应的仓储方法实现具体相关业务逻辑。
UserAppService中定义一个私有且只读的用户管理仓储接口对象,依然通过构造函数的方式进行依赖注入。
/// <summary>
/// 用户管理服务
/// </summary>
public class UserAppService : IUserAppService
{
//用户管理仓储接口
private readonly IUserRepository _userReporitory; /// <summary>
/// 构造函数 实现依赖注入
/// </summary>
/// <param name="userRepository">仓储对象</param>
public UserAppService(IUserRepository userRepository)
{
_userReporitory = userRepository;
} public User CheckUser(string userName, string password)
{
return _userReporitory.CheckUser(userName, password);
}
}
4 Asp.Net Core依赖注入实现
在上一节中,我们讲到在Fonour.MVC项目的Startup.cs文件的ConfigureServices方法中通过使用
services.AddDbContext<FonourDbContext>(options =>options.UseNpgsql(sqlConnectionString));
方法将数据库上上下文添加到系统服务中,正是在此时同时对数据访问上下文进行了依赖注入实现。
通过添加以下代码在ConfigureServices方法中添加对上面创建的仓储及服务进行依赖注入的实现。
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<IUserAppService, UserAppService>();
注意:Asp.Net Core提供的依赖注入拥有三种生命周期模式,由短到长依次为:
- Transient ServiceProvider总是创建一个新的服务实例。
- Scoped ServiceProvider创建的服务实例由自己保存,(同一次请求)所以同一个ServiceProvider对象提供的服务实例均是同一个对象。
- Singleton 始终是同一个实例对象
对于数据访问上下文,我们可以通过重载方法的第二个参数,控制数据访问上下文对象的生命周期,默认生命周期为Scoped。
services.AddDbContext<FonourDbContext>(options => options.UseNpgsql(sqlConnectionString), ServiceLifetime.Transient);
services.AddDbContext<FonourDbContext>(options => options.UseNpgsql(sqlConnectionString), ServiceLifetime.Scoped);
services.AddDbContext<FonourDbContext>(options => options.UseNpgsql(sqlConnectionString), ServiceLifetime.Singleton);
对于要依赖注入的接口和对象提供AddTransient、AddScoped、AddSingleton三个方法控制对象声明周期。
5 测试
我们在Fonour.MVC项目的LoginController中增加一个IUserAppService服务对象的定义,同时提供LoginController的构造函数,在构造函数中实现对UserAppService服务的依赖注入。
在Index控制器中增加IUserAppService的用户检查方法的调用代码,增加一个断点,用于测试。
public class LoginController : Controller
{
private IUserAppService _userAppService;
public LoginController(IUserAppService userAppService)
{
_userAppService = userAppService;
}
// GET: /<controller>/
public IActionResult Index()
{
var user = _userAppService.CheckUser("admin", "");
return View();
}
}
运行程序,进入断点,发现已经成功根据用户名和密码,把上一节创建的用户数据信息取出,至此,我们项目的分层之间通道已经打通。

6 总结
本节主要涉及到Asp.Net Core的知识点是它的依赖注入机制,我们通过清晰多项目分层结构,采用依赖注入机制,实现了各通之间的连接。
下一节实现用户登录相关,主要有用户登录验证,以及用户对控制器Action路由访问的拦截及判断。
Asp.Net Core 项目实战之权限管理系统(4) 依赖注入、仓储、服务的多项目分层实现的更多相关文章
- Asp.Net Core 项目实战之权限管理系统(0) 无中生有
0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...
- Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端
0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...
- Asp.Net Core 项目实战之权限管理系统(2) 功能及实体设计
0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...
- Asp.Net Core 项目实战之权限管理系统(3) 通过EntityFramework Core使用PostgreSQL
0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...
- Asp.Net Core 项目实战之权限管理系统(5) 用户登录
0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...
- Asp.Net Core 项目实战之权限管理系统(6) 功能管理
0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...
- Asp.Net Core 项目实战之权限管理系统(7) 组织机构、角色、用户权限
0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...
- Asp.Net Core 项目实战之权限管理系统(8) 功能菜单的动态加载
0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...
- Net Core 项目实战之权限管理系统(0)
0 前言 Net Core 项目实战之权限管理系统(0) 无中生有 0 http://www.cnblogs.com/fonour/p/5848933.html 学习的最好方法就是动手去做,这里以 ...
随机推荐
- SQL:指定名称查不到数据的衍伸~空格 换行符 回车符的批量处理
异常处理汇总-数据库系列 http://www.cnblogs.com/dunitian/p/4522990.html 先看看啥情况 复制查询到的数据,粘贴一下看看啥情况 那就批量处理一下~ 就这样 ...
- .Net Core MVC 网站开发(Ninesky) 2.3、项目架构调整(续)-使用配置文件动态注入
上次实现了依赖注入,但是web项目必须要引用业务逻辑层和数据存储层的实现,项目解耦并不完全:另一方面,要同时注入业务逻辑层和数据访问层,注入的服务直接写在Startup中显得非常臃肿.理想的方式是,w ...
- ASP.NET Core的路由[4]:来认识一下实现路由的RouterMiddleware中间件
虽然ASP.NET Core应用的路由是通过RouterMiddleware这个中间件来完成的,但是具体的路由解析功能都落在指定的Router对象上,不过我们依然有必要以代码实现的角度来介绍一下这个中 ...
- Openfiler配置RAC共享存储
将 Openfiler 用作 iSCSI 存储服务器,主要操作步骤如下: 1.设置 iSCSI 服务 2.配置网络访问 3.指定物理存储器并对其分区 4.创建新的卷组 5.创建所有逻辑卷 6.为每个逻 ...
- FineBI:一个简单易用的自助BI工具
过去,有关企业数据分析的重担都压在IT部门,传统BI分析更多面向的是具有IT背景的人员.但随着业务分析需求的增加,很多公司都希望为业务用户提供自助分析服务,将分析工作落实到业务人员手中.但同时,分析工 ...
- 【SAP业务模式】之ICS(四):组织单元的配置
SAP的ICS业务后台配置主要有以下几个配置点: 1.组织单元的配置(公司代码.销售组织.工厂.采购组织等): 2.主数据的部分: 3.订单和开票的定价过程: 4.开票输出类型: 5.公司间发票的配置 ...
- docker4dotnet #1 – 前世今生 & 世界你好
作为一名.NET Developer,这几年看着docker的流行实在是有些眼馋.可惜的是,Docker是基于Linux环境的,眼瞧着那些 java, python, node.js, go 甚至连p ...
- 图解Spark API
初识spark,需要对其API有熟悉的了解才能方便开发上层应用.本文用图形的方式直观表达相关API的工作特点,并提供了解新的API接口使用的方法.例子代码全部使用python实现. 1. 数据源准备 ...
- 一点公益商城开发系统模式Ring Buffer+
一个队列如果只生产不消费肯定不行的,那么如何及时消费Ring Buffer的数据呢?简单的方案就是当Ring Buffer"写满"的时候一次性将数据"消费"掉. ...
- [每日Linux]Linux下xsell和xftp的使用
实验缘由: 1.xsell在Linux下的作用就是远程登录的一个界面,也就是实现访问在Windows下访问Linux服务器的功能.之前在数据挖掘实验中因为自己电脑的内存不够,曾经使用过实验室的服务器跑 ...