I am researching about how to load the startup config in .Net Core. I notice there are different ways to do that, I have seen XML, JSON, init file, and from a Dictionary located in memory (I will go back to this later). I'm using something like the code below:
new ConfigurationBuilder().AddJsonFile("file.json").Build();
All of that is OK, but, isn't there any way to load that configuration from a JSON string? I mean, I don't want to store the json in a temporal file because is a real-time built file and It makes no sense.
About the dictionary located in memory. Its easy to build it manually, but, What about for complex and too hierarchical JSON structures? As far as I know, the dictionary is
dictionary < string, string >
whose key is the parents of the tree concatenated by ":" managing at the same time the repeated nodes, enumerating them, etc. A pain build this algorithm from scratch.
I liked Adam's answer a lot, but the interface implementations he linked to were a bit monolithic. Here is a smaller one:
public class InMemoryFileProvider : IFileProvider
{
private class InMemoryFile : IFileInfo
{
private readonly byte[] _data;
public InMemoryFile(string json) => _data = Encoding.UTF8.GetBytes(json);
public Stream CreateReadStream() => new MemoryStream(_data);
public bool Exists { get; } = true;
public long Length => _data.Length;
public string PhysicalPath { get; } = string.Empty;
public string Name { get; } = string.Empty;
public DateTimeOffset LastModified { get; } = DateTimeOffset.UtcNow;
public bool IsDirectory { get; } = false;
}
private readonly IFileInfo _fileInfo;
public InMemoryFileProvider(string json) => _fileInfo = new InMemoryFile(json);
public IFileInfo GetFileInfo(string _) => _fileInfo;
public IDirectoryContents GetDirectoryContents(string _) => null;
public IChangeToken Watch(string _) => NullChangeToken.Singleton;
}
Then, as per Adam's answer, you can use:
var memoryFileProvider = new InMemoryFileProvider(jsonString);
var configuration = new ConfigurationBuilder()
.AddJsonFile(memoryFileProvider, "appsettings.json", false, false)
.Build();
You can re-use the existing API (Microsoft.Extensions.Configuration.Json) relatively easily by implementing an in-memory file provider.
You'll need
- a dummy IFileProvider implementation - something like the one used here.
- a dummy IFileInfo implementation - you can borrow one from here.
The code below demonstrates how to assemble all this:
var json = "{ \"option1\": 1, \"option2\": \"abc\", }";
var memoryJsonFile = new MemoryFileInfo("config.json", Encoding.UTF8.GetBytes(json), DateTimeOffset.Now);
var memoryFileProvider = new MockFileProvider(memoryJsonFile);
var configuration = new ConfigurationBuilder()
.AddJsonFile(memoryFileProvider, "config.json", false, false)
.Build();
Console.WriteLine(configuration["option2"]);
And there you go ;)
In ASPNETCORE 2.0 (not sure about other versions) you can use config.AddInMemoryCollection
:
var host = new WebHostBuilder()
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddInMemoryCollection(new Dictionary<string, string>()
{
{ "MyParentKey:MySubKey", "MyValue" }
});
});
UPDATE:
I've adapted some code from the link below to parse a JSON string and return a Dictionary:
https://github.com/aspnet/Configuration/blob/d469707ab18eef7ed0002f00175a9ad5b0f36250/src/Config.Json/JsonConfigurationFileParser.cs
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
namespace Config
{
public class JsonConfigurationParser
{
private JsonConfigurationParser() { }
private readonly IDictionary<string, string> _data = new SortedDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
private readonly Stack<string> _context = new Stack<string>();
private string _currentPath;
public static IDictionary<string, string> Parse(string json) => new JsonConfigurationParser().ParseJson(json);
private IDictionary<string, string> ParseJson(string json)
{
_data.Clear();
var jsonConfig = JObject.Parse(json);
VisitJObject(jsonConfig);
return _data;
}
private void VisitJObject(JObject jObject)
{
foreach (var property in jObject.Properties())
{
EnterContext(property.Name);
VisitProperty(property);
ExitContext();
}
}
private void VisitProperty(JProperty property)
{
VisitToken(property.Value);
}
private void VisitToken(JToken token)
{
switch (token.Type)
{
case JTokenType.Object:
VisitJObject(token.Value<JObject>());
break;
case JTokenType.Array:
VisitArray(token.Value<JArray>());
break;
case JTokenType.Integer:
case JTokenType.Float:
case JTokenType.String:
case JTokenType.Boolean:
case JTokenType.Bytes:
case JTokenType.Raw:
case JTokenType.Null:
VisitPrimitive(token.Value<JValue>());
break;
default:
throw new FormatException("Unsupported JSON token");
}
}
private void VisitArray(JArray array)
{
for (int index = 0; index < array.Count; index++)
{
EnterContext(index.ToString());
VisitToken(array[index]);
ExitContext();
}
}
private void VisitPrimitive(JValue data)
{
var key = _currentPath;
if (_data.ContainsKey(key))
{
throw new FormatException("Duplicate Key");
}
_data[key] = data.ToString(CultureInfo.InvariantCulture);
}
private void EnterContext(string context)
{
_context.Push(context);
_currentPath = ConfigurationPath.Combine(_context.Reverse());
}
private void ExitContext()
{
_context.Pop();
_currentPath = ConfigurationPath.Combine(_context.Reverse());
}
}
}
USAGE:
var dictionary = JsonConfigurationParser.Parse(MyJsonString);