├── tests └── ConfigManager.Tests │ ├── Config │ ├── TestYaml.yaml │ ├── TestYaml2.yaml │ ├── TestYaml3.yaml │ ├── Test1.conf │ ├── Test2.conf │ ├── Test3.conf │ └── TestJson.json │ ├── packages.config │ ├── ConfigClasses │ └── TestConfig.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── ConfigManager.Tests.csproj │ └── ConfigManagerTests.cs ├── NuGet ├── NuGetPack.cmd └── ConfigManager │ └── ConfigManager.nuspec ├── lib ├── ConfigManager.dll ├── ConfigManager.pdb └── ConfigManager.XML ├── src └── ConfigManager │ ├── packages.config │ ├── ConfigClasses │ └── ConfigPathsConfig.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── ConfigManager.sln │ ├── ConfigManager.csproj │ └── ConfigManager.cs ├── LICENSE ├── .gitignore └── README.md /tests/ConfigManager.Tests/Config/TestYaml.yaml: -------------------------------------------------------------------------------- 1 | Foo: 1 2 | Bar: 2 3 | Baz: 3 -------------------------------------------------------------------------------- /tests/ConfigManager.Tests/Config/TestYaml2.yaml: -------------------------------------------------------------------------------- 1 | Foo: a 2 | Bar: b 3 | Baz: c -------------------------------------------------------------------------------- /NuGet/NuGetPack.cmd: -------------------------------------------------------------------------------- 1 | nuget pack "ConfigManager\ConfigManager.nuspec" -symbols 2 | -------------------------------------------------------------------------------- /tests/ConfigManager.Tests/Config/TestYaml3.yaml: -------------------------------------------------------------------------------- 1 | Foo: "!" 2 | Bar: "@" 3 | Baz: "#" -------------------------------------------------------------------------------- /lib/ConfigManager.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tejacques/ConfigManager/HEAD/lib/ConfigManager.dll -------------------------------------------------------------------------------- /lib/ConfigManager.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tejacques/ConfigManager/HEAD/lib/ConfigManager.pdb -------------------------------------------------------------------------------- /tests/ConfigManager.Tests/Config/Test1.conf: -------------------------------------------------------------------------------- 1 | { 2 | "Foo" : "1", 3 | "Bar" : "2", 4 | "Baz" : "3" 5 | } -------------------------------------------------------------------------------- /tests/ConfigManager.Tests/Config/Test2.conf: -------------------------------------------------------------------------------- 1 | { 2 | "Foo" : "a", 3 | "Bar" : "b", 4 | "Baz" : "c" 5 | } -------------------------------------------------------------------------------- /tests/ConfigManager.Tests/Config/Test3.conf: -------------------------------------------------------------------------------- 1 | { 2 | "Foo" : "!", 3 | "Bar" : "@", 4 | "Baz" : "#" 5 | } -------------------------------------------------------------------------------- /tests/ConfigManager.Tests/Config/TestJson.json: -------------------------------------------------------------------------------- 1 | { 2 | "Foo" : "1", 3 | "Bar" : "2", 4 | "Baz" : "3" 5 | } -------------------------------------------------------------------------------- /src/ConfigManager/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /tests/ConfigManager.Tests/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /tests/ConfigManager.Tests/ConfigClasses/TestConfig.cs: -------------------------------------------------------------------------------- 1 | namespace ConfigManager.Tests.ConfigClasses 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | public class TestConfig 9 | { 10 | public TestConfig() 11 | { 12 | Foo = "foo"; 13 | Bar = "bar"; 14 | Baz = "baz"; 15 | } 16 | public string Foo { get; set; } 17 | public string Bar { get; set; } 18 | public string Baz { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2013 Tom Jacques 2 | https://github.com/tejacques/ConfigManager 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/ConfigManager/ConfigClasses/ConfigPathsConfig.cs: -------------------------------------------------------------------------------- 1 | namespace ConfigManager.ConfigClasses 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Configuration; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Text; 9 | 10 | /// 11 | /// The strongly typed class to deserialize the 12 | /// global configuration file ConfigPaths.conf 13 | /// 14 | public class ConfigPathsConfig 15 | { 16 | /// 17 | /// An ordered list of search paths to search 18 | /// in for configuration files. 19 | /// 20 | public DirectoryInfo[] Paths { get; set; } 21 | 22 | /// 23 | /// Contructs the configuration with the default 24 | /// values for search paths, which are: the local 25 | /// application root directory, C:/Configs, and 26 | /// D:/Configs 27 | /// 28 | public ConfigPathsConfig() 29 | { 30 | DirectoryInfo baseDirectory = new DirectoryInfo( 31 | AppDomain.CurrentDomain.BaseDirectory); 32 | while (baseDirectory.Name.ToLowerInvariant().EndsWith("bin") 33 | || baseDirectory.Name.ToLowerInvariant().EndsWith("debug") 34 | || baseDirectory.Name.ToLowerInvariant().EndsWith("release")) 35 | { 36 | baseDirectory = baseDirectory.Parent; 37 | } 38 | Paths = new DirectoryInfo[] { baseDirectory }; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/ConfigManager/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("ConfigManager")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("ConfigManager")] 13 | [assembly: AssemblyCopyright("Copyright © 2013")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("efdf8c44-9edf-425a-ae80-ba986c87b2a1")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /tests/ConfigManager.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("ConfigManager.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("ConfigManager.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2013")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("1b2abcce-4806-4c8a-888a-e9e9a328e448")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/ConfigManager/ConfigManager.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.30110.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConfigManager", "ConfigManager.csproj", "{6DFDDA59-1266-45D7-8B54-332D7B686ACE}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConfigManager.Tests", "..\..\tests\ConfigManager.Tests\ConfigManager.Tests.csproj", "{61F2083F-81E0-44CF-A529-D65B74E6CC2B}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {6DFDDA59-1266-45D7-8B54-332D7B686ACE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {6DFDDA59-1266-45D7-8B54-332D7B686ACE}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {6DFDDA59-1266-45D7-8B54-332D7B686ACE}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {6DFDDA59-1266-45D7-8B54-332D7B686ACE}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {61F2083F-81E0-44CF-A529-D65B74E6CC2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {61F2083F-81E0-44CF-A529-D65B74E6CC2B}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {61F2083F-81E0-44CF-A529-D65B74E6CC2B}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {61F2083F-81E0-44CF-A529-D65B74E6CC2B}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | [Bb]in/ 3 | [Oo]bj/ 4 | 5 | # mstest test results 6 | TestResults 7 | 8 | ## Ignore Visual Studio temporary files, build results, and 9 | ## files generated by popular Visual Studio add-ons. 10 | 11 | # User-specific files 12 | *.suo 13 | *.user 14 | *.sln.docstates 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Rr]elease/ 19 | x64/ 20 | *_i.c 21 | *_p.c 22 | *.ilk 23 | *.meta 24 | *.obj 25 | *.pch 26 | *.pgc 27 | *.pgd 28 | *.rsp 29 | *.sbr 30 | *.tlb 31 | *.tli 32 | *.tlh 33 | *.tmp 34 | *.log 35 | *.vspscc 36 | *.vssscc 37 | .builds 38 | 39 | # Visual C++ cache files 40 | ipch/ 41 | *.aps 42 | *.ncb 43 | *.opensdf 44 | *.sdf 45 | 46 | # Visual Studio profiler 47 | *.psess 48 | *.vsp 49 | *.vspx 50 | 51 | # Guidance Automation Toolkit 52 | *.gpState 53 | 54 | # ReSharper is a .NET coding add-in 55 | _ReSharper* 56 | 57 | # NCrunch 58 | *.ncrunch* 59 | .*crunch*.local.xml 60 | 61 | # Installshield output folder 62 | [Ee]xpress 63 | 64 | # DocProject is a documentation generator add-in 65 | DocProject/buildhelp/ 66 | DocProject/Help/*.HxT 67 | DocProject/Help/*.HxC 68 | DocProject/Help/*.hhc 69 | DocProject/Help/*.hhk 70 | DocProject/Help/*.hhp 71 | DocProject/Help/Html2 72 | DocProject/Help/html 73 | 74 | # Click-Once directory 75 | publish 76 | 77 | # Publish Web Output 78 | *.Publish.xml 79 | 80 | # NuGet Packages Directory 81 | packages 82 | 83 | # Windows Azure Build Output 84 | csx 85 | *.build.csdef 86 | 87 | # Windows Store app package directory 88 | AppPackages/ 89 | 90 | # Others 91 | [Bb]in 92 | [Oo]bj 93 | sql 94 | TestResults 95 | [Tt]est[Rr]esult* 96 | *.Cache 97 | ClientBin 98 | [Ss]tyle[Cc]op.* 99 | ~$* 100 | *.dbmdl 101 | Generated_Code #added for RIA/Silverlight projects 102 | 103 | # Backup & report files from converting an old project file to a newer 104 | # Visual Studio version. Backup files are not needed, because we have git ;-) 105 | _UpgradeReport_Files/ 106 | Backup*/ 107 | UpgradeLog*.XML 108 | -------------------------------------------------------------------------------- /NuGet/ConfigManager/ConfigManager.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ConfigManager 5 | 2.0.1 6 | ConfigManager 7 | Tom Jacques 8 | Tom Jacques 9 | https://github.com/tejacques/ConfigManager/blob/master/LICENSE 10 | https://github.com/tejacques/ConfigManager 11 | false 12 | 13 | Binaries for the ConfigManager library. 14 | Visit https://github.com/tejacques/ConfigManager for an overview and usage examples. 15 | 16 | 17 | An open source helper library for retrieving strongly typed automatically updating configuration data from config files. 18 | 19 | 20 | Notes: 21 | 22 | Version 2.0.1: 23 | - Fix bug where LogException was called without a null check. 24 | 25 | Version 2.0.0: 26 | - Remove Parsed object from Configuration class 27 | - GetConfig now returns a copy of the configuration so that the Configuration held by the manager is immutable. 28 | - Remove NLog Dependency, instead user can provide Log and LogException functions to ConfigManager. 29 | 30 | Version 1.1.1: 31 | - Include source files and pdbs. 32 | 33 | Version 1.1.0: 34 | - Add a delegate to retrieve the configuration from a user defined location (ex: read config from a database). 35 | - Add a delegate to run if the configuration file is newer than the configuration from the user defined location (ex: update database). 36 | - Do not log an error for a missing configuration file, only if the file existed and failed to read. 37 | 38 | Version 1.0.3: 39 | - Update dependency version compatibility 40 | 41 | Version 1.0.2: 42 | - Add a DevMode setting. If true, ConfigManager will first check for config files ending with .dev.conf. 43 | Useful for having different settings on development. 44 | 45 | Version 1.0.1: 46 | - Fix a bug in updating files where the key had an incorrect substring check 47 | 48 | Version 1.0.0: 49 | - File handlers to automatically update config data in the running program 50 | - Generic functions to retrieve configuration data as a strongly typed object 51 | 52 | 53 | 54 | 55 | Copyright 2014 56 | Config Configuration auto updating 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ConfigManager 2 | ============= 3 | 4 | What is it? 5 | ----------- 6 | 7 | A json configuration file manager for C# / .NET / Mono 8 | 9 | How can I get it? 10 | ----------------- 11 | 12 | ConfigManager is available as a NuGet package: https://www.nuget.org/packages/ConfigManager/ 13 | 14 | ``` 15 | PM> Install-Package ConfigManager 16 | ``` 17 | 18 | Why was it made? 19 | ---------------- 20 | 21 | I wanted a strongly typed configuration system that would automatically update it's values in long running processes, so if the configuration file changed, the changes would propagate automatically without restarting the binary or interupting service. 22 | 23 | Example Usage 24 | ------------- 25 | 26 | ConfigManager is a globally available static class that is typically used as follows: 27 | 28 | ProjectDirectory/Config/configuredNames.conf 29 | ```json 30 | [ 31 | "Billy", 32 | "John", 33 | "Harold" 34 | ] 35 | ``` 36 | 37 | ```csharp 38 | public void Example() 39 | { 40 | List configuredNames = ConfigManager.GetCreateConfig>("configuredNames"); 41 | foreach(string name in configuredNames) 42 | { 43 | Console.WriteLine(name); 44 | } 45 | } 46 | ``` 47 | 48 | The output will be: 49 | ``` 50 | Billy 51 | John 52 | Harold 53 | ``` 54 | 55 | ### Config File Types ### 56 | 57 | Config Manager supports both json and yaml. The type is differentiated by the file extension. A .conf or .json file is deserialized as Json, a .yaml file is deserialized as Yaml. 58 | 59 | ### Creating a Configuration class to use 60 | 61 | Part of the magic is that ConfigManager can translate your config files to any strongly typed object you define. The only constraint is that it must implement new(). The reason why is so that everything that comes back from the command is guaranteed to not be null. 62 | 63 | ```csharp 64 | public class ExampleConfig 65 | { 66 | public string String { get; set; } 67 | public int Int { get; set; } 68 | public Website { get; set; } 69 | 70 | // Provide the default values of your config class here 71 | public ExampleConfig() 72 | { 73 | String = "Default String"; 74 | Int = 0; 75 | Website = "www.example.org"; 76 | } 77 | } 78 | ``` 79 | 80 | ProjectDirectory/Config/ExampleConfig.conf 81 | 82 | ```json 83 | { 84 | "String" : "Test", 85 | "Int" : 10, 86 | "Website" : "https://github.com/tejacques/ConfigManager/" 87 | } 88 | ``` 89 | 90 | ```csharp 91 | public void ExampleConfigTest() 92 | { 93 | ExampleConfig exampleConfig = ConfigManager.GetCreateConfig("ExampleConfig"); 94 | Console.WriteLine(exampleConfig.String); // Test 95 | Console.WriteLine(exampleConfig.Int); // 10 96 | Console.WriteLine(exampleConfig.Website); // https://github.com/tejacques/ConfigManager/ 97 | } 98 | ``` 99 | 100 | Additional Features 101 | ------------------- 102 | 103 | While in debug mode, ConfigManager will first look for files that end in `.dev.(conf|json|yaml)` with the given name, unless a fully specified file path is given. This is ideal for things that differ between dev and prod such as a connection string. 104 | -------------------------------------------------------------------------------- /src/ConfigManager/ConfigManager.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {6DFDDA59-1266-45D7-8B54-332D7B686ACE} 8 | Library 9 | Properties 10 | ConfigManager 11 | ConfigManager 12 | v4.5 13 | 512 14 | ..\..\..\..\src\ 15 | true 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | bin\Release\ConfigManager.XML 34 | 35 | 36 | 37 | False 38 | packages\Newtonsoft.Json.6.0.1\lib\net45\Newtonsoft.Json.dll 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | packages\YamlDotNet.3.1.1\lib\net35\YamlDotNet.dll 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 68 | -------------------------------------------------------------------------------- /tests/ConfigManager.Tests/ConfigManager.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {61F2083F-81E0-44CF-A529-D65B74E6CC2B} 8 | Library 9 | Properties 10 | ConfigManager.Tests 11 | ConfigManager.Tests 12 | v4.5 13 | 512 14 | ..\ 15 | true 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | False 37 | ..\..\src\ConfigManager\packages\Newtonsoft.Json.6.0.1\lib\net45\Newtonsoft.Json.dll 38 | 39 | 40 | ..\..\src\ConfigManager\packages\NUnit.2.6.3\lib\nunit.framework.dll 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | False 52 | ..\..\src\ConfigManager\packages\YamlDotNet.3.1.1\lib\net35\YamlDotNet.dll 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | {6dfdda59-1266-45d7-8b54-332d7b686ace} 73 | ConfigManager 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 88 | -------------------------------------------------------------------------------- /lib/ConfigManager.XML: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ConfigManager 5 | 6 | 7 | 8 | 9 | A data type containing metadata about a configuration item. 10 | 11 | 12 | 13 | 14 | The name of the configuration item. 15 | 16 | 17 | 18 | 19 | The raw configuration data. 20 | 21 | 22 | 23 | 24 | The time the configuration file was last updated. 25 | 26 | 27 | 28 | 29 | A bool indicating whether the configuration item came from 30 | ConfigManager or a user-specified source delegate. 31 | 32 | 33 | 34 | 35 | A data type containing metadata about a configuration file. 36 | 37 | 38 | 39 | 40 | The raw text read from the configuration file. 41 | 42 | 43 | 44 | 45 | The filepath to the configuration file. 46 | 47 | 48 | 49 | 50 | The time the configuration file was last updated. 51 | 52 | 53 | 54 | 55 | A utility class to make managing configuration files painless. 56 | ConfigManager uses json configuration files which map to strongly 57 | typed C# objects using ServiceStack.Text.DeserializeJson 58 | 59 | 60 | 61 | 62 | Adds a configuration to the manager. 63 | 64 | 65 | The type of the configuration object. 66 | 67 | 68 | The name of the configuration object to add. 69 | 70 | 71 | The file path (relative or absolute) to add. 72 | 73 | 74 | Optional boolean argument that specifies 75 | the config should be updated if it exists. 76 | 77 | 78 | 79 | 80 | Adds and then gets the configuration of a given name. 81 | 82 | 83 | The type of the configuration object. 84 | 85 | 86 | The name of the configuration object to add. 87 | 88 | 89 | The file path (relative or absolute) to add. 90 | 91 | 92 | Optional boolean argument that specifies the 93 | config should be updated if it exists. 94 | 95 | 96 | An object of type T with the values in the specified config file. 97 | 98 | 99 | 100 | 101 | Creates a Configuration of the given type 102 | from the specified file path. 103 | 104 | 105 | The type of the configuration object. 106 | 107 | 108 | The name of the configuration. 109 | 110 | 111 | The file path (relative or absolute) of the configuration file. 112 | 113 | 114 | An object of type T with the values in the specified config file. 115 | 116 | 117 | 118 | 119 | Gets a configuration item from a file. 120 | 121 | The name of the configuration. 122 | The path of the file. 123 | 124 | The configuration item pulled from the specified file. 125 | 126 | 127 | 128 | 129 | Get a configuration item from a delegate. 130 | 131 | The name of the configuration. 132 | The configuration item pulled from the delegate. 133 | 134 | 135 | 136 | Remove the specified config file from the ConfigManager 137 | 138 | The specified config file key 139 | 140 | 141 | 142 | Returns the configuration object of the specified type and name 143 | from the in-memory dictionary of the ConfigManager. 144 | 145 | The type of the configuration object. 146 | 147 | The name of the configuration 148 | object to retrieve. 149 | An object of type T with the values in the 150 | ConfigManager. 151 | 152 | 153 | 154 | Returns the DateTime that the specified file was last written to. 155 | 156 | The file path. 157 | 158 | The DateTime that the specified file was last written to. 159 | 160 | 161 | 162 | 163 | Reads a relative or absolute file path and returns the contents. 164 | Nonrooted file paths are checked relative to the configured 165 | search directories. 166 | 167 | The file path. 168 | The contents of the file that was found, or empty string 169 | if no file present. 170 | 171 | 172 | 173 | Parses json into the specified object type. 174 | 175 | The object type to return. 176 | The raw json. 177 | 178 | The contents of the raw json as the specified object type. 179 | 180 | 181 | 182 | 183 | Development mode. If true, ConfigManager will look for 184 | files ending with .dev.conf rather than .conf 185 | 186 | 187 | 188 | 189 | User provided Log function. 190 | 191 | 192 | 193 | 194 | User provided LogException function 195 | 196 | 197 | 198 | 199 | A delegate settable by the user which says how to try 200 | and get the configuration before going to the file system. 201 | 202 | 203 | 204 | 205 | A delegate settable by the user which says what to do after 206 | getting the configuration from the file system. 207 | 208 | 209 | 210 | 211 | A Utility Enum to ensure that typos aren't make for 212 | global configuration files. 213 | 214 | 215 | 216 | 217 | Maps to the string of the same name. 218 | 219 | 220 | 221 | 222 | The strongly typed class to deserialize the 223 | global configuration file ConfigPaths.conf 224 | 225 | 226 | 227 | 228 | Contructs the configuration with the default 229 | values for search paths, which are: the local 230 | application root directory, C:/Configs, and 231 | D:/Configs 232 | 233 | 234 | 235 | 236 | An ordered list of search paths to search 237 | in for configuration files. 238 | 239 | 240 | 241 | 242 | -------------------------------------------------------------------------------- /tests/ConfigManager.Tests/ConfigManagerTests.cs: -------------------------------------------------------------------------------- 1 | namespace ConfigManager.Tests 2 | { 3 | using ConfigClasses; 4 | using Newtonsoft.Json; 5 | using Newtonsoft.Json.Linq; 6 | using NUnit.Framework; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.IO; 10 | using System.Linq; 11 | using System.Text; 12 | using System.Threading; 13 | using System.Threading.Tasks; 14 | 15 | [TestFixture] 16 | public class ConfigManagerTests 17 | { 18 | private static int loops; 19 | private static string json = @" 20 | [ 21 | ""Foo"", 22 | ""Bar"", 23 | ""Baz"", 24 | ""1"",""2"",""3"",""4"",""5"",""6"",""7"",""8"",""9"",""10"", 25 | ""1"",""2"",""3"",""4"",""5"",""6"",""7"",""8"",""9"",""10"", 26 | ""1"",""2"",""3"",""4"",""5"",""6"",""7"",""8"",""9"",""10"", 27 | ""1"",""2"",""3"",""4"",""5"",""6"",""7"",""8"",""9"",""10"", 28 | ""1"",""2"",""3"",""4"",""5"",""6"",""7"",""8"",""9"",""10"" 29 | ] 30 | "; 31 | [TestFixtureSetUp] 32 | public void SetUp() 33 | { 34 | loops = 100000; 35 | ConfigManager.DevMode = true; 36 | var parsedJsonDotNet = JsonConvert.DeserializeObject>(json); 37 | } 38 | 39 | [Test] 40 | public static void TestConfigManager() 41 | { 42 | TestConfig config = 43 | ConfigManager.GetCreateConfig("TestDefault"); 44 | Assert.NotNull(config); 45 | } 46 | 47 | [Test] 48 | public static void TestConfigParse() 49 | { 50 | TestConfig config = ConfigManager.GetCreateConfig 51 | ("Test1"); 52 | Assert.NotNull(config); 53 | Assert.AreEqual("1", config.Foo); 54 | Assert.AreEqual("2", config.Bar); 55 | Assert.AreEqual("3", config.Baz); 56 | } 57 | 58 | [Test] 59 | public static void TestConfigParseJson() 60 | { 61 | TestConfig config = ConfigManager.GetCreateConfig 62 | ("TestJson"); 63 | Assert.NotNull(config); 64 | Assert.AreEqual("1", config.Foo); 65 | Assert.AreEqual("2", config.Bar); 66 | Assert.AreEqual("3", config.Baz); 67 | } 68 | 69 | [Test] 70 | public static void TestConfigParseYaml() 71 | { 72 | TestConfig config = ConfigManager.GetCreateConfig 73 | ("TestYaml"); 74 | Assert.NotNull(config); 75 | Assert.AreEqual("1", config.Foo); 76 | Assert.AreEqual("2", config.Bar); 77 | Assert.AreEqual("3", config.Baz); 78 | } 79 | 80 | [Test] 81 | public static void TestConfigParseSerial() 82 | { 83 | for (int i = 0; i < loops; i++) 84 | { 85 | TestConfig config = ConfigManager.GetCreateConfig 86 | ("Test2"); 87 | Assert.NotNull(config); 88 | Assert.AreEqual("a", config.Foo); 89 | Assert.AreEqual("b", config.Bar); 90 | Assert.AreEqual("c", config.Baz); 91 | } 92 | } 93 | 94 | [Test] 95 | public static void TestConfigParseSerialYaml() 96 | { 97 | for (int i = 0; i < loops; i++) 98 | { 99 | TestConfig config = ConfigManager.GetCreateConfig 100 | ("TestYaml2"); 101 | Assert.NotNull(config); 102 | Assert.AreEqual("a", config.Foo); 103 | Assert.AreEqual("b", config.Bar); 104 | Assert.AreEqual("c", config.Baz); 105 | } 106 | } 107 | 108 | [Test] 109 | public static void TestConfigParseParallel() 110 | { 111 | Parallel.For(0, loops, (x) => 112 | { 113 | TestConfig config = ConfigManager.GetCreateConfig 114 | ("Test3"); 115 | Assert.NotNull(config); 116 | Assert.AreEqual("!", config.Foo); 117 | Assert.AreEqual("@", config.Bar); 118 | Assert.AreEqual("#", config.Baz); 119 | }); 120 | } 121 | 122 | [Test] 123 | public static void TestConfigParseParallelYaml() 124 | { 125 | Parallel.For(0, loops, (x) => 126 | { 127 | TestConfig config = ConfigManager.GetCreateConfig 128 | ("TestYaml3"); 129 | Assert.NotNull(config); 130 | Assert.AreEqual("!", config.Foo); 131 | Assert.AreEqual("@", config.Bar); 132 | Assert.AreEqual("#", config.Baz); 133 | }); 134 | } 135 | 136 | [Test] 137 | public static void TestConfigParseMultipleParallel() 138 | { 139 | Parallel.For(0, loops, (x) => 140 | { 141 | TestConfig config = ConfigManager.GetCreateConfig 142 | ("Test_" + (x % 100), ""); 143 | Assert.NotNull(config); 144 | Assert.AreEqual("foo", config.Foo); 145 | Assert.AreEqual("bar", config.Bar); 146 | Assert.AreEqual("baz", config.Baz); 147 | }); 148 | } 149 | 150 | [Test] 151 | public static void TestConfigEvents() 152 | { 153 | // Test getting the config when the config file doesn't exist 154 | TestConfig config = 155 | ConfigManager.GetCreateConfig("TestUpdate"); 156 | Assert.NotNull(config); 157 | Assert.AreEqual("foo", config.Foo); 158 | Assert.AreEqual("bar", config.Bar); 159 | Assert.AreEqual("baz", config.Baz); 160 | 161 | // Create a new config file based on Test1 162 | string path = "TestUpdate.conf"; 163 | using (StreamWriter sw = new StreamWriter(path, append:true)) 164 | { 165 | sw.Write(JsonConvert.SerializeObject( 166 | ConfigManager 167 | .GetCreateConfig("Test1"))); 168 | sw.Flush(); 169 | } 170 | 171 | // Sleep to let the ConfigManager deal with the events 172 | Thread.Sleep(100); 173 | 174 | // Re-grab the config from the ConfigManager 175 | config = ConfigManager.GetCreateConfig("TestUpdate"); 176 | Assert.NotNull(config); 177 | Assert.AreEqual("1", config.Foo); 178 | Assert.AreEqual("2", config.Bar); 179 | Assert.AreEqual("3", config.Baz); 180 | 181 | using (StreamWriter sw = new StreamWriter(path, false)) 182 | { 183 | sw.Write(JsonConvert.SerializeObject( 184 | ConfigManager 185 | .GetCreateConfig("Test2"))); 186 | sw.Flush(); 187 | } 188 | 189 | // Sleep to let the ConfigManager deal with the events 190 | Thread.Sleep(100); 191 | 192 | // Re-grab the config from the ConfigManager 193 | config = ConfigManager.GetCreateConfig("TestUpdate"); 194 | Assert.NotNull(config); 195 | Assert.AreEqual("a", config.Foo); 196 | Assert.AreEqual("b", config.Bar); 197 | Assert.AreEqual("c", config.Baz); 198 | 199 | // Delete the config file 200 | if (File.Exists(path)) 201 | { 202 | File.Delete(path); 203 | } 204 | 205 | // Sleep to let the ConfigManager deal with the events 206 | Thread.Sleep(100); 207 | 208 | // Re-grab the config from the ConfigManager 209 | config = ConfigManager.GetCreateConfig("TestUpdate"); 210 | Assert.NotNull(config); 211 | Assert.AreEqual("foo", config.Foo); 212 | Assert.AreEqual("bar", config.Bar); 213 | Assert.AreEqual("baz", config.Baz); 214 | } 215 | 216 | [Test] 217 | public static void TestConfigEventsYaml() 218 | { 219 | // Test getting the config when the config file doesn't exist 220 | TestConfig config = 221 | ConfigManager.GetCreateConfig("TestUpdateYaml"); 222 | Assert.NotNull(config); 223 | Assert.AreEqual("foo", config.Foo); 224 | Assert.AreEqual("bar", config.Bar); 225 | Assert.AreEqual("baz", config.Baz); 226 | 227 | // Create a new config file based on Test1 228 | string path = "TestUpdateYaml.yaml"; 229 | using (StreamWriter sw = new StreamWriter(path, append: true)) 230 | { 231 | var serializer = new YamlDotNet.Serialization.Serializer(); 232 | serializer.Serialize(sw, 233 | ConfigManager 234 | .GetCreateConfig("Test1")); 235 | 236 | sw.Flush(); 237 | } 238 | 239 | // Sleep to let the ConfigManager deal with the events 240 | Thread.Sleep(100); 241 | 242 | // Re-grab the config from the ConfigManager 243 | config = ConfigManager.GetCreateConfig("TestUpdateYaml"); 244 | Assert.NotNull(config); 245 | Assert.AreEqual("1", config.Foo); 246 | Assert.AreEqual("2", config.Bar); 247 | Assert.AreEqual("3", config.Baz); 248 | 249 | using (StreamWriter sw = new StreamWriter(path, append: false)) 250 | { 251 | var serializer = new YamlDotNet.Serialization.Serializer(); 252 | serializer.Serialize(sw, 253 | ConfigManager 254 | .GetCreateConfig("Test2")); 255 | 256 | sw.Flush(); 257 | } 258 | 259 | // Sleep to let the ConfigManager deal with the events 260 | Thread.Sleep(100); 261 | 262 | // Re-grab the config from the ConfigManager 263 | config = ConfigManager.GetCreateConfig("TestUpdateYaml"); 264 | Assert.NotNull(config); 265 | Assert.AreEqual("a", config.Foo); 266 | Assert.AreEqual("b", config.Bar); 267 | Assert.AreEqual("c", config.Baz); 268 | 269 | // Delete the config file 270 | if (File.Exists(path)) 271 | { 272 | File.Delete(path); 273 | } 274 | 275 | // Sleep to let the ConfigManager deal with the events 276 | Thread.Sleep(100); 277 | 278 | // Re-grab the config from the ConfigManager 279 | config = ConfigManager.GetCreateConfig("TestUpdateYaml"); 280 | Assert.NotNull(config); 281 | Assert.AreEqual("foo", config.Foo); 282 | Assert.AreEqual("bar", config.Bar); 283 | Assert.AreEqual("baz", config.Baz); 284 | } 285 | 286 | [TestFixtureTearDown] 287 | public static void TearDown() 288 | { 289 | // Delete the config file in case there was an error 290 | string[] paths = { "TestUpdate.conf", "TestUpdateYaml.yaml" }; 291 | 292 | foreach (string path in paths) 293 | { 294 | if (File.Exists(path)) 295 | { 296 | File.Delete(path); 297 | } 298 | } 299 | } 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /src/ConfigManager/ConfigManager.cs: -------------------------------------------------------------------------------- 1 | namespace ConfigManager 2 | { 3 | using ConfigClasses; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Collections.Concurrent; 7 | using System.Diagnostics; 8 | using System.IO; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Timers; 12 | using System.Threading.Tasks; 13 | 14 | /// 15 | /// The type of configuration file 16 | /// 17 | public enum ConfigType 18 | { 19 | /// 20 | /// The config file type is unknown 21 | /// 22 | Unknown, 23 | /// 24 | /// The config file is Json 25 | /// 26 | Json, 27 | /// 28 | /// The config file is Yaml 29 | /// 30 | Yaml 31 | } 32 | 33 | /// 34 | /// A data type containing metadata about a configuration item. 35 | /// 36 | public class ConfigurationItem 37 | { 38 | /// 39 | /// The name of the configuration item. 40 | /// 41 | public string Name { get; set; } 42 | 43 | /// 44 | /// The raw configuration data. 45 | /// 46 | public string Data { get; set; } 47 | 48 | /// 49 | /// The time the configuration file was last updated. 50 | /// 51 | public DateTime LastUpdated { get; set; } 52 | 53 | /// 54 | /// A bool indicating whether the configuration item came from 55 | /// ConfigManager or a user-specified source delegate. 56 | /// 57 | public bool FromConfigManager { get; set; } 58 | } 59 | 60 | /// 61 | /// A data type containing metadata about a configuration file. 62 | /// 63 | public class Configuration 64 | { 65 | /// 66 | /// The raw text read from the configuration file. 67 | /// 68 | public string Raw { get; set; } 69 | /// 70 | /// The parsed object 71 | /// 72 | public object Parsed { get; set; } 73 | /// 74 | /// The filepath to the configuration file. 75 | /// 76 | public string FilePath { get; set; } 77 | /// 78 | /// The time the configuration file was last updated. 79 | /// 80 | public DateTime LastUpdated { get; set; } 81 | /// 82 | /// The type of config file. 83 | /// 84 | public ConfigType ConfigType { get; set; } 85 | } 86 | 87 | /// 88 | /// A utility class to make managing configuration files painless. 89 | /// ConfigManager uses json configuration files which map to strongly 90 | /// typed C# objects using ServiceStack.Text.DeserializeJson 91 | /// 92 | public class ConfigManager 93 | { 94 | private static ConcurrentDictionary _configs; 95 | private static List _fsWatchers; 96 | private static readonly object _fsWatcherLock = new object(); 97 | private static readonly YamlDotNet 98 | .Serialization 99 | .Deserializer _yamlDeserializer = 100 | new YamlDotNet 101 | .Serialization 102 | .Deserializer(null, null, true); 103 | 104 | private static Func _getConfiguration; 105 | private static Action _putConfiguration; 106 | 107 | /// 108 | /// Development mode. If true, ConfigManager will look for 109 | /// files ending with .dev.conf rather than .conf 110 | /// 111 | public static bool DevMode { get; set; } 112 | 113 | /// 114 | /// User provided Log function. 115 | /// 116 | public static Action Log{ get; set; } 117 | 118 | /// 119 | /// User provided LogException function 120 | /// 121 | public static Action LogException { get; set; } 122 | 123 | /// 124 | /// A Utility Enum to ensure that typos aren't make for 125 | /// global configuration files. 126 | /// 127 | public enum ConfigKeys 128 | { 129 | /// 130 | /// Maps to the string of the same name. 131 | /// 132 | ConfigPathsConfig 133 | } 134 | 135 | static ConfigManager() 136 | { 137 | _configs = new ConcurrentDictionary(); 138 | 139 | // TJ: Add the ConfigManagerConfig file default 140 | // to the class, then attempt to load 141 | AddConfig( 142 | ConfigKeys.ConfigPathsConfig.ToString()); 143 | AddConfig( 144 | ConfigKeys.ConfigPathsConfig.ToString(), update: true); 145 | 146 | SetupWatchDirectories(); 147 | } 148 | 149 | /// 150 | /// A delegate settable by the user which says how to try 151 | /// and get the configuration before going to the file system. 152 | /// 153 | public static Func GetConfiguration 154 | { 155 | private get { return _getConfiguration; } 156 | set { _getConfiguration = value; } 157 | } 158 | 159 | /// 160 | /// A delegate settable by the user which says what to do after 161 | /// getting the configuration from the file system. 162 | /// 163 | public static Action PutConfiguration 164 | { 165 | private get { return _putConfiguration; } 166 | set { _putConfiguration = value; } 167 | } 168 | 169 | /// 170 | /// Adds a configuration to the manager. 171 | /// 172 | /// 173 | /// The type of the configuration object. 174 | /// 175 | /// 176 | /// The name of the configuration object to add. 177 | /// 178 | /// 179 | /// The file path (relative or absolute) to add. 180 | /// 181 | /// 182 | /// Optional boolean argument that specifies 183 | /// the config should be updated if it exists. 184 | /// 185 | public static void AddConfig( 186 | string configName, 187 | string configPath = null, 188 | bool update = false) 189 | where T : new() 190 | { 191 | if (!update && _configs.ContainsKey(configName)) 192 | { 193 | return; 194 | } 195 | 196 | if (configPath == null) 197 | { 198 | var tmpPath = configName + ".conf"; 199 | configPath = tmpPath; 200 | 201 | if (FileExists(tmpPath)) 202 | { 203 | configPath = tmpPath; 204 | } 205 | else if (FileExists(tmpPath = configName + ".json")) 206 | { 207 | configPath = tmpPath; 208 | } 209 | else if (FileExists(tmpPath = configName + ".yaml")) 210 | { 211 | configPath = tmpPath; 212 | } 213 | } 214 | 215 | _configs.AddOrUpdate(configName, 216 | (x) => 217 | { 218 | return CreateConfig(configName, configPath); 219 | }, 220 | (x, y) => 221 | { 222 | if (update) 223 | { 224 | y = CreateConfig(configName, configPath); 225 | } 226 | return y; 227 | }); 228 | } 229 | 230 | /// 231 | /// Adds and then gets the configuration of a given name. 232 | /// 233 | /// 234 | /// The type of the configuration object. 235 | /// 236 | /// 237 | /// The name of the configuration object to add. 238 | /// 239 | /// 240 | /// The file path (relative or absolute) to add. 241 | /// 242 | /// 243 | /// Optional boolean argument that specifies the 244 | /// config should be updated if it exists. 245 | /// 246 | /// 247 | /// An object of type T with the values in the specified config file. 248 | /// 249 | public static T GetCreateConfig( 250 | string configName, 251 | string configPath = null, 252 | bool update = false, 253 | bool cached = true) 254 | where T : new() 255 | { 256 | AddConfig(configName, configPath: configPath, update: update); 257 | return GetConfig(configName, cached); 258 | } 259 | 260 | /// 261 | /// Creates a Configuration of the given type 262 | /// from the specified file path. 263 | /// 264 | /// 265 | /// The type of the configuration object. 266 | /// 267 | /// 268 | /// The name of the configuration. 269 | /// 270 | /// 271 | /// The file path (relative or absolute) of the configuration file. 272 | /// 273 | /// 274 | /// An object of type T with the values in the specified config file. 275 | /// 276 | private static Configuration CreateConfig( 277 | string configName, string configPath) 278 | where T : new() 279 | { 280 | Configuration config = new Configuration(); 281 | 282 | var path = GetPath(configPath); 283 | var fromFile = GetConfigFromFile(configName, path); 284 | var fromDelegate = GetConfigFromDelegate(configName); 285 | HandlePutConfig(fromFile, fromDelegate); 286 | 287 | var newest = fromFile.LastUpdated > fromDelegate.LastUpdated 288 | ? fromFile 289 | : fromDelegate; 290 | 291 | config.FilePath = path; 292 | config.LastUpdated = newest.LastUpdated; 293 | config.Raw = newest.Data; 294 | 295 | if (config.FilePath.Length >= 5) 296 | { 297 | var extension = config 298 | .FilePath 299 | .Substring(config.FilePath.Length - 5, 5) 300 | .ToLowerInvariant(); 301 | 302 | switch(extension) 303 | { 304 | case ".conf": 305 | case ".json": 306 | config.ConfigType = ConfigType.Json; 307 | break; 308 | case ".yaml": 309 | config.ConfigType = ConfigType.Yaml; 310 | break; 311 | default: 312 | config.ConfigType = ConfigType.Unknown; 313 | break; 314 | } 315 | } 316 | 317 | config.Parsed = ParseConfig(config.Raw, config.ConfigType); 318 | 319 | return config; 320 | } 321 | 322 | /// 323 | /// Gets a configuration item from a file. 324 | /// 325 | /// The name of the configuration. 326 | /// The path of the file. 327 | /// 328 | /// The configuration item pulled from the specified file. 329 | /// 330 | private static ConfigurationItem GetConfigFromFile( 331 | string name, string path) 332 | { 333 | ConfigurationItem configItem = new ConfigurationItem(); 334 | 335 | configItem.Name = name; 336 | configItem.Data = ReadConfig(path); 337 | configItem.LastUpdated = GetConfigUpdateTime(path); 338 | configItem.FromConfigManager = true; 339 | 340 | return configItem; 341 | } 342 | 343 | /// 344 | /// Get a configuration item from a delegate. 345 | /// 346 | /// The name of the configuration. 347 | /// The configuration item pulled from the delegate. 348 | private static ConfigurationItem GetConfigFromDelegate(string name) 349 | { 350 | ConfigurationItem configItem = null; 351 | 352 | if(null != _getConfiguration) 353 | { 354 | configItem = _getConfiguration(name); 355 | } 356 | 357 | if (null == configItem) 358 | { 359 | configItem = new ConfigurationItem(); 360 | } 361 | 362 | configItem.Name = name; 363 | 364 | return configItem; 365 | } 366 | 367 | private static void HandlePutConfig( 368 | ConfigurationItem fromFile, 369 | ConfigurationItem fromDelegate) 370 | { 371 | if (null != _putConfiguration 372 | && fromFile.Name == fromDelegate.Name 373 | && fromFile.LastUpdated > fromDelegate.LastUpdated 374 | && !string.IsNullOrEmpty(fromFile.Data)) 375 | { 376 | Task.Run(() => _putConfiguration(fromFile)); 377 | } 378 | } 379 | 380 | private static string GetPath(string configPath) 381 | { 382 | string devConfigPath = ""; 383 | if (DevMode) 384 | { 385 | devConfigPath = configPath 386 | .Replace(".conf", ".dev.conf") 387 | .Replace(".json", ".dev.json") 388 | .Replace(".yaml", ".dev.yaml"); 389 | } 390 | 391 | if (!Path.IsPathRooted(configPath)) 392 | { 393 | DirectoryInfo[] paths = GetConfig( 394 | ConfigKeys.ConfigPathsConfig.ToString()).Paths; 395 | foreach (DirectoryInfo path in paths) 396 | { 397 | // TJ: Skip over nonexistant paths 398 | if (path == null || !path.Exists) 399 | { 400 | continue; 401 | } 402 | 403 | FileInfo fi = null; 404 | 405 | if (DevMode) 406 | { 407 | fi = path.EnumerateFiles( 408 | devConfigPath, 409 | SearchOption.AllDirectories).FirstOrDefault(); 410 | } 411 | 412 | if (null == fi) 413 | { 414 | fi = path.EnumerateFiles( 415 | configPath, 416 | SearchOption.AllDirectories).FirstOrDefault(); 417 | } 418 | if (fi != null) 419 | { 420 | configPath = fi.FullName; 421 | break; 422 | } 423 | } 424 | } 425 | 426 | return configPath; 427 | } 428 | 429 | private static void SetupWatchDirectories() 430 | { 431 | lock (_fsWatcherLock) 432 | { 433 | _fsWatchers = new List(); 434 | 435 | DirectoryInfo[] paths = GetConfig( 436 | ConfigKeys.ConfigPathsConfig.ToString()).Paths; 437 | 438 | var fsEventHandler = 439 | new FileSystemEventHandler(HandleUpdate); 440 | var errorEventHandler = 441 | new ErrorEventHandler(HandleError); 442 | var renamedEventHandler = 443 | new RenamedEventHandler(HandleRename); 444 | 445 | string[] fileTypes = { "*.conf", "*.json", "*.yaml"}; 446 | 447 | _fsWatchers.AddRange(paths 448 | .Where(path => null != path && path.Exists) 449 | .Select(path => 450 | { 451 | List fileWatchers = 452 | new List(); 453 | 454 | foreach(var fileType in fileTypes) 455 | { 456 | var fsWatcher = new FileSystemWatcher( 457 | path.FullName, fileType); 458 | 459 | fsWatcher.NotifyFilter = 460 | NotifyFilters.LastWrite 461 | | NotifyFilters.FileName 462 | | NotifyFilters.DirectoryName; 463 | 464 | fsWatcher.IncludeSubdirectories = true; 465 | 466 | fsWatcher.Changed += fsEventHandler; 467 | fsWatcher.Created += fsEventHandler; 468 | fsWatcher.Deleted += fsEventHandler; 469 | fsWatcher.Error += errorEventHandler; 470 | fsWatcher.Renamed += renamedEventHandler; 471 | 472 | fsWatcher.EnableRaisingEvents = true; 473 | 474 | fileWatchers.Add(fsWatcher); 475 | } 476 | 477 | return fileWatchers; 478 | }) 479 | .SelectMany(fileWatchers => fileWatchers)); 480 | } 481 | } 482 | 483 | private static void TearDownWatchDirectories() 484 | { 485 | lock (_fsWatcherLock) 486 | { 487 | if (null == _fsWatchers) 488 | { 489 | return; 490 | } 491 | 492 | foreach (var fsWatcher in _fsWatchers) 493 | { 494 | fsWatcher.Dispose(); 495 | } 496 | 497 | _fsWatchers = null; 498 | } 499 | } 500 | 501 | /// 502 | /// Remove the specified config file from the ConfigManager 503 | /// 504 | /// The specified config file key 505 | public static void RemoveConfig(string key) 506 | { 507 | Configuration config; 508 | 509 | key = key.Split('\\', '/').LastOrDefault(); 510 | 511 | string conf = ".conf"; 512 | string json = ".json"; 513 | string yaml = ".yaml"; 514 | 515 | if (key.EndsWith(conf) 516 | || key.EndsWith(json) 517 | || key.EndsWith(yaml)) 518 | { 519 | key = key.Substring(0, key.Length - conf.Length); 520 | } 521 | 522 | if (DevMode) 523 | { 524 | string dev = ".dev"; 525 | if (key.EndsWith(dev)) 526 | { 527 | key = key.Substring(0, key.Length - dev.Length); 528 | } 529 | } 530 | 531 | _configs.TryRemove(key, out config); 532 | 533 | if (ConfigKeys.ConfigPathsConfig.ToString() == key) 534 | { 535 | TearDownWatchDirectories(); 536 | SetupWatchDirectories(); 537 | } 538 | } 539 | 540 | private static void HandleUpdate(object sender, FileSystemEventArgs e) 541 | { 542 | RemoveConfig(e.Name); 543 | } 544 | 545 | private static void HandleError(object sender, ErrorEventArgs e) 546 | { 547 | if (null != LogException) 548 | { 549 | LogException( 550 | "Exception thrown by FileSystemWatcher", 551 | e.GetException()); 552 | } 553 | } 554 | 555 | private static void HandleRename(object sender, RenamedEventArgs e) 556 | { 557 | RemoveConfig(e.OldName); 558 | RemoveConfig(e.Name); 559 | } 560 | 561 | private static bool FileExists(string path) 562 | { 563 | return File.Exists(GetPath(path)); 564 | } 565 | 566 | /// 567 | /// Returns the configuration object of the specified type and name 568 | /// from the in-memory dictionary of the ConfigManager. 569 | /// 570 | /// The type of the configuration object. 571 | /// 572 | /// The name of the configuration 573 | /// object to retrieve. 574 | /// An object of type T with the values in the 575 | /// ConfigManager. 576 | public static T GetConfig(string configName, bool cached = true) 577 | where T : new() 578 | { 579 | Configuration configuration; 580 | _configs.TryGetValue(configName, out configuration); 581 | return configuration == null 582 | ? new T() 583 | : cached 584 | ? (T)configuration.Parsed 585 | : ParseConfig( 586 | configuration.Raw, 587 | configuration.ConfigType); 588 | } 589 | 590 | /// 591 | /// Returns the DateTime that the specified file was last written to. 592 | /// 593 | /// The file path. 594 | /// 595 | /// The DateTime that the specified file was last written to. 596 | /// 597 | public static DateTime GetConfigUpdateTime(string configPath) 598 | { 599 | DateTime t = DateTime.MinValue; 600 | try 601 | { 602 | t = File.GetLastWriteTime(configPath); 603 | } 604 | catch (Exception e) 605 | { 606 | if (null != LogException) 607 | { 608 | LogException( 609 | "ConfigManager Error: could not read file creation time: " 610 | + configPath, e); 611 | } 612 | } 613 | 614 | return t; 615 | } 616 | 617 | /// 618 | /// Reads a relative or absolute file path and returns the contents. 619 | /// Nonrooted file paths are checked relative to the configured 620 | /// search directories. 621 | /// 622 | /// The file path. 623 | /// The contents of the file that was found, or empty string 624 | /// if no file present. 625 | public static string ReadConfig(string configPath) 626 | { 627 | string config = ""; 628 | // TJ: Check the configPath 629 | try 630 | { 631 | config = File.ReadAllText(configPath); 632 | } 633 | catch (System.IO.FileNotFoundException) 634 | { 635 | // Ignore 636 | } 637 | catch (Exception e) 638 | { 639 | if (null != LogException) 640 | { 641 | LogException( 642 | "ConfigManager Error: could not read file: " 643 | + configPath, e); 644 | } 645 | } 646 | 647 | return config; 648 | } 649 | 650 | /// 651 | /// Parses json into the specified object type. 652 | /// 653 | /// The object type to return. 654 | /// The raw config contents. 655 | /// The type of config file. 656 | /// 657 | /// The contents of the raw json as the specified object type. 658 | /// 659 | public static T ParseConfig( 660 | string configRaw, 661 | ConfigType configType = ConfigType.Json) 662 | where T : new() 663 | { 664 | T config = default(T); 665 | if (configRaw == null) 666 | { 667 | if (null != Log) 668 | { 669 | Log(string.Format( 670 | "ConfigManager Error: " 671 | + "Cannot deserialize null string for Type {0}", 672 | typeof(T).Name)); 673 | } 674 | } 675 | else if (configRaw != string.Empty) 676 | { 677 | switch(configType) 678 | { 679 | case ConfigType.Json: 680 | config = Newtonsoft 681 | .Json 682 | .JsonConvert 683 | .DeserializeObject(configRaw); 684 | break; 685 | case ConfigType.Yaml: 686 | config = _yamlDeserializer.Deserialize( 687 | new StringReader(configRaw)); 688 | break; 689 | default: 690 | break; 691 | } 692 | 693 | 694 | if (null == config && null != Log) 695 | { 696 | // Deserializing configraw failed, log configraw 697 | Log(string.Format( 698 | "ConfigManager Error: " 699 | +@"Unable to deserialize string ""{0}"" to Type {1}", 700 | configRaw, 701 | typeof(T).Name)); 702 | } 703 | } 704 | 705 | if (config == null) 706 | { 707 | config = new T(); 708 | } 709 | return config; 710 | } 711 | } 712 | } 713 | --------------------------------------------------------------------------------