上次實現了依賴註入,但是web項目必須要引用業務邏輯層和數據存儲層的實現,項目解耦並不完全;另一方面,要同時註入業務邏輯層和數據訪問層,註入的服務直接寫在Startup中顯得非常臃腫。理想的方式是,web項目近引用介面而不引用實現,在配置文件中進行配置實現程式集合類,註入業務邏輯層而不必註入數據訪問 ...
上次實現了依賴註入,但是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); } }View Code
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; } } }View Code
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); } }View Code
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(); } } }View Code
三、實現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; } } }View Code
然後在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; } } }View Code
添加配置文件
在Web項目中添加配置文件service.json
{ "AssemblyCollections": [ { "ServiceAssembly": "Ninesky.InterfaceBase", "ImplementationAssembly": "Ninesky.Base.dll", "DICollections": [ { "ServiceType": "Ninesky.InterfaceBase.InterfaceCategoryService", "ImplementationType": "Ninesky.Base.CategoryService", "LifeTime": "Scoped" } ] } ] }View Code
可以看到配置文件的鍵值對於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>>();View Code
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)); } }View Code
代碼中可以看到載入介面程式集使用的方法是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