Asp.Net Core 項目實戰之權限管理系統(4) 依賴注入、倉儲、服務的多項目分層實現

0 Asp.Net Core 項目實戰之權限管理系統(0) 無中生有html

1 Asp.Net Core 項目實戰之權限管理系統(1) 使用AdminLTE搭建前端前端

2 Asp.Net Core 項目實戰之權限管理系統(2) 功能及實體設計git

3 Asp.Net Core 項目實戰之權限管理系統(3) 經過EntityFramework Core使用PostgreSQLgithub

4 Asp.Net Core 項目實戰之權限管理系統(4) 依賴注入、倉儲、服務的多項目分層實現sql

5 Asp.Net Core 項目實戰之權限管理系統(5) 用戶登陸數據庫

6 Asp.Net Core 項目實戰之權限管理系統(6) 功能管理ide

7 Asp.Net Core 項目實戰之權限管理系統(7) 組織機構、角色、用戶權限函數

8 Asp.Net Core 項目實戰之權限管理系統(8) 功能菜單的動態加載學習

github源碼地址測試

0 項目結構

寫這個系列的最初目的其實只是爲了本身能更好的學習Asp.Net Core,用一個小的系統做爲練習,也督促本身。短時間內不見得在實際項目中真的會運用,但至少經過學習,大體的對Asp.Net Core有個瞭解,也是爲之後可能的應用作一下技術儲備。起初的設想很簡單,就是在一個Web項目中完成全部工做,大體瞭解Asp.Net Core的知識體系。

在實踐的過程當中不自覺的對這個練習的項目進行了一下分層。目前項目總體機構以下:

QQ圖片20160924222505

項目說明:

  • 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);
}

QQ圖片20160924225551

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);
    }
}

QQ圖片20160924231108

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", "123456");
        return View();
    }
}

運行程序,進入斷點,發現已經成功根據用戶名和密碼,把上一節建立的用戶數據信息取出,至此,咱們項目的分層之間通道已經打通。

QQ圖片20160924234550

6 總結

本節主要涉及到Asp.Net Core的知識點是它的依賴注入機制,咱們經過清晰多項目分層結構,採用依賴注入機制,實現了各通之間的鏈接。

下一節實現用戶登陸相關,主要有用戶登陸驗證,以及用戶對控制器Action路由訪問的攔截及判斷。

相關文章
相關標籤/搜索