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

    }
}

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

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

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

三、实现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; }
    }
}

然后在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; }
    }
}

添加配置文件

在Web项目中添加配置文件service.json

{
  "AssemblyCollections": [
    {
      "ServiceAssembly": "Ninesky.InterfaceBase",
      "ImplementationAssembly": "Ninesky.Base.dll",
      "DICollections": [
        {
          "ServiceType": "Ninesky.InterfaceBase.InterfaceCategoryService",
          "ImplementationType": "Ninesky.Base.CategoryService",
          "LifeTime": "Scoped"
        }
      ]
    }
  ]
}

可以看到配置文件的键值对于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>>();

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

代码中可以看到加载接口程序集使用的方法是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

http://mzwhj.cnblogs.com/

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

返回目录

时间: 12-25

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

.Net Core MVC 网站开发(Ninesky) 2.3、项目架构调整-控制反转和依赖注入的使用

再次调整项目架构是因为和群友dezhou的一次聊天,我原来的想法是项目尽量做简单点别搞太复杂了,仅使用了DbContext的注入,其他的也没有写接口耦合度很高.和dezhou聊过之后我仔细考虑了一下,还是解耦吧,本来按照软件设计模式就应该是高内聚低耦合的,低耦合使项目的模块独立于其他模块,增加了可维护性和移植性! 注:前面写的博客详细记录没项目操作的每一步,其实写起博客来很费时间,而且整片博文里很多无用的信息.对MVC来说会添加控制器,添加视图,添加类这些都最基本的要求了,并且前面博文里都写了,

.Net Core MVC 网站开发(Ninesky) 2.2、栏目管理功能-System区域添加

在asp或asp.net中为了方便网站的结构清晰,通常把具有类似功能的页面放到一个文件夹中,用户管理功能都放在Admin文件夹下,用户功能都放在Member文件夹下,在MVC中,通常使用区域(Areas)来组织,在.Net Core MVC 之前的MVC版本,区域都包含在Areas文件夹下,.Net Core MVC 与以前的版本还是有所不同,固定位置的限制,控制器和视图等都可以放置在任何地方,只要在控制器上注明区域名称就可以了. 创建区域的方式如下: 1.在项目的Startup.cs文件中注册

ASP.NET MVC 网站开发总结(三) ——图片截图上传

本着简洁直接,我们就直奔主题吧,这里需要使用到一个网页在线截图插件imgareaselect(请自行下载). 前台页面: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="css/imgareaselect-default.css" /> </head> <

ASP.NET MVC 网站开发总结(一)

历经一个多月的努力,工作室的门户网站终于结束了内部公测. 仔细算一下,大概把网站开发出1.0版本大概用了一个月的时间(主要是利用课余时间),其后大概用了十几天来测试.完善.改进,最终定型.感觉最有难度的既不是后台代码的编写,也不是前台页面的编写,而是将不同人编写的前台和后台很好的拼接起来(当然若是前台和后台都是同一个人做,当然不存在这个问题^_^).这也是自己第一次与工作室的伙伴共同完成一个网站.简单的总结一下,团队开发网站需要注意的地方以及收获吧!(其中我主要的任务是后台代码编写以及后台与前台

ASP.NET MVC 网站开发总结(六)——简谈Json的序列化与反序列化

首先,先简单的谈一下什么是序列化与反序列化,序列化 (Serialization)将对象的状态信息转换为可以存储或传输的形式的过程.在序列化期间,对象将其当前状态写入到临时或持久性存储区.以后,可以通过从存储区中读取或反序列化(Deserialization)对象的状态,重新创建该对象. 一般我们把Json的序列化与反序列化用在哪些地方呢?对于网站开发来说,一般我们会用Json来进行前台和后台的数据传递(常与Ajax一起使用),而这个过程就涉及到了Json的序列化与反序列化. 那么我们就来简单看

ASP.NET MVC 网站开发总结(四)——校友平台开发总结

又历经一个多月的努力,学校的一个校友平台项目也接近内测的尾声了,简单的总结一下这次的项目开发. 与上次做WingStudio工作室的门户网站相比,同样是团队开发,参与的人员多了一个,用的时间也差不多一个月,但从总体做的情况来看,这次比上一次要做的好得多(不论是页面的样式还是后台的模块化封装),这是进步也是必然,这次自己的任务仍旧是负责后台以及后台与前台的拼接. 总的来说,用ASP.NET MVC开发网站的效率是相当高的(而且也相当的好部署),特别是针对后台的开发,只要前期对页面的功能分析到位,立

从svn上检出威尼斯人网站开发多模块maven项目

一.SVN上Maven多威尼斯人网站开发模块项目结构 haozbbs.comQ1446595067 使用eclipse导入SVN上的Maven多模块项目 Maven多模块项目所在SVN目录 二.eclipse通过SVN导入到工作空间 工作空间位于F:/HPCWorkspace 2.1 File->Import,选择从SVN检出项目下载 2.2 选择/新建SVN资源库位置 如果资源库还没创建好,选择创建新的资源库位置,如果已经创建好资源库了,那么选择使用现有的资源库位置下载 不存在的话新建 存在的

Ubuntu运行DOTNET Core MVC(基础,非部署项目)

一. 配置 Ubuntu18.04.3 LTS DOTNET Core3.0 SDK 1.更新 sudo apt-get update 二.安装DOTNET Core SDK 1.首先注册微软密钥以及所需依赖 wget -q https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb sudo dpkg -i packages-micro

ASP.NET MVC 网站开发总结(五)——Ajax异步提交表单之检查验证码

首先提出一个问题:在做网站开发的时候,用到了验证码来防止恶意提交表单,那么要如何实现当验证码错误时,只是刷新一下验证码,而其它填写的信息不改变? 先说一下为什么有这个需求:以提交注册信息页面为例,一般注册都需要用户填一个验证码信息(防止机器恶意注册),并且这个验证码会提交到后台去进行比对,若是错了则不会检查其他提交信息而直接返回浏览器端提示验证码错误.若是简单地用form表单直接将数据提交到指定的url,当验证码填写错误的信息返回浏览器端的时候,不可避免整个页面都会重新刷新一次,这是用户所不想要