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 后會發現 兩者會有很多相似之處
最主要還是相當 輕量級 可以高度的自定義