• .Net Core MVC 网站开发(Ninesky) 2.3、项目架构调整(续)-使用配置文件动态注入


    上次实现了依赖注入,但是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的完整路径。

    到这里就完整实现了解耦,现在项目结构看起来是这样子

    image

    解耦后有些麻烦的是修改Base项目的代码后运行项目会出错,必须生成项目后将Base项目生成的Ninesky.Base.dll和Ninesky.Base.pdb复制到Web项目的binDebug etcoreapp1.1目录下才能正常运行。

    F5运行一下可以看到正常读出了数据。

    image

    四、其他

     

    代码托管地址:https://git.oschina.net/ninesky/Ninesky

    文章发布地址:http://www.ninesky.cn

                     http://mzwhj.cnblogs.com/

    代码包下载:Ninesky2.3、项目架构调整(续)-使用配置文件动态注入.rar

    返回目录

  • 相关阅读:
    npm version patch
    nginx 操作
    基于 Vue CLI 组件库封装,按需加载实现
    nginx 配置文件路径获取
    Laravel 生产资源路由并指定模型
    base.js,通用js方法,Js方法封装
    jquery.params.js,Jquery获取页面参数,js获取页面参数
    layui使用,LayUI select不显示,LayUI文件上传,Layui自定义校验规则
    Layer弹窗消息封装,Layer消息提示封装,Layer使用
    Html跨域js封装,前端页面跨域js,postMessage实现跨域
  • 原文地址:https://www.cnblogs.com/mzwhj/p/6224237.html
Copyright © 2020-2023  润新知