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) 用戶登錄
github源碼地址
寫這個系列的最初目的其實只是為了自己能更好的學習Asp.Net Core,用一個小的系統作為練習,也督促自己。短期內不見得在實際項目中真的會運用,但至少通過學習,大致的對Asp.Net Core有個了解,也是為以後可能的應用做一下技術儲備。起初的設想很簡單,就是在一個Web項目中完成所有工作,大致了解Asp.Net Core的知識體系。
在實踐的過程中不自覺的對這個練習的項目進行了一下分層。目前項目整體機構如下:
/// <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 { }
如無特殊操作需要,我們的基礎接口基本上能夠滿足各類實體共性的增刪改查操作。對於某種實體特有的操作,就需要單獨進行操作接口的定義。比如後面我們要實現用戶登錄的驗證功能,即提供用戶名、密碼,驗證該用戶是否存在,以及用戶名密碼是否正確。對於此類特定需求,我們針對用戶實體定義一個用戶管理的倉儲接口。
在“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); }
/// <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) { } }
在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); } }
/// <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);
}
}
在上一節中,我們講到在Fonour.MVC項目的Startup.cs文件的ConfigureServices方法中通過使用
services.AddDbContext<FonourDbContext>(options =>options.UseNpgsql(sqlConnectionString));
方法將數據庫上上下文添加到系統服務中,正是在此時同時對數據訪問上下文進行了依賴注入實現。
通過添加以下代碼在ConfigureServices方法中添加對上面創建的倉儲及服務進行依賴注入的實現。
services.AddScoped<IUserRepository, UserRepository>(); services.AddScoped<IUserAppService, UserAppService>();
注意:Asp.Net Core提供的依賴注入擁有三種生命周期模式,由短到長依次為:
對於數據訪問上下文,我們可以通過重載方法的第二個參數,控制數據訪問上下文對象的生命周期,默認生命周期為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三個方法控制對象聲明周期。
我們在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(); } }
運行程序,進入斷點,發現已經成功根據用戶名和密碼,把上一節創建的用戶數據信息取出,至此,我們項目的分層之間通道已經打通。
本節主要涉及到Asp.Net Core的知識點是它的依賴注入機制,我們通過清晰多項目分層結構,采用依賴注入機制,實現了各通之間的連接。
下一節實現用戶登錄相關,主要有用戶登錄驗證,以及用戶對控制器Action路由訪問的攔截及判斷。