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

1 public interface IResolver 2 { 3 /// <summary> 4 /// Resolves this instance. 5 /// </summary> 6 /// <typeparam name="T"></typeparam> 7 /// <returns></returns> 8 T Resolve<T>(ILifetimeScope scope = null); 9 10 /// <summary> 11 /// Determines whether this instance is registered. 12 /// </summary> 13 /// <typeparam name="T"></typeparam> 14 /// <returns> 15 /// <c>true</c> if this instance is registered; otherwise, <c>false</c>. 16 /// </returns> 17 bool IsRegistered<T>() where T : class; 18 19 /// <summary> 20 /// Determines whether the specified type is registered. 21 /// </summary> 22 /// <param name="type">The type.</param> 23 /// <param name="scope">The ILifetimeScope</param> 24 /// <returns> 25 /// <c>true</c> if the specified type is registered; otherwise, <c>false</c>. 26 /// </returns> 27 bool IsRegistered(Type type, ILifetimeScope scope = null); 28 29 /// <summary> 30 /// Releases a pre-resolved object. See Resolve methods. 31 /// </summary> 32 /// <param name="obj">Object to be released</param> 33 void Release(object obj); 34 35 /// <summary> 36 /// Resolve 37 /// </summary> 38 /// <typeparam name="T"></typeparam> 39 /// <param name="parameters"></param> 40 /// <param name="scope"></param> 41 /// <returns></returns> 42 T Resolve<T>(IEnumerable<Parameter> parameters, ILifetimeScope scope = null); 43 44 /// <summary> 45 /// Resolve 46 /// </summary> 47 /// <typeparam name="T"></typeparam> 48 /// <param name="parameters"></param> 49 /// <returns></returns> 50 T ResolveParameter<T>(params Parameter[] parameters); 51 52 /// <summary> 53 /// Resolve 54 /// </summary> 55 /// <typeparam name="T"></typeparam> 56 /// <returns></returns> 57 T ResolveName<T>(string name); 58 59 /// <summary> 60 /// Resolve 61 /// </summary> 62 /// <returns></returns> 63 object Resolve(Type type); 64 }
-------------------------------------------------------------------------------------------------------------------------------------
然后 在.net core 中已经内置了Ioc 基本代码如下

1 /// <summary> 2 /// ConfigureServices 3 /// </summary> 4 /// <param name="services"></param> 5 /// <returns></returns> 6 public IServiceProvider ConfigureServices(IServiceCollection services) 7 { 8 }
我们可以知道 .net core 内置的Ioc 是以 IServiceCollection 为核心,所以 如果我们需要支持.net core版本则需要 IServiceCollection 所以我们提供一个 IServiceCollectionResolve
IServiceCollectionResolve

1 public interface IServiceCollectionResolve 2 { 3 IServiceCollection ServiceCollection { get; set; } 4 5 T ResolveServiceValue<T>() where T : class, new(); 6 }
Autofac 的核心在于 IContainer 所以我们提供一个 IIocManager
IIocManager

1 public interface IIocManager : IResolver, IRegistrar, IServiceCollectionResolve 2 { 3 /// <summary> 4 /// Reference to the Autofac Container. 5 /// </summary> 6 IContainer IocContainer { get; set; } 7 8 /// <summary> 9 /// ServiceLocatorCurrent 10 /// </summary> 11 IServiceLocator ServiceLocatorCurrent { get; set; } 12 13 /// <summary> 14 /// SetContainer 15 /// </summary> 16 /// <param name="containerBuilder"></param> 17 void SetContainer(ContainerBuilder containerBuilder); 18 19 /// <summary> 20 /// SetServiceCollection 21 /// </summary> 22 /// <param name="serviceCollection"></param> 23 void SetServiceCollection(IServiceCollection serviceCollection); 24 25 /// <summary> 26 /// UpdateContainer 27 /// </summary> 28 /// <param name="containerBuilder"></param> 29 [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.")] 30 void UpdateContainer(ContainerBuilder containerBuilder); 31 }
说明:
IServiceLocator 来源于 CommonServiceLocator 可以在 nuget 找到 可以理解为IResolver
UpdateContainer 官方已经不推荐使用 可以使用但是尽量避免使用 主要适用场景是 :已经初始化完成后 再需要进行二次注册等操作
至此我们所需要的接口基本定义完成。
我们需要一个实现 即 IocManager

1 /// <summary> 2 /// IocManager 3 /// </summary> 4 public class IocManager : IIocManager 5 { 6 /// <summary> 7 /// The Singleton instance. 8 /// </summary> 9 public static IocManager Instance { get; } 10 11 #region ContainerBuilder 12 13 /// <summary> 14 /// ContainerBuilder 15 /// </summary> 16 ContainerBuilder IRegistrar.ContainerBuilder 17 { 18 get => ContainerBuilder; 19 set => ContainerBuilder = value; 20 } 21 22 /// <summary> 23 /// ContainerBuilder 24 /// </summary> 25 public static ContainerBuilder ContainerBuilder { get; set; } 26 27 #endregion 28 29 #region IContainer 30 31 /// <summary> 32 /// IocContainer 33 /// </summary> 34 IContainer IIocManager.IocContainer 35 { 36 get => IocContainer; 37 set => IocContainer = value; 38 } 39 40 /// <summary> 41 /// IocContainer 42 /// </summary> 43 public static IContainer IocContainer { get; set; } 44 45 #endregion 46 47 #region IServiceLocator 48 49 IServiceLocator IIocManager.ServiceLocatorCurrent 50 { 51 get => ServiceLocatorCurrent; 52 set => ServiceLocatorCurrent = value; 53 } 54 55 /// <summary> 56 /// ServiceLocator 57 /// </summary> 58 public static IServiceLocator ServiceLocatorCurrent { get; set; } 59 60 #endregion 61 62 #region IServiceCollection 63 64 IServiceCollection IServiceCollectionResolve.ServiceCollection 65 { 66 get => ServiceCollection; 67 set => ServiceCollection = value; 68 } 69 70 /// <summary> 71 /// ServiceCollection 72 /// </summary> 73 public static IServiceCollection ServiceCollection { get; set; } 74 75 #endregion 76 77 /// <summary> 78 /// IocManager 79 /// </summary> 80 static IocManager() 81 { 82 Instance = new IocManager(); 83 } 84 85 /// <summary> 86 /// SetContainer 87 /// </summary> 88 /// <param name="containerBuilder"></param> 89 public void SetContainer(ContainerBuilder containerBuilder) 90 { 91 ContainerBuilder = containerBuilder; 92 var container = containerBuilder.Build(); 93 IocContainer = container; 94 95 //设置定位器 96 ServiceLocatorCurrent = new AutofacServiceLocator(IocContainer); 97 } 98 99 /// <summary> 100 /// SetServiceCollection 101 /// </summary> 102 /// <param name="serviceCollection"></param> 103 public void SetServiceCollection(IServiceCollection serviceCollection) 104 { 105 ServiceCollection = serviceCollection; 106 } 107 108 /// <summary> 109 /// UpdateContainer 110 /// </summary> 111 /// <param name="containerBuilder"></param> 112 [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.")] 113 public void UpdateContainer(ContainerBuilder containerBuilder) 114 { 115 ContainerBuilder = containerBuilder; 116 containerBuilder?.Update(IocContainer); 117 } 118 119 /// <summary> 120 /// resolve T by lifetime scope 121 /// </summary> 122 /// <typeparam name="T"></typeparam> 123 /// <param name="scope"></param> 124 /// <returns></returns> 125 public T Resolve<T>(ILifetimeScope scope = null) 126 { 127 if (scope == null) 128 { 129 scope = Scope(); 130 } 131 return scope.Resolve<T>(); 132 } 133 134 /// <summary> 135 /// Resolve 136 /// </summary> 137 /// <typeparam name="T"></typeparam> 138 /// <param name="parameters"></param> 139 /// <param name="scope"></param> 140 /// <returns></returns> 141 public T Resolve<T>(IEnumerable<Parameter> parameters, ILifetimeScope scope = null) 142 { 143 if (scope == null) 144 { 145 scope = Scope(); 146 } 147 return scope.Resolve<T>(parameters); 148 } 149 150 /// <summary> 151 /// Resolve 152 /// </summary> 153 /// <typeparam name="T"></typeparam> 154 /// <param name="parameters"></param> 155 /// <returns></returns> 156 public T ResolveParameter<T>(Parameter[] parameters) 157 { 158 var scope = Scope(); 159 return scope.Resolve<T>(parameters); 160 } 161 162 /// <summary> 163 /// ResolveName 164 /// </summary> 165 /// <typeparam name="T"></typeparam> 166 /// <returns></returns> 167 public T ResolveName<T>(string name) 168 { 169 var scope = Scope(); 170 var item = scope.ResolveNamed<T>(name); 171 return item; 172 } 173 174 /// <summary> 175 /// 176 /// </summary> 177 /// <param name="type"></param> 178 /// <returns></returns> 179 public object Resolve(Type type) 180 { 181 var scope = Scope(); 182 var item = scope.Resolve(type); 183 return item; 184 } 185 186 /// <summary> 187 /// IsRegistered 188 /// </summary> 189 /// <typeparam name="T"></typeparam> 190 /// <returns></returns> 191 public bool IsRegistered<T>() where T : class 192 { 193 return IsRegistered(typeof(T)); 194 } 195 196 /// <summary> 197 /// IsRegistered 198 /// </summary> 199 /// <param name="type"></param> 200 /// <param name="scope"></param> 201 /// <returns></returns> 202 public bool IsRegistered(Type type, ILifetimeScope scope = null) 203 { 204 if (scope == null) 205 { 206 scope = Scope(); 207 } 208 209 return scope.IsRegistered(type); 210 } 211 212 /// <summary> 213 /// release object lifetimescope 214 /// </summary> 215 /// <param name="obj"></param> 216 public void Release(object obj) 217 { 218 } 219 220 /// <summary> 221 /// create ILifetimeScope from container 222 /// </summary> 223 /// <returns></returns> 224 private static ILifetimeScope Scope() 225 { 226 return IocContainer.BeginLifetimeScope(); 227 } 228 229 /// <summary> 230 /// ResolveServiceValue 231 /// </summary> 232 /// <typeparam name="T"></typeparam> 233 /// <returns></returns> 234 public T ResolveServiceValue<T>() where T : class, new() 235 { 236 return ServiceCollection.ResolveServiceValue<T>(); 237 } 238 }
说明:
主要依赖于:
这些全部完成后 我们需要一个最终的装载程序 Bootstrap

1 /// <summary> 2 /// 初始化装载程序 3 /// </summary> 4 public class Bootstrap 5 { 6 /// <summary> 7 /// _isInit 8 /// </summary> 9 private static bool _isInit; 10 11 /// <summary> 12 /// _iocManager 13 /// </summary> 14 public IIocManager IocManager { get; set; } 15 16 /// <summary> 17 /// StartupModule 18 /// </summary> 19 public Type StartupModule { get; set; } 20 21 /// <summary> 22 /// Instance 23 /// </summary> 24 /// <returns></returns> 25 public static Bootstrap Instance<TStartupModule>() where TStartupModule : WorkDataBaseModule 26 { 27 return new Bootstrap(typeof(TStartupModule)); 28 } 29 30 /// <summary> 31 /// instance bootstrap 32 /// </summary> 33 /// <returns></returns> 34 public static Bootstrap Instance() 35 { 36 return new Bootstrap(); 37 } 38 39 /// <summary> 40 /// Bootstrap 41 /// </summary> 42 public Bootstrap() : this(Dependency.IocManager.Instance) 43 { 44 } 45 46 /// <summary> 47 /// Bootstrap 48 /// </summary> 49 public Bootstrap(Type startupModule) : this(startupModule, Dependency.IocManager.Instance) 50 { 51 } 52 53 /// <summary> 54 /// Bootstrap 55 /// </summary> 56 /// <param name="iocManager"></param> 57 public Bootstrap(IIocManager iocManager) 58 { 59 IocManager = iocManager; 60 } 61 62 /// <summary> 63 /// Bootstrap 64 /// </summary> 65 /// <param name="startupModule"></param> 66 /// <param name="iocManager"></param> 67 public Bootstrap(Type startupModule, IIocManager iocManager) 68 { 69 StartupModule = startupModule; 70 IocManager = iocManager; 71 } 72 73 /// <summary> 74 /// 初始化集成框架(配置方式) 75 /// </summary> 76 [STAThread] 77 public void InitiateConfig(IServiceCollection services, List<string> paths) 78 { 79 if (_isInit) return; 80 var builder = new ContainerBuilder(); 81 82 #region RegisterConfig 83 var config = new ConfigurationBuilder(); 84 config.SetBasePath(AppDomain.CurrentDomain.BaseDirectory); 85 if (paths != null) 86 { 87 foreach (var item in paths) 88 { 89 config.AddJsonFile(item); 90 } 91 } 92 93 var module = new ConfigurationModule(config.Build()); 94 builder.RegisterModule(module); 95 96 #endregion 97 98 //注入初始module 99 builder.RegisterModule(new WorkDataModule()); 100 101 IocManager.SetServiceCollection(services); 102 103 builder.Populate(services); 104 105 IocManager.SetContainer(builder); 106 _isInit = true; 107 } 108 109 /// <summary> 110 /// InitiateConfig 111 /// </summary> 112 /// <param name="paths"></param> 113 public void InitiateConfig(List<string> paths) 114 { 115 if (_isInit) return; 116 var builder = new ContainerBuilder(); 117 118 #region RegisterConfig 119 var config = new ConfigurationBuilder(); 120 config.SetBasePath(AppDomain.CurrentDomain.BaseDirectory); 121 if (paths != null) 122 { 123 foreach (var item in paths) 124 { 125 config.AddJsonFile(item); 126 } 127 } 128 129 var module = new ConfigurationModule(config.Build()); 130 builder.RegisterModule(module); 131 132 #endregion 133 134 //注入初始module 135 builder.RegisterModule(new WorkDataModule()); 136 137 IocManager.SetContainer(builder); 138 _isInit = true; 139 } 140 141 /// <summary> 142 /// UpdateContainer 143 /// </summary> 144 /// <param name="services"></param> 145 [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.")] 146 public void CoreUpdateContainer(IServiceCollection services) 147 { 148 var builder = new ContainerBuilder(); 149 builder.Populate(services); 150 IocManager.UpdateContainer(builder); 151 } 152 153 154 }
这样我们整体的架子就算初步完成了
扩展
1.随着项目逐渐增大 我们会有很多很多的接口 去进行注入 我们 希望通过反射的方式进行注入 所以我们提供一个 ITypeFinder 方便进行操作
注: ITypeFinder 来源于 nopcommerce
代码位置 :
https://github.com/wulaiwei/WorkData.Core/tree/master/WorkData/WorkData/Extensions/TypeFinders
2. 在 .net Framework autofac 针对常量的配置 我们可以采用属性注入的方式完成 ,但是针对.net core版本 不推荐采用 属性注入 ,推荐使用core 自带的文件注入方式

1 public Startup(IHostingEnvironment env) 2 { 3 var builder = new ConfigurationBuilder() 4 .SetBasePath(env.ContentRootPath) 5 .AddJsonFile("Config/appsettings.json", optional: true, reloadOnChange: true) 6 .AddJsonFile($"Config/appsettings.{env.EnvironmentName}.json", optional: true) 7 .AddEnvironmentVariables(); 8 this.Configuration = builder.Build(); 9 }
有个参数选项 为 reloadOnChange: true 即修改后会自动重新加载 ,然后注入至 IServiceCollection既可以
我们可以采用IocManager 进行 Resolve 但是你就立马会遇到很尴尬的问题 加入我还没注入完成 我就需要这个对象呢
例如 加入我需要使用JWT

1 services.AddAuthentication(options => 2 { 3 //认证middleware配置 4 options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; 5 options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; 6 }) 7 .AddJwtBearer(o => 8 { 9 //主要是jwt token参数设置 10 o.TokenValidationParameters = new TokenValidationParameters 11 { 12 //Token颁发机构 13 ValidIssuer = workDataBaseJwt.Issuer, 14 //颁发给谁 15 ValidAudience = workDataBaseJwt.Audience, 16 //这里的key要进行加密,需要引用Microsoft.IdentityModel.Tokens 17 IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(workDataBaseJwt.SecretKey)), 18 //ValidateIssuerSigningKey=true, 19 ////是否验证Token有效期,使用当前时间与Token的Claims中的NotBefore和Expires对比 20 ValidateLifetime = true, 21 ////允许的服务器时间偏移量 22 ClockSkew = TimeSpan.Zero 23 }; 24 });
然后就需要这个对象 workDataBaseJwt 所以 我们这边需要对 IServiceCollection 做个扩展

1 public static class WorkDataServiceCollection 2 { 3 public static T ResolveServiceValue<T>(this IServiceCollection services) where T : class, new() 4 { 5 try 6 { 7 var provider = services.BuildServiceProvider(); 8 var entity = provider.GetRequiredService<IOptions<T>>().Value; 9 return entity; 10 } 11 catch (Exception) 12 { 13 return default(T); 14 } 15 } 16 }
这样我就可以在注入之前进行Resolve 即:

1 services.Configure<WorkDataBaseJwt>(Configuration.GetSection("WorkDataBaseJwt")); 2 services.Configure<WorkDataDbConfig>(Configuration.GetSection("WorkDataDbContextConfig")); 3 services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); 4 services.AddTransient<IPrincipal>(provider => 5 provider.GetService<IHttpContextAccessor>().HttpContext.User); 6 7 var workDataBaseJwt = services.ResolveServiceValue<WorkDataBaseJwt>();
最后推荐使用 json 进行模块注册

1 { 2 "modules": [ 3 { 4 "type": "WorkData.Web.WorkDataWebModule,WorkData.Web" 5 }, 6 { 7 "type": "WorkData.Domain.EntityFramework.DomainEntityFrameworkModule,WorkData.Domain.EntityFramework" 8 }, 9 { 10 "type": "WorkData.EntityFramework.EntityFrameworkModule,WorkData.EntityFramework" 11 }, 12 { 13 "type": "WorkData.Code.WorkDataCodeModule,WorkData.Code" 14 } 15 ] 16 }
关于在.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;

1 public class WorkDataAutofacNancyBootstrapper : AutofacNancyBootstrapper 2 { 3 /// <summary> 4 /// Gets a reference to the <see cref="Bootstrap" /> instance. 5 /// </summary> 6 public static Bootstrap BootstrapWarpper { get; } = Bootstrap.Instance(); 7 8 private readonly ILogService _logService; 9 10 static WorkDataAutofacNancyBootstrapper() 11 { 12 BootstrapWarpper.InitiateConfig(); 13 } 14 15 public WorkDataAutofacNancyBootstrapper() 16 { 17 _logService = BootstrapWarpper.IocManager.Resolve<ILogService>(); 18 } 19 20 protected override ILifetimeScope GetApplicationContainer() 21 { 22 return BootstrapWarpper.IocManager.IocContainer; 23 } 24 25 protected override void ConfigureApplicationContainer(ILifetimeScope container) 26 { 27 var builder = new ContainerBuilder(); 28 builder.RegisterType<CustomJsonNetSerializer>().As<ISerializer>(); 29 builder.RegisterType<UserMapper>().As<IUserMapper>(); 30 31 BootstrapWarpper.IocManager.UpdateContainer(builder); 32 } 33 34 protected override void RequestStartup(ILifetimeScope container, IPipelines pipelines, NancyContext context) 35 { 36 base.RequestStartup(container, pipelines, context); 37 38 #region 拦截器 39 40 pipelines.BeforeRequest += ctx => 41 { 42 var logRequest = new LogRequest 43 { 44 Url = context.Request.Url, 45 Form = JsonConvert.SerializeObject(context.Request.Form), 46 Query = JsonConvert.SerializeObject(context.Request.Query), 47 Method = context.Request.Method, 48 Body = context.Request.Body.AsString(), 49 Key = Guid.NewGuid().ToString(), 50 CreateTime = DateTime.Now, 51 CreateUserId = ctx.GetUserIdentity()?.UserId 52 }; 53 _logService.AddRequestIndex(logRequest); 54 return null; 55 }; 56 pipelines.AfterRequest += ctx => { }; 57 pipelines.OnError += (ctx, ex) => 58 { 59 var logRequestError = new LogRequestError 60 { 61 Url = context.Request.Url, 62 ErrorMessage = ex.Message, 63 Key = Guid.NewGuid().ToString(), 64 CreateTime=DateTime.Now, 65 CreateUserId= ctx.GetUserIdentity()?.UserId 66 }; 67 _logService.AddRequestErrorIndex(logRequestError); 68 69 return null; 70 }; 71 72 #endregion 73 } 74 75 protected override void ApplicationStartup(ILifetimeScope container, IPipelines pipelines) 76 { 77 base.ApplicationStartup(container, pipelines); 78 DiagnosticsHook.Disable(pipelines); 79 80 pipelines.AfterRequest += ctx => 81 { 82 ctx.Response.Headers.Add("Access-Control-Allow-Origin", "*"); 83 ctx.Response.Headers.Add("Access-Control-Allow-Credentials", "true"); 84 ctx.Response.Headers.Add("Access-Control-Allow-Methods", "POST,GET"); 85 ctx.Response.Headers.Add("Access-Control-Allow-Headers", "Content-Type,Access-Token"); 86 ctx.Response.Headers.Add("Access-Control-Expose-Headers", "*"); 87 }; 88 89 #region Authentication 90 var configuration = new StatelessAuthenticationConfiguration( 91 nancyContext => 92 { 93 //返回null代码token无效或用户未认证 94 var token = nancyContext.Request.Headers.Authorization; 95 if (string.IsNullOrEmpty(token)) 96 return null; 97 var userValidator = BootstrapWarpper.IocManager.Resolve<IUserMapper>(); 98 99 var userIdentity = userValidator.GetUserFromAccessToken(token); 100 101 return userIdentity; 102 } 103 ); 104 StatelessAuthentication.Enable(pipelines, configuration); 105 #endregion 106 107 //启用Session 108 //CookieBasedSessions.Enable(pipelines); 109 110 base.ApplicationStartup(container, pipelines); 111 } 112 113 /// <summary> 114 /// RootPathProvider 115 /// </summary> 116 protected override IRootPathProvider RootPathProvider => 117 new WorkDataRootPathProvider(); 118 119 /// <summary> 120 /// 配置静态文件访问权限 121 /// </summary> 122 /// <param name="conventions"></param> 123 protected override void ConfigureConventions(NancyConventions conventions) 124 { 125 base.ConfigureConventions(conventions); 126 127 //静态文件夹访问 设置 css,js,image 128 conventions.StaticContentsConventions.AddDirectory("Contents"); 129 } 130 }
推荐 下Nancy 虽然 有.net core 但是 如果你用Nancy 后会发现 两者会有很多相似之处
最主要还是相当 轻量级 可以高度的自定义