Ioc (Inversion of Control, 控制反转) 把创建对象的操作交给框架, 亦被称为 DI(Dependency Injection, 依赖注入).
为什么叫做 "控制反转" 呢? 之前, 我们想要一个对象都是 new 出来的, 天天需要 new 对象是不是感觉有点麻烦. 有人就想到了, 把这些简单重复的工作也交给框架做. 本来需要我们向框架 "射入" 对象, 现在框架自己能产生对象了, 这不正是 控制反转 吗? 于是, 就有了这个响亮的名字.
本文不做具体概念讲解 , 项目采用 Autofac 作为基础框架
关于 Autofac 的基础用法可以参照官方的文档教程 很详细 很具体 针对各种版本都有说明 (不要去看各种入门教程 或者翻译文档 全是瞎扯淡) https://autofaccn.readthedocs.io/zh/latest/
熟悉 Ioc 的都应该很清楚 我们常用的操作主要就是两个 Resolver 和 Registrar
Registrar:
随着项目的逐渐增大, 我们基本都采用模块化的方式即 Module Autofac 已经提供了一个基础的 Module 我们可以在其内部里面重写 Load 方法即可, 但是考虑以后可能还需要做其他扩展所以我们还是提供一个 IRegistrar 接口备用
参照 Load 方法 我们只提供一个 ContainerBuilder
- protected override void Load(ContainerBuilder builder)
- Resolver:
Autofac 已经帮我们实现很多场景下的自动 Resolver, 但是具体的业务情况却是我们可能需要在自己任意想要的地方去 Resolver 所以我们需要自己来实现个 IResolver
- public interface IResolver
- {
- /// <summary>
- /// Resolves this instance.
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <returns></returns>
- T Resolve<T>(ILifetimeScope scope = null);
- /// <summary>
- /// Determines whether this instance is registered.
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <returns>
- /// <c>true</c> if this instance is registered; otherwise, <c>false</c>.
- /// </returns>
- bool IsRegistered<T>() where T : class;
- /// <summary>
- /// Determines whether the specified type is registered.
- /// </summary>
- /// <param name="type">The type.</param>
- /// <param name="scope">The ILifetimeScope</param>
- /// <returns>
- /// <c>true</c> if the specified type is registered; otherwise, <c>false</c>.
- /// </returns>
- bool IsRegistered(Type type, ILifetimeScope scope = null);
- /// <summary>
- /// Releases a pre-resolved object. See Resolve methods.
- /// </summary>
- /// <param name="obj">Object to be released</param>
- void Release(object obj);
- /// <summary>
- /// Resolve
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="parameters"></param>
- /// <param name="scope"></param>
- /// <returns></returns>
- T Resolve<T>(IEnumerable<Parameter> parameters, ILifetimeScope scope = null);
- /// <summary>
- /// Resolve
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="parameters"></param>
- /// <returns></returns>
- T ResolveParameter<T>(params Parameter[] parameters);
- /// <summary>
- /// Resolve
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <returns></returns>
- T ResolveName<T>(string name);
- /// <summary>
- /// Resolve
- /// </summary>
- /// <returns></returns>
- object Resolve(Type type);
- }
- View Code
- -------------------------------------------------------------------------------------------------------------------------------------
然后 在. net core 中已经内置了 Ioc 基本代码如下
- /// <summary>
- /// ConfigureServices
- /// </summary>
- /// <param name="services"></param>
- /// <returns></returns>
- public IServiceProvider ConfigureServices(IServiceCollection services)
- {
- }
- View Code
我们可以知道 .net core 内置的 Ioc 是以 IServiceCollection 为核心, 所以 如果我们需要支持. net core 版本则需要 IServiceCollection 所以我们提供一个 IServiceCollectionResolve
IServiceCollectionResolve
- public interface IServiceCollectionResolve
- {
- IServiceCollection ServiceCollection { get; set; }
- T ResolveServiceValue<T>() where T : class, new();
- }
- View Code
Autofac 的核心在于 IContainer 所以我们提供一个 IIocManager
IIocManager
- public interface IIocManager : IResolver, IRegistrar, IServiceCollectionResolve
- {
- /// <summary>
- /// Reference to the Autofac Container.
- /// </summary>
- IContainer IocContainer { get; set; }
- /// <summary>
- /// ServiceLocatorCurrent
- /// </summary>
- IServiceLocator ServiceLocatorCurrent { get; set; }
- /// <summary>
- /// SetContainer
- /// </summary>
- /// <param name="containerBuilder"></param>
- void SetContainer(ContainerBuilder containerBuilder);
- /// <summary>
- /// SetServiceCollection
- /// </summary>
- /// <param name="serviceCollection"></param>
- void SetServiceCollection(IServiceCollection serviceCollection);
- /// <summary>
- /// UpdateContainer
- /// </summary>
- /// <param name="containerBuilder"></param>
- [Obsolete("Containers should generally be considered immutable. Register all of your dependencies before building/resolving. If you need to change the contents of a container, you technically should rebuild the container. This method may be removed in a future major release.")]
- void UpdateContainer(ContainerBuilder containerBuilder);
- }
- View Code
说明:
IServiceLocator 来源于 CommonServiceLocator 可以在 nuget 找到 可以理解为 IResolver
UpdateContainer 官方已经不推荐使用 可以使用但是尽量避免使用 主要适用场景是 : 已经初始化完成后 再需要进行二次注册等操作
至此我们所需要的接口基本定义完成.
我们需要一个实现 即 IocManager
- /// <summary>
- /// IocManager
- /// </summary>
- public class IocManager : IIocManager
- {
- /// <summary>
- /// The Singleton instance.
- /// </summary>
- public static IocManager Instance { get; }
- #region ContainerBuilder
- /// <summary>
- /// ContainerBuilder
- /// </summary>
- ContainerBuilder IRegistrar.ContainerBuilder
- {
- get => ContainerBuilder;
- set => ContainerBuilder = value;
- }
- /// <summary>
- /// ContainerBuilder
- /// </summary>
- public static ContainerBuilder ContainerBuilder { get; set; }
- #endregion
- #region IContainer
- /// <summary>
- /// IocContainer
- /// </summary>
- IContainer IIocManager.IocContainer
- {
- get => IocContainer;
- set => IocContainer = value;
- }
- /// <summary>
- /// IocContainer
- /// </summary>
- public static IContainer IocContainer { get; set; }
- #endregion
- #region IServiceLocator
- IServiceLocator IIocManager.ServiceLocatorCurrent
- {
- get => ServiceLocatorCurrent;
- set => ServiceLocatorCurrent = value;
- }
- /// <summary>
- /// ServiceLocator
- /// </summary>
- public static IServiceLocator ServiceLocatorCurrent { get; set; }
- #endregion
- #region IServiceCollection
- IServiceCollection IServiceCollectionResolve.ServiceCollection
- {
- get => ServiceCollection;
- set => ServiceCollection = value;
- }
- /// <summary>
- /// ServiceCollection
- /// </summary>
- public static IServiceCollection ServiceCollection { get; set; }
- #endregion
- /// <summary>
- /// IocManager
- /// </summary>
- static IocManager()
- {
- Instance = new IocManager();
- }
- /// <summary>
- /// SetContainer
- /// </summary>
- /// <param name="containerBuilder"></param>
- public void SetContainer(ContainerBuilder containerBuilder)
- {
- ContainerBuilder = containerBuilder;
- var container = containerBuilder.Build();
- IocContainer = container;
- // 设置定位器
- ServiceLocatorCurrent = new AutofacServiceLocator(IocContainer);
- }
- /// <summary>
- /// SetServiceCollection
- /// </summary>
- /// <param name="serviceCollection"></param>
- public void SetServiceCollection(IServiceCollection serviceCollection)
- {
- ServiceCollection = serviceCollection;
- }
- /// <summary>
- /// UpdateContainer
- /// </summary>
- /// <param name="containerBuilder"></param>
- [Obsolete("Containers should generally be considered immutable. Register all of your dependencies before building/resolving. If you need to change the contents of a container, you technically should rebuild the container. This method may be removed in a future major release.")]
- public void UpdateContainer(ContainerBuilder containerBuilder)
- {
- ContainerBuilder = containerBuilder;
- containerBuilder?.Update(IocContainer);
- }
- /// <summary>
- /// resolve T by lifetime scope
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="scope"></param>
- /// <returns></returns>
- public T Resolve<T>(ILifetimeScope scope = null)
- {
- if (scope == null)
- {
- scope = Scope();
- }
- return scope.Resolve<T>();
- }
- /// <summary>
- /// Resolve
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="parameters"></param>
- /// <param name="scope"></param>
- /// <returns></returns>
- public T Resolve<T>(IEnumerable<Parameter> parameters, ILifetimeScope scope = null)
- {
- if (scope == null)
- {
- scope = Scope();
- }
- return scope.Resolve<T>(parameters);
- }
- /// <summary>
- /// Resolve
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="parameters"></param>
- /// <returns></returns>
- public T ResolveParameter<T>(Parameter[] parameters)
- {
- var scope = Scope();
- return scope.Resolve<T>(parameters);
- }
- /// <summary>
- /// ResolveName
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <returns></returns>
- public T ResolveName<T>(string name)
- {
- var scope = Scope();
- var item = scope.ResolveNamed<T>(name);
- return item;
- }
- /// <summary>
- ///
- /// </summary>
- /// <param name="type"></param>
- /// <returns></returns>
- public object Resolve(Type type)
- {
- var scope = Scope();
- var item = scope.Resolve(type);
- return item;
- }
- /// <summary>
- /// IsRegistered
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <returns></returns>
- public bool IsRegistered<T>() where T : class
- {
- return IsRegistered(typeof(T));
- }
- /// <summary>
- /// IsRegistered
- /// </summary>
- /// <param name="type"></param>
- /// <param name="scope"></param>
- /// <returns></returns>
- public bool IsRegistered(Type type, ILifetimeScope scope = null)
- {
- if (scope == null)
- {
- scope = Scope();
- }
- return scope.IsRegistered(type);
- }
- /// <summary>
- /// release object lifetimescope
- /// </summary>
- /// <param name="obj"></param>
- public void Release(object obj)
- {
- }
- /// <summary>
- /// create ILifetimeScope from container
- /// </summary>
- /// <returns></returns>
- private static ILifetimeScope Scope()
- {
- return IocContainer.BeginLifetimeScope();
- }
- /// <summary>
- /// ResolveServiceValue
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <returns></returns>
- public T ResolveServiceValue<T>() where T : class, new()
- {
- return ServiceCollection.ResolveServiceValue<T>();
- }
- }
- View Code
说明:
主要依赖于:
这些全部完成后 我们需要一个最终的装载程序 Bootstrap
- /// <summary>
- /// 初始化装载程序
- /// </summary>
- public class Bootstrap
- {
- /// <summary>
- /// _isInit
- /// </summary>
- private static bool _isInit;
- /// <summary>
- /// _iocManager
- /// </summary>
- public IIocManager IocManager { get; set; }
- /// <summary>
- /// StartupModule
- /// </summary>
- public Type StartupModule { get; set; }
- /// <summary>
- /// Instance
- /// </summary>
- /// <returns></returns>
- public static Bootstrap Instance<TStartupModule>() where TStartupModule : WorkDataBaseModule
- {
- return new Bootstrap(typeof(TStartupModule));
- }
- /// <summary>
- /// instance bootstrap
- /// </summary>
- /// <returns></returns>
- public static Bootstrap Instance()
- {
- return new Bootstrap();
- }
- /// <summary>
- /// Bootstrap
- /// </summary>
- public Bootstrap() : this(Dependency.IocManager.Instance)
- {
- }
- /// <summary>
- /// Bootstrap
- /// </summary>
- public Bootstrap(Type startupModule) : this(startupModule, Dependency.IocManager.Instance)
- {
- }
- /// <summary>
- /// Bootstrap
- /// </summary>
- /// <param name="iocManager"></param>
- public Bootstrap(IIocManager iocManager)
- {
- IocManager = iocManager;
- }
- /// <summary>
- /// Bootstrap
- /// </summary>
- /// <param name="startupModule"></param>
- /// <param name="iocManager"></param>
- public Bootstrap(Type startupModule, IIocManager iocManager)
- {
- StartupModule = startupModule;
- IocManager = iocManager;
- }
- /// <summary>
- /// 初始化集成框架 (配置方式)
- /// </summary>
- [STAThread]
- public void InitiateConfig(IServiceCollection services, List<string> paths)
- {
- if (_isInit) return;
- var builder = new ContainerBuilder();
- #region RegisterConfig
- var config = new ConfigurationBuilder();
- config.SetBasePath(AppDomain.CurrentDomain.BaseDirectory);
- if (paths != null)
- {
- foreach (var item in paths)
- {
- config.AddJsonFile(item);
- }
- }
- var module = new ConfigurationModule(config.Build());
- builder.RegisterModule(module);
- #endregion
- // 注入初始 module
- builder.RegisterModule(new WorkDataModule());
- IocManager.SetServiceCollection(services);
- builder.Populate(services);
- IocManager.SetContainer(builder);
- _isInit = true;
- }
- /// <summary>
- /// InitiateConfig
- /// </summary>
- /// <param name="paths"></param>
- public void InitiateConfig(List<string> paths)
- {
- if (_isInit) return;
- var builder = new ContainerBuilder();
- #region RegisterConfig
- var config = new ConfigurationBuilder();
- config.SetBasePath(AppDomain.CurrentDomain.BaseDirectory);
- if (paths != null)
- {
- foreach (var item in paths)
- {
- config.AddJsonFile(item);
- }
- }
- var module = new ConfigurationModule(config.Build());
- builder.RegisterModule(module);
- #endregion
- // 注入初始 module
- builder.RegisterModule(new WorkDataModule());
- IocManager.SetContainer(builder);
- _isInit = true;
- }
- /// <summary>
- /// UpdateContainer
- /// </summary>
- /// <param name="services"></param>
- [Obsolete("Containers should generally be considered immutable. Register all of your dependencies before building/resolving. If you need to change the contents of a container, you technically should rebuild the container. This method may be removed in a future major release.")]
- public void CoreUpdateContainer(IServiceCollection services)
- {
- var builder = new ContainerBuilder();
- builder.Populate(services);
- IocManager.UpdateContainer(builder);
- }
- }
- View Code
这样我们整体的架子就算初步完成了
扩展
1. 随着项目逐渐增大 我们会有很多很多的接口 去进行注入 我们 希望通过反射的方式进行注入 所以我们提供一个 ITypeFinder 方便进行操作
注: ITypeFinder 来源于 nopcommerce
代码位置 :
https://github.com/wulaiwei/WorkData.Core/tree/master/WorkData/WorkData/Extensions/TypeFinders
2. 在 .net Framework autofac 针对常量的配置 我们可以采用属性注入的方式完成 , 但是针对. net core 版本 不推荐采用 属性注入 , 推荐使用 core 自带的文件注入方式
- public Startup(IHostingEnvironment env)
- {
- var builder = new ConfigurationBuilder()
- .SetBasePath(env.ContentRootPath)
- .AddJsonFile("Config/appsettings.json", optional: true, reloadOnChange: true)
- .AddJsonFile($"Config/appsettings.{env.EnvironmentName}.json", optional: true)
- .AddEnvironmentVariables();
- this.Configuration = builder.Build();
- }
- View Code
有个参数选项 为 reloadOnChange: true 即修改后会自动重新加载 , 然后注入至 IServiceCollection 既可以
我们可以采用 IocManager 进行 Resolve 但是你就立马会遇到很尴尬的问题 加入我还没注入完成 我就需要这个对象呢
例如 加入我需要使用 JWT
- services.AddAuthentication(options =>
- {
- // 认证 middleware 配置
- options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
- options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
- })
- .AddJwtBearer(o =>
- {
- // 主要是 jwt token 参数设置
- o.TokenValidationParameters = new TokenValidationParameters
- {
- //Token 颁发机构
- ValidIssuer = workDataBaseJwt.Issuer,
- // 颁发给谁
- ValidAudience = workDataBaseJwt.Audience,
- // 这里的 key 要进行加密, 需要引用 Microsoft.IdentityModel.Tokens
- IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(workDataBaseJwt.SecretKey)),
- //ValidateIssuerSigningKey=true,
- //// 是否验证 Token 有效期, 使用当前时间与 Token 的 Claims 中的 NotBefore 和 Expires 对比
- ValidateLifetime = true,
- //// 允许的服务器时间偏移量
- ClockSkew = TimeSpan.Zero
- };
- });
- View Code
然后就需要这个对象 workDataBaseJwt 所以 我们这边需要对 IServiceCollection 做个扩展
- public static class WorkDataServiceCollection
- {
- public static T ResolveServiceValue<T>(this IServiceCollection services) where T : class, new()
- {
- try
- {
- var provider = services.BuildServiceProvider();
- var entity = provider.GetRequiredService<IOptions<T>>().Value;
- return entity;
- }
- catch (Exception)
- {
- return default(T);
- }
- }
- }
- View Code
这样我就可以在注入之前进行 Resolve 即:
- services.Configure<WorkDataBaseJwt>(Configuration.GetSection("WorkDataBaseJwt"));
- services.Configure<WorkDataDbConfig>(Configuration.GetSection("WorkDataDbContextConfig"));
- services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
- services.AddTransient<IPrincipal>(provider =>
- provider.GetService<IHttpContextAccessor>().HttpContext.User);
- var workDataBaseJwt = services.ResolveServiceValue<WorkDataBaseJwt>();
- View Code
最后推荐使用 json 进行模块注册
- {
- "modules": [
- {
- "type": "WorkData.Web.WorkDataWebModule,WorkData.Web"
- },
- {
- "type": "WorkData.Domain.EntityFramework.DomainEntityFrameworkModule,WorkData.Domain.EntityFramework"
- },
- {
- "type": "WorkData.EntityFramework.EntityFrameworkModule,WorkData.EntityFramework"
- },
- {
- "type": "WorkData.Code.WorkDataCodeModule,WorkData.Code"
- }
- ]
- }
- View Code
关于在. net core 版本下的完整使用 可以参考 :
https://github.com/wulaiwei/WorkData.Core/tree/master/WorkData/WorkData.Web
最后针对 Nancy 下 WorkData 的使用
主要依赖于
- using Autofac;
- using Nancy;
- using Nancy.Bootstrapper;
- using Nancy.Bootstrappers.Autofac;
- public class WorkDataAutofacNancyBootstrapper : AutofacNancyBootstrapper
- {
- /// <summary>
- /// Gets a reference to the <see cref="Bootstrap" /> instance.
- /// </summary>
- public static Bootstrap BootstrapWarpper { get; } = Bootstrap.Instance();
- private readonly ILogService _logService;
- static WorkDataAutofacNancyBootstrapper()
- {
- BootstrapWarpper.InitiateConfig();
- }
- public WorkDataAutofacNancyBootstrapper()
- {
- _logService = BootstrapWarpper.IocManager.Resolve<ILogService>();
- }
- protected override ILifetimeScope GetApplicationContainer()
- {
- return BootstrapWarpper.IocManager.IocContainer;
- }
- protected override void ConfigureApplicationContainer(ILifetimeScope container)
- {
- var builder = new ContainerBuilder();
- builder.RegisterType<CustomJsonNetSerializer>().As<ISerializer>();
- builder.RegisterType<UserMapper>().As<IUserMapper>();
- BootstrapWarpper.IocManager.UpdateContainer(builder);
- }
- protected override void RequestStartup(ILifetimeScope container, IPipelines pipelines, NancyContext context)
- {
- base.RequestStartup(container, pipelines, context);
- #region 拦截器
- pipelines.BeforeRequest += ctx =>
- {
- var logRequest = new LogRequest
- {
- Url = context.Request.Url,
- Form = JsonConvert.SerializeObject(context.Request.Form),
- Query = JsonConvert.SerializeObject(context.Request.Query),
- Method = context.Request.Method,
- Body = context.Request.Body.AsString(),
- Key = Guid.NewGuid().ToString(),
- CreateTime = DateTime.Now,
- CreateUserId = ctx.GetUserIdentity()?.UserId
- };
- _logService.AddRequestIndex(logRequest);
- return null;
- };
- pipelines.AfterRequest += ctx => { };
- pipelines.OnError += (ctx, ex) =>
- {
- var logRequestError = new LogRequestError
- {
- Url = context.Request.Url,
- ErrorMessage = ex.Message,
- Key = Guid.NewGuid().ToString(),
- CreateTime=DateTime.Now,
- CreateUserId= ctx.GetUserIdentity()?.UserId
- };
- _logService.AddRequestErrorIndex(logRequestError);
- return null;
- };
- #endregion
- }
- protected override void ApplicationStartup(ILifetimeScope container, IPipelines pipelines)
- {
- base.ApplicationStartup(container, pipelines);
- DiagnosticsHook.Disable(pipelines);
- pipelines.AfterRequest += ctx =>
- {
- ctx.Response.Headers.Add("Access-Control-Allow-Origin", "*");
- ctx.Response.Headers.Add("Access-Control-Allow-Credentials", "true");
- ctx.Response.Headers.Add("Access-Control-Allow-Methods", "POST,GET");
- ctx.Response.Headers.Add("Access-Control-Allow-Headers", "Content-Type,Access-Token");
- ctx.Response.Headers.Add("Access-Control-Expose-Headers", "*");
- };
- #region Authentication
- var configuration = new StatelessAuthenticationConfiguration(
- nancyContext =>
- {
- // 返回 null 代码 token 无效或用户未认证
- var token = nancyContext.Request.Headers.Authorization;
- if (string.IsNullOrEmpty(token))
- return null;
- var userValidator = BootstrapWarpper.IocManager.Resolve<IUserMapper>();
- var userIdentity = userValidator.GetUserFromAccessToken(token);
- return userIdentity;
- }
- );
- StatelessAuthentication.Enable(pipelines, configuration);
- #endregion
- // 启用 Session
- //CookieBasedSessions.Enable(pipelines);
- base.ApplicationStartup(container, pipelines);
- }
- /// <summary>
- /// RootPathProvider
- /// </summary>
- protected override IRootPathProvider RootPathProvider =>
- new WorkDataRootPathProvider();
- /// <summary>
- /// 配置静态文件访问权限
- /// </summary>
- /// <param name="conventions"></param>
- protected override void ConfigureConventions(NancyConventions conventions)
- {
- base.ConfigureConventions(conventions);
- // 静态文件夹访问 设置 css,js,image
- conventions.StaticContentsConventions.AddDirectory("Contents");
- }
- }
- View Code
推荐 下 Nancy 虽然 有. net core 但是 如果你用 Nancy 后会发现 两者会有很多相似之处
最主要还是相当 轻量级 可以高度的自定义
来源: https://www.cnblogs.com/wulaiwei/p/9389736.html