从零开始搭建前后端分离的NetCore2.2(EF Core CodeFirst+Autofac)+Vue的项目框架之二autofac解耦
在 上一篇 中将项目的基本骨架搭起来能正常跑通,这一篇将讲到,如何通过autofac将DbContext和model进行解耦,只用添加model,而不用在DbContext中添加DbSet。
在这里就不详细讲autofac是干什么用的了,简单说下autofac。
1.autofac可替换net core自带的DI IOC,用来扩展。
2.autofac可提供Aop,具体实现在博客园有很多示例。
3.autofac的几个生命周期用法:InstancePerDependency 每次都创建一个对象 ,SingleInstance 每次都是同一个对象,InstancePerLifetimeScope 同一个生命周期生成的对象是同一个。
接下来,我们需要在启动项目上通过nuget安装两个Package:Autofac
、Autofac.Extensions.DependencyInjection。
因为autofac是通过接口来进行注入的,因此我们需要创建对应的基层接口用来注入。在basic项目通过nuget安装Autofac.Extensions.DependencyInjection、,
然后中添加 Dependency 文件夹来存放基层接口,添加IOC容器接口:IIocManager,代码如下:
using System; using Autofac; using Autofac.Core; namespace DemoFrame_Basic.Dependency { /// <summary> /// IOC容器接口 /// </summary> public interface IIocManager { IContainer Container { get; } bool IsRegistered(Type serviceType, ILifetimeScope scope = null); object Resolve(Type type, ILifetimeScope scope = null); T Resolve<T>(string key = "", ILifetimeScope scope = null) where T : class; T Resolve<T>(params Parameter[] parameters) where T : class; T[] ResolveAll<T>(string key = "", ILifetimeScope scope = null); object ResolveOptional(Type serviceType, ILifetimeScope scope = null); object ResolveUnregistered(Type type, ILifetimeScope scope = null); T ResolveUnregistered<T>(ILifetimeScope scope = null) where T : class; ILifetimeScope Scope(); bool TryResolve(Type serviceType, ILifetimeScope scope, out object instance); } }
再添加一个数据库基础接口:IEntityBase
/// <summary> /// 数据库基础接口 /// </summary> public interface IEntityBase { }
IIocManager的实现类:IocManager
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.Loader; using Autofac; using Autofac.Core; using Autofac.Core.Lifetime; using Autofac.Extensions.DependencyInjection; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyModel; namespace DemoFrame_Basic.Dependency { /// <summary> /// Container manager /// </summary> public class IocManager : IIocManager { private IContainer _container; public static IocManager Instance { get { return SingletonInstance; } } private static readonly IocManager SingletonInstance = new IocManager(); /// <summary> /// Ioc容器初始化 /// </summary> /// <param name="config"></param> /// <returns></returns> public IServiceProvider Initialize(IServiceCollection services) { //.InstancePerDependency() //每次都创建一个对象 //.SingleInstance() //每次都是同一个对象 //.InstancePerLifetimeScope() //同一个生命周期生成的对象是同一个 var builder = new ContainerBuilder(); builder.RegisterInstance(Instance).As<IIocManager>().SingleInstance(); //所有程序集 和程序集下类型 var deps = DependencyContext.Default; var libs = deps.CompileLibraries.Where(lib => !lib.Serviceable && lib.Type != "package");//排除所有的系统程序集、Nuget下载包 var listAllType = new List<Type>(); foreach (var lib in libs) { try { var assembly = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(lib.Name)); listAllType.AddRange(assembly.GetTypes().Where(type => type != null)); } catch { } } //注册IEntityBase实现类 var entityBaseType = typeof(IEntityBase); var arrEntityBaseType = listAllType.Where(t => entityBaseType.IsAssignableFrom(t) && t != entityBaseType).ToArray(); builder.RegisterTypes(arrEntityBaseType) .AsImplementedInterfaces() .SingleInstance() .PropertiesAutowired(); foreach (var type in arrEntityBaseType) { if (type.IsClass && !type.IsAbstract && !type.BaseType.IsInterface && type.BaseType != typeof(object)) { builder.RegisterType(type).As(type.BaseType) .SingleInstance() .PropertiesAutowired(); } } //注册controller实现类 让Controller能被找到 var controller = typeof(ControllerBase); var arrcontrollerType = listAllType.Where(t => controller.IsAssignableFrom(t) && t != controller).ToArray(); builder.RegisterTypes(arrcontrollerType) .AsImplementedInterfaces() .SingleInstance() .PropertiesAutowired(); foreach (var type in arrcontrollerType) { if (type.IsClass && !type.IsAbstract && !type.BaseType.IsInterface && type.BaseType != typeof(object)) { builder.RegisterType(type).AsSelf(); } } builder.Populate(services); _container = builder.Build(); return new AutofacServiceProvider(_container); } /// <summary> /// Gets a container /// </summary> public virtual IContainer Container { get { return _container; } } /// <summary> /// Resolve /// </summary> /// <typeparam name="T">Type</typeparam> /// <param name="key">key</param> /// <param name="scope">Scope; pass null to automatically resolve the current scope</param> /// <returns>Resolved service</returns> public virtual T Resolve<T>(string key = "", ILifetimeScope scope = null) where T : class { if (scope == null) { //no scope specified scope = Scope(); } if (string.IsNullOrEmpty(key)) { return scope.Resolve<T>(); } return scope.ResolveKeyed<T>(key); } /// <summary> /// Resolve /// </summary> /// <typeparam name="T">Type</typeparam> /// <param name="key">key</param> /// <param name="scope">Scope; pass null to automatically resolve the current scope</param> /// <returns>Resolved service</returns> public virtual T Resolve<T>(params Parameter[] parameters) where T : class { var scope = Scope(); return scope.Resolve<T>(parameters); } /// <summary> /// Resolve /// </summary> /// <param name="type">Type</param> /// <param name="scope">Scope; pass null to automatically resolve the current scope</param> /// <returns>Resolved service</returns> public virtual object Resolve(Type type, ILifetimeScope scope = null) { if (scope == null) { //no scope specified scope = Scope(); } return scope.Resolve(type); } /// <summary> /// Resolve all /// </summary> /// <typeparam name="T">Type</typeparam> /// <param name="key">key</param> /// <param name="scope">Scope; pass null to automatically resolve the current scope</param> /// <returns>Resolved services</returns> public virtual T[] ResolveAll<T>(string key = "", ILifetimeScope scope = null) { if (scope == null) { //no scope specified scope = Scope(); } if (string.IsNullOrEmpty(key)) { return scope.Resolve<IEnumerable<T>>().ToArray(); } return scope.ResolveKeyed<IEnumerable<T>>(key).ToArray(); } /// <summary> /// Resolve unregistered service /// </summary> /// <typeparam name="T">Type</typeparam> /// <param name="scope">Scope; pass null to automatically resolve the current scope</param> /// <returns>Resolved service</returns> public virtual T ResolveUnregistered<T>(ILifetimeScope scope = null) where T : class { return ResolveUnregistered(typeof(T), scope) as T; } /// <summary> /// Resolve unregistered service /// </summary> /// <param name="type">Type</param> /// <param name="scope">Scope; pass null to automatically resolve the current scope</param> /// <returns>Resolved service</returns> public virtual object ResolveUnregistered(Type type, ILifetimeScope scope = null) { if (scope == null) { //no scope specified scope = Scope(); } var constructors = type.GetConstructors(); foreach (var constructor in constructors) { try { var parameters = constructor.GetParameters(); var parameterInstances = new List<object>(); foreach (var parameter in parameters) { var service = Resolve(parameter.ParameterType, scope); if (service == null) throw new Exception("Unknown dependency"); parameterInstances.Add(service); } return Activator.CreateInstance(type, parameterInstances.ToArray()); } catch (Exception) { } } throw new Exception("No constructor was found that had all the dependencies satisfied."); } /// <summary> /// Try to resolve srevice /// </summary> /// <param name="serviceType">Type</param> /// <param name="scope">Scope; pass null to automatically resolve the current scope</param> /// <param name="instance">Resolved service</param> /// <returns>Value indicating whether service has been successfully resolved</returns> public virtual bool TryResolve(Type serviceType, ILifetimeScope scope, out object instance) { if (scope == null) { //no scope specified scope = Scope(); } return scope.TryResolve(serviceType, out instance); } /// <summary> /// Check whether some service is registered (can be resolved) /// </summary> /// <param name="serviceType">Type</param> /// <param name="scope">Scope; pass null to automatically resolve the current scope</param> /// <returns>Result</returns> public virtual bool IsRegistered(Type serviceType, ILifetimeScope scope = null) { if (scope == null) { //no scope specified scope = Scope(); } return scope.IsRegistered(serviceType); } /// <summary> /// Resolve optional /// </summary> /// <param name="serviceType">Type</param> /// <param name="scope">Scope; pass null to automatically resolve the current scope</param> /// <returns>Resolved service</returns> public virtual object ResolveOptional(Type serviceType, ILifetimeScope scope = null) { if (scope == null) { //no scope specified scope = Scope(); } return scope.ResolveOptional(serviceType); } /// <summary> /// Get current scope /// </summary> /// <returns>Scope</returns> public virtual ILifetimeScope Scope() { try { //when such lifetime scope is returned, you should be sure that it'll be disposed once used (e.g. in schedule tasks) return Container.BeginLifetimeScope(); } catch (Exception) { //we can get an exception here if RequestLifetimeScope is already disposed //for example, requested in or after "Application_EndRequest" handler //but note that usually it should never happen //when such lifetime scope is returned, you should be sure that it'll be disposed once used (e.g. in schedule tasks) return Container.BeginLifetimeScope(MatchingScopeLifetimeTags.RequestLifetimeScopeTag); } } } }
在这里添加完以后,我们需要将自带的DI容器给替换成现在使用的autofac,
在启动项目的Startup文件中更改,最终代码如下:
public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>()); services.AddDbContext<DemoDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("SqlServerConnection"))); return IocManager.Instance.Initialize(services); }
为了方便使用,在CoreMvc项目中添加DemoWeb的类来存放一些系统数据:
using DemoFrame_Basic.Dependency; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using System.Linq; namespace DemoFrame_CoreMvc { public class DemoWeb { private static IHttpContextAccessor _httpContextAccessor; /// <summary> /// Configure /// </summary> /// <param name="httpContextAccessor"></param> public static void Configure(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } /// <summary> /// 当前请求HttpContext /// </summary> public static HttpContext HttpContext { get => _httpContextAccessor.HttpContext; set => _httpContextAccessor.HttpContext = value; } /// <summary> /// IocManager /// </summary> public static IIocManager IocManager { get; set; } /// <summary> /// Environment /// </summary> public static IHostingEnvironment Environment { get; set; } /// <summary> /// Configuration /// </summary> public static IConfiguration Configuration { get; set; } /// <summary> /// MemoryCache /// </summary> public static IMemoryCache MemoryCache { get; set; } /// <summary> /// 获取当前请求客户端IP /// </summary> /// <returns></returns> public static string GetClientIp() { var ip = HttpContext.Request.Headers["X-Forwarded-For"].FirstOrDefault()?.Split(',')[0].Trim(); if (string.IsNullOrEmpty(ip)) { ip = HttpContext.Connection.RemoteIpAddress.ToString(); } return ip; } } }
Startup的完整代码如下:
public class Startup { public Startup(IConfiguration configuration) { DemoWeb.Configuration = configuration; Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>()); services.AddDbContext<DemoDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("SqlServerConnection"))); return IocManager.Instance.Initialize(services); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { DemoWeb.IocManager = app.ApplicationServices.GetService<IIocManager>(); DemoWeb.Environment = env; try//注意这里在本地开发允许时会重置数据库,并清空所有数据,如不需要请注释 { if (env.IsDevelopment()) { using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>() .CreateScope()) { var dbContent = serviceScope.ServiceProvider.GetService<DemoDbContext>(); //CheckMigrations(dbContent); var database = serviceScope.ServiceProvider.GetService<DemoDbContext>().Database; database.EnsureDeleted(); database.EnsureCreated(); } } } catch (Exception ex) { //LogHelper.Logger.Error(ex, "Failed to migrate or seed database"); } DemoWeb.Configure(app.ApplicationServices.GetRequiredService<IHttpContextAccessor>()); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseHsts(); } app.UseCors(builder => builder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod().AllowCredentials());//允许跨域 app.UseHttpsRedirection(); app.UseMvc(); } }
在这么多的配置都完成了的情况下,我们该去实现mode与DbContext的解耦操作了。那么该如何做呢?
废话不多说了,直接上代码,在数据库上下文DemoDbContext中将之前的DbSet删掉,更改如下:
!!!先将之前的model都继承 IEntityBase 接口。这样在模型生成时才能生成到数据库!!!
/// <summary> /// 数据库上下文 /// </summary> public class DemoDbContext : DbContext { public DemoDbContext(DbContextOptions<DemoDbContext> options) : base(options) { } #region IOC得到所有实体 private readonly IEntityBase[] _entitys = DemoWeb.IocManager.ResolveAll<IEntityBase>(); #endregion protected override void OnModelCreating(ModelBuilder modelBuilder) { if (_entitys == null) { return; } foreach (var entity in _entitys) { modelBuilder.Model.AddEntityType(entity.GetType()); } } }
在使用数据库上下文是,使用Set<T>方法设置需要使用的的类
在下一篇中将介绍如何使用基类controller来统一前后端交互数据,统一使用一个模型进行返回。
有需要源码的可通过此 GitHub 链接拉取 觉得还可以的给个 start 哦,谢谢!
学习本是一个不断模仿、练习、创新、超越的过程。 由于博主能力有限,文中可能存在描述不正确,欢迎指正、补充! 感谢您的阅读,麻烦动动手指点个推荐哟。
-------------------------------------------------------------------------------------------------------------------------------------------------------------