.Net Core MVC 網站開發(Ninesky) 2.三、項目架構調整(續)-使用配置文件動態注入

上次實現了依賴注入,可是web項目必需要引用業務邏輯層和數據存儲層的實現,項目解耦並不徹底;另外一方面,要同時注入業務邏輯層和數據訪問層,注入的服務直接寫在Startup中顯得很是臃腫。理想的方式是,web項目近引用接口而不引用實現,在配置文件中進行配置實現程序集合類,注入業務邏輯層而沒必要注入數據訪問層。html

1、數據訪問層

在項目中摒棄數據訪問層或者使用EntityFramework做爲數據訪問層。git

在項目中數據訪問層主要實現數據的存儲,仔細看一下EntityFramework發現DbContext的功能徹底實現了查、增、刪、改等各類操做,而且有緩存等功能,自己就實現了倉儲模式,而且比本身封裝的數據存儲層的功能還強大,乾脆在項目中用EntityFramework做爲數據存儲層。刪除掉Ninesky.InterfaceDataLibrary項目和Ninesky.DataLibrary項目。web

注:項目結構調整的確實太頻繁了,之後一段時間內毫不再調整了。數據庫

2、實現業務邏輯層。

添加業務邏輯層接口項目Ninesky.InterfaceBasejson

一、添加接口基類接口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

二、在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

三、在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

四、在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

3、實現dll動態加載和注入

要在web項目中對實現類進行解耦和注入,那麼項目只能對接口進行依賴,解除對實現的依賴,而後在配置文件中配置實現的程序集和注入的服務,在Startup類中讀取配置文件並加載程序集,而後實現接口的注入。數組

一、解除實現類依賴

在Web項目中添加對Ninesky.InterfaceBase項目的引用,解除對Ninesky.Base項目的引用。緩存

二、實現注入的配置文件

首先在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項目中實現注入須要加載的程序集配置項 AssemblyItemapp

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

添加配置文件ide

在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

三、進行注入

在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的完整路徑。

 

 

到這裏就完整實現瞭解耦,如今項目結構看起來是這樣子

image

解耦後有些麻煩的是修改Base項目的代碼後運行項目會出錯,必須生成項目後將Base項目生成的Ninesky.Base.dll和Ninesky.Base.pdb複製到Web項目的bin\Debug\netcoreapp1.1目錄下才能正常運行。

F5運行一下能夠看到正常讀出了數據。

image

 

4、其餘

 

代碼託管地址:https://git.oschina.net/ninesky/Ninesky

文章發佈地址:http://www.ninesky.cn

                 http://mzwhj.cnblogs.com/

代碼包下載:Ninesky2.三、項目架構調整(續)-使用配置文件動態注入.rar

 

返回目錄

相關文章
相關標籤/搜索