From 968aa576100260ebc6cadd2d4008167b547faaa0 Mon Sep 17 00:00:00 2001 From: wangzelin <315872858@qq.com> Date: Thu, 21 Feb 2019 15:12:14 +0800 Subject: [PATCH] Upgrade --- Example/Example.csproj | 8 +- Example/Program.cs | 45 ++-- Example/Startup.cs | 65 +++--- Example/appsettings.Development.json | 2 +- README.md | 56 +++-- .../ConsulAgentConfiguration.cs | 10 +- .../ConsulConfigurationExtensions.cs | 47 ---- .../ConsulConfigurationHostedService.cs | 78 +++++++ .../ConsulConfigurationProvider.cs | 217 ++++++++---------- .../ConsulConfigurationSource.cs | 29 ++- .../ConsulQueryOptions.cs | 36 +-- .../Extensions.Configuration.Consul.csproj | 6 +- .../ExtensionsMethods.cs | 144 ++++++++++++ .../IObserver.cs | 11 + .../ObserverManager.cs | 24 ++ 15 files changed, 482 insertions(+), 296 deletions(-) delete mode 100644 src/Extensions.Configuration.Consul/ConsulConfigurationExtensions.cs create mode 100644 src/Extensions.Configuration.Consul/ConsulConfigurationHostedService.cs create mode 100644 src/Extensions.Configuration.Consul/ExtensionsMethods.cs create mode 100644 src/Extensions.Configuration.Consul/IObserver.cs create mode 100644 src/Extensions.Configuration.Consul/ObserverManager.cs diff --git a/Example/Example.csproj b/Example/Example.csproj index dc084b6..d9cd067 100644 --- a/Example/Example.csproj +++ b/Example/Example.csproj @@ -1,7 +1,7 @@ - netcoreapp2.1 + netcoreapp2.2 @@ -9,11 +9,11 @@ - - + + - + diff --git a/Example/Program.cs b/Example/Program.cs index 0739315..25b9351 100644 --- a/Example/Program.cs +++ b/Example/Program.cs @@ -1,6 +1,4 @@ -using System; -using System.IO; -using Consul; +using System.IO; using Extensions.Configuration.Consul; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; @@ -8,29 +6,22 @@ namespace Example { - public class Program - { - public static void Main(string[] args) - { - CreateWebHostBuilder(args).Build().Run(); - ConsulConfigurationExtensions.Shutdown(); - } - public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + public class Program + { + public static void Main(string[] args) + { + CreateWebHostBuilder(args).Build().Run(); + } + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => - WebHost.CreateDefaultBuilder(args).ConfigureAppConfiguration((context, config) => - { - config.SetBasePath(Directory.GetCurrentDirectory()); - config.AddCommandLine(args); - config.AddJsonFile("appsettings.json", false, true); - config.AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json", false, true); - config.AddConsul(new ConsulClientConfiguration - { - Address = new Uri("http://192.168.1.142:8500") - }, new ConsulQueryOptions - { - Prefix = "AppSetting/", - TrimPrefix = true - }, true); - }).UseStartup(); - } + WebHost.CreateDefaultBuilder(args).ConfigureAppConfiguration((context, config) => + { + config.SetBasePath(Directory.GetCurrentDirectory()); + config.AddJsonFile("appsettings.json", false, true); + config.AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json", false, true); + //config.AddConsul("http://192.168.1.133:8500"); + config.AddConsul(args); + config.AddCommandLine(args); + }).UseStartup(); + } } diff --git a/Example/Startup.cs b/Example/Startup.cs index cc8618e..0866448 100644 --- a/Example/Startup.cs +++ b/Example/Startup.cs @@ -5,6 +5,7 @@ using Autofac; using Autofac.Extensions.DependencyInjection; using Consul; +using Extensions.Configuration.Consul; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Server.Features; @@ -17,41 +18,41 @@ namespace Example { - public class Startup - { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } - public IConfiguration Configuration { get; } - public IContainer ApplicationContainer { get; private set; } + public IConfiguration Configuration { get; } + public IContainer ApplicationContainer { get; private set; } - // This method gets called by the runtime. Use this method to add services to the container. - public IServiceProvider ConfigureServices(IServiceCollection services) - { - services.AddOptions(); - services.Configure(Configuration.GetSection("TestConfig")); - services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + // This method gets called by the runtime. Use this method to add services to the container. + public IServiceProvider ConfigureServices(IServiceCollection services) + { + services.AddOptions(); + services.Configure(Configuration.GetSection("TestConfig")); + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + services.AddConsulConfigurationCenter(); + var builder = new ContainerBuilder(); + builder.Populate(services); + builder.RegisterType().InstancePerLifetimeScope(); + builder.RegisterType().SingleInstance(); + ApplicationContainer = builder.Build(); + return new AutofacServiceProvider(ApplicationContainer); + } - var builder = new ContainerBuilder(); - builder.Populate(services); - builder.RegisterType().InstancePerLifetimeScope(); - builder.RegisterType().SingleInstance(); - ApplicationContainer = builder.Build(); - return new AutofacServiceProvider(ApplicationContainer); - } + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IHostingEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseMvc(); - } - } + app.UseMvc(); + } + } } diff --git a/Example/appsettings.Development.json b/Example/appsettings.Development.json index 4648c53..1d75a0b 100644 --- a/Example/appsettings.Development.json +++ b/Example/appsettings.Development.json @@ -1,7 +1,7 @@ { "Logging": { "LogLevel": { - "Default": "Debug", + "Default": "Trace", "System": "Information", "Microsoft": "Information" } diff --git a/README.md b/README.md index 23fdae2..5978957 100644 --- a/README.md +++ b/README.md @@ -6,50 +6,68 @@ Package | NuGet ---------|------ Extensions.Configuration.Consul|[![NuGet package](https://buildstats.info/nuget/Extensions.Configuration.Consul)](https://www.nuget.org/packages/Extensions.Configuration.Consul) -# Usage -### Register +# Configuration + +## Hardcoded configuration ```csharp public class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); - ConsulConfigurationExtensions.Shutdown(); } + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args).ConfigureAppConfiguration((context, config) => + { + config.AddConsul("http://127.0.0.1:8500"); + }).UseStartup(); + } +``` + +## Command line configuration +Command | Describetion +---------|------ +consul-configuration-addr|Consul agent address +consul-configuration-token|ACL Token HTTP API +consul-configuration-dc|Consul data center +consul-configuration-folder|Prefix of key + +```csharp + public class Program + { + public static void Main(string[] args) + { + CreateWebHostBuilder(args).Build().Run(); + } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args).ConfigureAppConfiguration((context, config) => { - config.SetBasePath(Directory.GetCurrentDirectory()); - config.AddJsonFile("appsettings.json", false, true); - config.AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json", false, true); - config.AddConsul("http://192.168.1.142:8500", "", "AppSetting/", "dc1", true); + config.AddConsul(args); }).UseStartup(); } ``` +## Reload when modified ```csharp - public IServiceProvider ConfigureServices(IServiceCollection services) + public void ConfigureServices(IServiceCollection services) { + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); + + services.AddConsulConfigurationCenter(); + services.AddOptions(); services.Configure(Configuration.GetSection("TestConfig")); - services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); - - var builder = new ContainerBuilder(); - builder.Populate(services); - builder.RegisterType().InstancePerLifetimeScope(); - builder.RegisterType().SingleInstance(); - ApplicationContainer = builder.Build(); - return new AutofacServiceProvider(ApplicationContainer); + } ``` - -### InstancePerLifetimeScope +# Usage +## InstancePerLifetimeScope ```csharp public class LibClass { @@ -66,7 +84,7 @@ Extensions.Configuration.Consul|[![NuGet package](https://buildstats.info/nuget/ } ``` -### SingleInstance +## SingleInstance ```csharp public class SingleClass { diff --git a/src/Extensions.Configuration.Consul/ConsulAgentConfiguration.cs b/src/Extensions.Configuration.Consul/ConsulAgentConfiguration.cs index 5fefa40..9d78bf6 100644 --- a/src/Extensions.Configuration.Consul/ConsulAgentConfiguration.cs +++ b/src/Extensions.Configuration.Consul/ConsulAgentConfiguration.cs @@ -2,10 +2,10 @@ namespace Extensions.Configuration.Consul { - public class ConsulAgentConfiguration - { - public ConsulClientConfiguration ClientConfiguration { get; set; } + public class ConsulAgentConfiguration + { + public ConsulClientConfiguration ClientConfiguration { get; set; } - public ConsulQueryOptions QueryOptions { get; set; } - } + public ConsulQueryOptions QueryOptions { get; set; } + } } diff --git a/src/Extensions.Configuration.Consul/ConsulConfigurationExtensions.cs b/src/Extensions.Configuration.Consul/ConsulConfigurationExtensions.cs deleted file mode 100644 index 9ca294b..0000000 --- a/src/Extensions.Configuration.Consul/ConsulConfigurationExtensions.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using Consul; -using Microsoft.Extensions.Configuration; - -namespace Extensions.Configuration.Consul -{ - public static class ConsulConfigurationExtensions - { - - public static IConfigurationBuilder AddConsul(this IConfigurationBuilder configurationBuilder, ConsulClientConfiguration consulClientConfiguration, ConsulQueryOptions queryOptions, bool reloadOnChange = false) - { - if (consulClientConfiguration == null) - throw new ArgumentNullException(nameof(consulClientConfiguration), "The agent url can't be empty."); - return Add(configurationBuilder, new ConsulAgentConfiguration { ClientConfiguration = consulClientConfiguration, QueryOptions = queryOptions }, reloadOnChange); - } - - - public static IConfigurationBuilder AddConsul(this IConfigurationBuilder configurationBuilder, string agentUrl, string token = "", string prefix = "", string dataCenter = "", bool reloadOnChange = false) - { - if (string.IsNullOrWhiteSpace(agentUrl)) - throw new ArgumentNullException(nameof(agentUrl), "The agent url can't be empty."); - return Add(configurationBuilder, new ConsulAgentConfiguration - { - ClientConfiguration = new ConsulClientConfiguration - { - Address = new Uri(agentUrl), - Token = token, - Datacenter = dataCenter - }, - QueryOptions = new ConsulQueryOptions - { - Prefix = prefix - } - }, reloadOnChange); - } - - private static IConfigurationBuilder Add(IConfigurationBuilder configurationBuilder, ConsulAgentConfiguration configuration, bool reloadOnChange) - { - return configurationBuilder.Add(new ConsulConfigurationSource(configuration, reloadOnChange)); - } - - public static void Shutdown() - { - ConsulConfigurationProvider.Shutdown(); - } - } -} diff --git a/src/Extensions.Configuration.Consul/ConsulConfigurationHostedService.cs b/src/Extensions.Configuration.Consul/ConsulConfigurationHostedService.cs new file mode 100644 index 0000000..40ee970 --- /dev/null +++ b/src/Extensions.Configuration.Consul/ConsulConfigurationHostedService.cs @@ -0,0 +1,78 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Consul; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace Extensions.Configuration.Consul +{ + public class ConsulConfigurationHostedService : IHostedService + { + private static readonly CancellationTokenSource CancellationTokenSource = new CancellationTokenSource(); + private ulong LastIndex { get; set; } + + private HostedServiceOptions HostedServiceOptions { get; } + + private ILogger Logger { get; } + + public ConsulConfigurationHostedService(HostedServiceOptions hostedServiceOptions, ILogger logger) + { + + HostedServiceOptions = hostedServiceOptions; + Logger = logger; + } + + public Task StartAsync(CancellationToken cancellationToken) + { + return Task.Factory.StartNew(async () => + { + do + { + try + { + await QueryConsulAsync(); + } + catch (Exception e) + { + Logger.LogError($"{e.Message}"); + } + + } while (!CancellationTokenSource.IsCancellationRequested); + }, CancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); + } + private async Task QueryConsulAsync() + { + using (var client = new ConsulClient(options => + { + options.WaitTime = ObserverManager.Configuration.ClientConfiguration.WaitTime; + options.Token = ObserverManager.Configuration.ClientConfiguration.Token; + options.Datacenter = ObserverManager.Configuration.ClientConfiguration.Datacenter; + options.Address = ObserverManager.Configuration.ClientConfiguration.Address; + })) + { + var result = await client.KV.List(ObserverManager.Configuration.QueryOptions.Folder, new QueryOptions + { + Token = ObserverManager.Configuration.ClientConfiguration.Token, + Datacenter = ObserverManager.Configuration.ClientConfiguration.Datacenter, + WaitIndex = LastIndex, + WaitTime = HostedServiceOptions.BlockingQueryWait + }, CancellationTokenSource.Token); + if (result.StatusCode != System.Net.HttpStatusCode.OK) + return; + if (result.LastIndex > LastIndex) + { + LastIndex = result.LastIndex; + ObserverManager.Notify(result.Response.ToList(), Logger); + } + } + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + CancellationTokenSource.Cancel(); + await Task.CompletedTask; + } + } +} diff --git a/src/Extensions.Configuration.Consul/ConsulConfigurationProvider.cs b/src/Extensions.Configuration.Consul/ConsulConfigurationProvider.cs index 7734862..1042de6 100644 --- a/src/Extensions.Configuration.Consul/ConsulConfigurationProvider.cs +++ b/src/Extensions.Configuration.Consul/ConsulConfigurationProvider.cs @@ -1,127 +1,106 @@ -using System.Net.Http; -using System.Text; -using System.Threading; +using System.Text; using System.Threading.Tasks; using Consul; -using System; +using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; namespace Extensions.Configuration.Consul { - internal class ConsulConfigurationProvider : ConfigurationProvider, IDisposable - { - private ConsulAgentConfiguration Configuration { get; } - - private bool ReloadOnChange { get; } - - private ulong LastIndex { get; set; } - - private static readonly CancellationTokenSource CancellationTokenSource = new CancellationTokenSource(); - - public ConsulConfigurationProvider(ConsulAgentConfiguration configuration, bool reloadOnChange) - { - Configuration = configuration; - ReloadOnChange = reloadOnChange; - } - - - public static void Shutdown() - { - CancellationTokenSource.Cancel(); - } - - public override void Load() - { - QueryConsulAsync(CancellationTokenSource.Token).GetAwaiter().GetResult(); - if (ReloadOnChange) - { - Task.Factory.StartNew(async () => - { - var failCount = 0; - do - { - try - { - await QueryConsulAsync(CancellationTokenSource.Token, true); - failCount = 0; - } - catch (Exception) - { - failCount++; - if (Configuration.QueryOptions.FailRetryInterval != null) - await Task.Delay(Configuration.QueryOptions.FailRetryInterval.Value, - CancellationTokenSource.Token); - } - - } while (!CancellationTokenSource.IsCancellationRequested && failCount <= Configuration.QueryOptions.ContinuousQueryFailures); - }, CancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); - } - } - - - private async Task QueryConsulAsync(CancellationToken cancellationToken, bool blocking = false) - { - using (var client = new ConsulClient(options => - { - options.WaitTime = Configuration.ClientConfiguration.WaitTime; - options.Token = Configuration.ClientConfiguration.Token; - options.Datacenter = Configuration.ClientConfiguration.Datacenter; - options.Address = Configuration.ClientConfiguration.Address; - })) - { - var result = await client.KV.List(Configuration.QueryOptions.Prefix, new QueryOptions - { - Token = Configuration.ClientConfiguration.Token, - Datacenter = Configuration.ClientConfiguration.Datacenter, - WaitIndex = LastIndex, - WaitTime = blocking ? Configuration.QueryOptions.BlockingQueryWait : null - }, cancellationToken); - if (result.LastIndex > LastIndex) - { - if (result.Response != null) - { - var deleted = Data.Where(p => result.Response.All(c => - p.Key != (Configuration.QueryOptions.TrimPrefix - ? c.Key.Substring(Configuration.QueryOptions.Prefix.Length, - c.Key.Length - Configuration.QueryOptions.Prefix.Length) - : c.Key))).ToList(); - - foreach (var del in deleted) - { - Data.Remove(del.Key); - } - - foreach (var item in result.Response) - { - if (Configuration.QueryOptions.TrimPrefix) - { - item.Key = item.Key.Substring(Configuration.QueryOptions.Prefix.Length, - item.Key.Length - Configuration.QueryOptions.Prefix.Length); - } - - if (!string.IsNullOrWhiteSpace(item.Key)) - { - Set(item.Key, - item.Value != null && item.Value?.Length > 0 - ? Encoding.UTF8.GetString(item.Value) - : ""); - } - } - } - else - Data.Clear(); - - LastIndex = result.LastIndex; - if (ReloadOnChange && blocking) - OnReload(); - } - } - } - - public void Dispose() - { - CancellationTokenSource.Cancel(); - } - } + internal class ConsulConfigurationProvider : ConfigurationProvider, IObserver + { + private ConsulAgentConfiguration Configuration { get; } + + + public ConsulConfigurationProvider(ConsulAgentConfiguration configuration) + { + Configuration = configuration; + } + + + public override void Load() + { + QueryConsulAsync().GetAwaiter().GetResult(); + } + + + private async Task QueryConsulAsync() + { + using (var client = new ConsulClient(options => + { + options.WaitTime = Configuration.ClientConfiguration.WaitTime; + options.Token = Configuration.ClientConfiguration.Token; + options.Datacenter = Configuration.ClientConfiguration.Datacenter; + options.Address = Configuration.ClientConfiguration.Address; + })) + { + var result = await client.KV.List(Configuration.QueryOptions.Folder, new QueryOptions + { + Token = Configuration.ClientConfiguration.Token, + Datacenter = Configuration.ClientConfiguration.Datacenter + }); + + if (result.Response == null || !result.Response.Any()) + return; + + foreach (var item in result.Response) + { + item.Key = item.Key.TrimFolderPrefix(Configuration.QueryOptions.Folder); + if (string.IsNullOrWhiteSpace(item.Key)) + return; + Set(item.Key, ReadValue(item.Value)); + } + } + } + + + private string ReadValue(byte[] bytes) + { + return bytes != null && bytes.Length > 0 + ? Encoding.UTF8.GetString(bytes) + : ""; + } + + public void OnChange(List kVs, ILogger logger) + { + if (kVs == null || !kVs.Any()) + { + Data.Clear(); + OnReload(); + return; + } + + var deleted = Data.Where(p => kVs.All(c => + p.Key != c.Key.TrimFolderPrefix(Configuration.QueryOptions.Folder))).ToList(); + + foreach (var del in deleted) + { + logger.LogTrace($"Remove key [{del.Key}]"); + Data.Remove(del.Key); + } + + foreach (var item in kVs) + { + item.Key = item.Key.TrimFolderPrefix(Configuration.QueryOptions.Folder); + if (string.IsNullOrWhiteSpace(item.Key)) + continue; + var newValue = ReadValue(item.Value); + if (Data.TryGetValue(item.Key, out var oldValue)) + { + if (oldValue == newValue) + continue; + + Set(item.Key, newValue); + logger.LogTrace($"The value of key [{item.Key}] is changed from [{oldValue}] to [{newValue}]"); + } + else + { + Set(item.Key, newValue); + logger.LogTrace($"Added key [{item.Key}][{newValue}]"); + } + OnReload(); + } + } + } } diff --git a/src/Extensions.Configuration.Consul/ConsulConfigurationSource.cs b/src/Extensions.Configuration.Consul/ConsulConfigurationSource.cs index 7ebfcfd..fc3e602 100644 --- a/src/Extensions.Configuration.Consul/ConsulConfigurationSource.cs +++ b/src/Extensions.Configuration.Consul/ConsulConfigurationSource.cs @@ -2,21 +2,20 @@ namespace Extensions.Configuration.Consul { - internal class ConsulConfigurationSource : IConfigurationSource - { - private ConsulAgentConfiguration Config { get; } + internal class ConsulConfigurationSource : IConfigurationSource + { + private ConsulAgentConfiguration Config { get; } - private bool ReloadOnChange { get; } + public ConsulConfigurationSource(ConsulAgentConfiguration config) + { + Config = config; + } - public ConsulConfigurationSource(ConsulAgentConfiguration config, bool reloadOnChange) - { - Config = config; - ReloadOnChange = reloadOnChange; - } - - public IConfigurationProvider Build(IConfigurationBuilder builder) - { - return new ConsulConfigurationProvider(Config, ReloadOnChange); - } - } + public IConfigurationProvider Build(IConfigurationBuilder builder) + { + var provider = new ConsulConfigurationProvider(Config); + ObserverManager.Attach(provider, Config); + return provider; + } + } } diff --git a/src/Extensions.Configuration.Consul/ConsulQueryOptions.cs b/src/Extensions.Configuration.Consul/ConsulQueryOptions.cs index e5c3def..f33afa4 100644 --- a/src/Extensions.Configuration.Consul/ConsulQueryOptions.cs +++ b/src/Extensions.Configuration.Consul/ConsulQueryOptions.cs @@ -2,31 +2,17 @@ namespace Extensions.Configuration.Consul { - public class ConsulQueryOptions - { - /// - /// The prefix string of consul key - /// - public string Prefix { get; set; } + public class ConsulQueryOptions + { + /// + /// The prefix string of consul key + /// + public string Folder { get; set; } - /// - /// Remove prefix string of consul key when binding - /// - public bool TrimPrefix { get; set; } = false; + } - /// - /// Wait time of long polling,the defautl is 2 minutes - /// - public TimeSpan? BlockingQueryWait { get; set; } = TimeSpan.FromMinutes(2); - - /// - /// Continuous query failures, the default is ignore exception. - /// - public int ContinuousQueryFailures { get; set; } = int.MaxValue; - - /// - /// Failure retry interval,the default is 2 minutes - /// - public TimeSpan? FailRetryInterval { get; set; } = TimeSpan.FromMinutes(2); - } + public class HostedServiceOptions + { + public TimeSpan BlockingQueryWait { get; set; } = TimeSpan.FromMinutes(3); + } } diff --git a/src/Extensions.Configuration.Consul/Extensions.Configuration.Consul.csproj b/src/Extensions.Configuration.Consul/Extensions.Configuration.Consul.csproj index 46f39ca..926ff8f 100644 --- a/src/Extensions.Configuration.Consul/Extensions.Configuration.Consul.csproj +++ b/src/Extensions.Configuration.Consul/Extensions.Configuration.Consul.csproj @@ -10,12 +10,14 @@ Extensions.Configuration.Consul configuration,config,consul Extensions.Configuration.Consul - 1.0.2 + 2.0.0 - + + + diff --git a/src/Extensions.Configuration.Consul/ExtensionsMethods.cs b/src/Extensions.Configuration.Consul/ExtensionsMethods.cs new file mode 100644 index 0000000..9aeb28b --- /dev/null +++ b/src/Extensions.Configuration.Consul/ExtensionsMethods.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using Consul; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Extensions.Configuration.Consul +{ + public static class ExtensionsMethods + { + public static IConfigurationBuilder AddConsul(this IConfigurationBuilder configurationBuilder, string address = "http://127.0.0.1:8500", string token = "", string folder = "", string dataCenter = "dc1") + { + if (string.IsNullOrWhiteSpace(address)) + throw new ArgumentNullException(nameof(address), "The address can't be empty."); + + if (!string.IsNullOrWhiteSpace(folder) && !folder.EndsWith("/")) + throw new ArgumentException("Folder must end with \"/\"."); + + return Add(configurationBuilder, new ConsulAgentConfiguration + { + ClientConfiguration = new ConsulClientConfiguration + { + Address = new Uri(address), + Token = token, + Datacenter = dataCenter + }, + QueryOptions = new ConsulQueryOptions + { + Folder = folder + } + }); + } + public static IConfigurationBuilder AddConsul(this IConfigurationBuilder configurationBuilder, ConsulClientConfiguration consulClientConfiguration, string folder = "") + { + if (consulClientConfiguration == null) + throw new ArgumentNullException(nameof(consulClientConfiguration), "The agent url can't be empty."); + + if (!string.IsNullOrWhiteSpace(folder) && !folder.EndsWith("/")) + throw new ArgumentException("Folder must end with \"/\"."); + + return Add(configurationBuilder, new ConsulAgentConfiguration + { + ClientConfiguration = consulClientConfiguration, + QueryOptions = new ConsulQueryOptions + { + Folder = folder + } + }); + } + + public static IConfigurationBuilder AddConsul(this IConfigurationBuilder configurationBuilder, string[] args) + { + var dic = ParseCommandLineArgs(args); + return configurationBuilder.AddConsul(new ConsulClientConfiguration + { + Address = new Uri(dic.GetDictionaryValue("consul-configuration-addr", "http://127.0.0.1:8500")), + Token = dic.GetDictionaryValue("consul-configuration-token"), + Datacenter = dic.GetDictionaryValue("consul-configuration-dc", "dc1"), + WaitTime = TimeSpan.FromSeconds(10) + }, dic.GetDictionaryValue("consul-configuration-folder")); + } + + private static string GetDictionaryValue(this Dictionary dictionary, string key, string defaultValue = "") + { + if (dictionary.TryGetValue(key, out var value)) + return value; + return defaultValue; + } + + public static IServiceCollection AddConsulConfigurationCenter(this IServiceCollection services, int blockingQueryWaitSeconds = 180) + { + if (blockingQueryWaitSeconds <= 0) + throw new ArgumentException("The value of blockingQueryWaitSeconds must be greater than 0.", nameof(blockingQueryWaitSeconds)); + services.AddSingleton(new HostedServiceOptions { BlockingQueryWait = TimeSpan.FromSeconds(blockingQueryWaitSeconds) }); + services.AddSingleton(); + return services; + } + + private static IConfigurationBuilder Add(IConfigurationBuilder configurationBuilder, ConsulAgentConfiguration configuration) + { + return configurationBuilder.Add(new ConsulConfigurationSource(configuration)); + } + + internal static string TrimFolderPrefix(this string key, string folder) + { + if (string.IsNullOrWhiteSpace(folder) || folder.Length == 0) + return key; + return key.Substring(folder.Length, key.Length - folder.Length); + } + + private static Dictionary ParseCommandLineArgs(IEnumerable args) + { + var dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); + using (var enumerator = args.GetEnumerator()) + { + while (enumerator.MoveNext()) + { + var key1 = enumerator.Current; + var startIndex = 0; + if (key1.StartsWith("--")) + startIndex = 2; + else if (key1.StartsWith("-")) + startIndex = 1; + else if (key1.StartsWith("/")) + { + key1 = $"--{key1.Substring(1)}"; + startIndex = 2; + } + var length = key1.IndexOf('='); + string index; + string str; + if (length < 0) + { + if (startIndex != 0) + { + if (startIndex != 1) + index = key1.Substring(startIndex); + else + continue; + + if (enumerator.MoveNext()) + str = enumerator.Current; + else + continue; + } + else + continue; + } + else + { + if (startIndex == 1) + throw new FormatException(key1); + index = key1.Substring(startIndex, length - startIndex); + str = key1.Substring(length + 1); + } + dictionary[index] = str; + } + } + + return dictionary; + } + } +} diff --git a/src/Extensions.Configuration.Consul/IObserver.cs b/src/Extensions.Configuration.Consul/IObserver.cs new file mode 100644 index 0000000..cecba6b --- /dev/null +++ b/src/Extensions.Configuration.Consul/IObserver.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using Consul; +using Microsoft.Extensions.Logging; + +namespace Extensions.Configuration.Consul +{ + internal interface IObserver + { + void OnChange(List kVPairs, ILogger logger); + } +} diff --git a/src/Extensions.Configuration.Consul/ObserverManager.cs b/src/Extensions.Configuration.Consul/ObserverManager.cs new file mode 100644 index 0000000..c1c3dd7 --- /dev/null +++ b/src/Extensions.Configuration.Consul/ObserverManager.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using Consul; +using Microsoft.Extensions.Logging; + +namespace Extensions.Configuration.Consul +{ + internal static class ObserverManager + { + private static IObserver Observer { get; set; } + + public static ConsulAgentConfiguration Configuration { get; private set; } + + public static void Attach(IObserver observer, ConsulAgentConfiguration configuration) + { + Observer = observer; + Configuration = configuration; + } + + public static void Notify(List kVPairs, ILogger logger) + { + Observer.OnChange(kVPairs, logger); + } + } +}