Retrieve sections from config.json in ASP.NET 5 - c#

Let's say I have a config.json like this:
{
"CustomSection": {
"A": 1,
"B": 2
}
}
I know I can use an IConfiguration object to get specific settings, i.e., configuration.Get("CustomSection:A"), but can I grab the whole hierarchy (in any type - even a raw string would be fine)? When I try configuration.Get("CustomSection"), I get a null result, so I think this isn't supported by default.
My use case is grabbing entire configuration dictionaries at once without having to grab each individual setting - some properties may not be known at compile time.

I have solved a similar problem where I wanted to bind the entire IConfigurationRoot or IConfigurationSection to a Dictionary. Here is an extension class:
public class ConfigurationExtensions
{
public static Dictionary<string, string> ToDictionary(this IConfiguration config, bool stripSectionPath = true)
{
var data = new Dictionary<string, string>();
var section = stripSectionPath ? config as IConfigurationSection : null;
ConvertToDictionary(config, data, section);
return data;
}
static void ConvertToDictionary(IConfiguration config, Dictionary<string, string> data = null, IConfigurationSection top = null)
{
if (data == null) data = new Dictionary<string, string>();
var children = config.GetChildren();
foreach (var child in children)
{
if (child.Value == null)
{
ConvertToDictionary(config.GetSection(child.Key), data);
continue;
}
var key = top != null ? child.Path.Substring(top.Path.Length + 1) : child.Path;
data[key] = child.Value;
}
}
}
And using the extension:
IConfigurationRoot config;
var data = config.ToDictionary();
var data = config.GetSection("CustomSection").ToDictionary();
There is an optional parameter (stripSectionPath) to either retain the full section key path or to strip the section path out, leaving a relative path.
var data = config.GetSection("CustomSection").ToDictionary(false);

configuration.Get is for getting a value to get a section you need
IConfiguration mysection = configuration.GetConfigurationSection("SectionKey");

I was able to load and bind multiple sub sections with unknown keys like this (the syntax has changed slightly since your post; I recommend keeping an eye on the github project and their unit tests to see how it is currently working):
var objectSections = Configuration.GetSection("CustomObjects").GetChildren();
var objects = objectSections.ToDictionary(x => x.Key, x =>
{
var obj = new CustomObject();
ConfigurationBinder.Bind(x, obj);
return obj ;
});

Edit: updating this answer for the 1.0 release of Core.
This is possible now if you use a strongly typed object, for example:
public class CustomSection
{
public int A {get;set;}
public int B {get;set;}
}
//In Startup.cs
services.Configure<CustomSection>(Configuration.GetSection("CustomSection"));
//You can then inject an IOptions instance
public HomeController(IOptions<CustomSection> options)
{
var settings = options.Value;
}

For a detailed explanation, see
https://dotnetcodr.com/2017/01/20/introduction-to-asp-net-core-part-3-the-configuration-file/
Below is an example from the site:
Config file has this:
"Languages": {
".NET": [ "C#", "VB.NET", "F#" ],
"JVM": ["Java", "Scala", "Clojure"]
}
Load this configuration as follows:
IConfigurationSection languagesSection = configRoot.GetSection("Languages");
IEnumerable<IConfigurationSection> languagesSectionMembers = languagesSection.GetChildren();
Dictionary<string, List<string>> platformLanguages = new Dictionary<string, List<string>>();
foreach (var platform in languagesSectionMembers)
{
List<string> langs = (from p in platform.GetChildren() select p.Value).ToList();
platformLanguages[platform.Key] = langs;
}

You can use the ConfigurationBinder and read everything as Dictionary<string, string>
Here are some test cases that you can use as example: https://github.com/aspnet/Configuration/blob/dev/test/Microsoft.Framework.Configuration.Binder.Test/ConfigurationCollectionBindingTests.cs

Related

how to edit appsettings.json programmatically in winform on .net 6 [duplicate]

I am using the IOptions pattern as described in the official documentation.
This works fine when I am reading values from appsetting.json, but how do I update values and save changes back to appsetting.json?
In my case, I have a few fields that can be edited from the user interface (by admin user in application). Hence I am looking for the ideal approach to update these values via the option accessor.
At the time of writing this answer it seemed that there is no component provided by the Microsoft.Extensions.Options package that has functionality to write configuration values back to appsettings.json.
In one of my ASP.NET Core projects I wanted to enable the user to change some application settings - and those setting values should be stored in appsettings.json, more precisly in an optional appsettings.custom.json file, that gets added to the configuration if present.
Like this...
public Startup(IHostingEnvironment env)
{
IConfigurationBuilder builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile("appsettings.custom.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
this.Configuration = builder.Build();
}
I declared the IWritableOptions<T> interface that extends IOptions<T>; so I can just replace IOptions<T> by IWritableOptions<T> whenever I want to read and write settings.
public interface IWritableOptions<out T> : IOptions<T> where T : class, new()
{
void Update(Action<T> applyChanges);
}
Also, I came up with IOptionsWriter, which is a component that is intended to be used by IWritableOptions<T> to update a configuration section. This is my implementation for the beforementioned interfaces...
class OptionsWriter : IOptionsWriter
{
private readonly IHostingEnvironment environment;
private readonly IConfigurationRoot configuration;
private readonly string file;
public OptionsWriter(
IHostingEnvironment environment,
IConfigurationRoot configuration,
string file)
{
this.environment = environment;
this.configuration = configuration;
this.file = file;
}
public void UpdateOptions(Action<JObject> callback, bool reload = true)
{
IFileProvider fileProvider = this.environment.ContentRootFileProvider;
IFileInfo fi = fileProvider.GetFileInfo(this.file);
JObject config = fileProvider.ReadJsonFileAsObject(fi);
callback(config);
using (var stream = File.OpenWrite(fi.PhysicalPath))
{
stream.SetLength(0);
config.WriteTo(stream);
}
this.configuration.Reload();
}
}
Since the writer is not aware about the file structure, I decided to handle sections as JObject objects. The accessor tries to find the requested section and deserializes it to an instance of T, uses the current value (if not found), or just creates a new instance of T, if the current value is null. This holder object is than passed to the caller, who will apply the changes to it. Than the changed object gets converted back to a JToken instance that is going to replace the section...
class WritableOptions<T> : IWritableOptions<T> where T : class, new()
{
private readonly string sectionName;
private readonly IOptionsWriter writer;
private readonly IOptionsMonitor<T> options;
public WritableOptions(
string sectionName,
IOptionsWriter writer,
IOptionsMonitor<T> options)
{
this.sectionName = sectionName;
this.writer = writer;
this.options = options;
}
public T Value => this.options.CurrentValue;
public void Update(Action<T> applyChanges)
{
this.writer.UpdateOptions(opt =>
{
JToken section;
T sectionObject = opt.TryGetValue(this.sectionName, out section) ?
JsonConvert.DeserializeObject<T>(section.ToString()) :
this.options.CurrentValue ?? new T();
applyChanges(sectionObject);
string json = JsonConvert.SerializeObject(sectionObject);
opt[this.sectionName] = JObject.Parse(json);
});
}
}
Finally, I implemented an extension method for IServicesCollection allowing me to easily configure a writable options accessor...
static class ServicesCollectionExtensions
{
public static void ConfigureWritable<T>(
this IServiceCollection services,
IConfigurationRoot configuration,
string sectionName,
string file) where T : class, new()
{
services.Configure<T>(configuration.GetSection(sectionName));
services.AddTransient<IWritableOptions<T>>(provider =>
{
var environment = provider.GetService<IHostingEnvironment>();
var options = provider.GetService<IOptionsMonitor<T>>();
IOptionsWriter writer = new OptionsWriter(environment, configuration, file);
return new WritableOptions<T>(sectionName, writer, options);
});
}
}
Which can be used in ConfigureServices like...
services.ConfigureWritable<CustomizableOptions>(this.Configuration,
"MySection", "appsettings.custom.json");
In my Controller class I can just demand an IWritableOptions<CustomizableOptions> instance, that has the same characteristics as IOptions<T>, but also allows to change and store configuration values.
private IWritableOptions<CustomizableOptions> options;
...
this.options.Update((opt) => {
opt.SampleOption = "...";
});
Simplified version of Matze's answer:
public interface IWritableOptions<out T> : IOptionsSnapshot<T> where T : class, new()
{
void Update(Action<T> applyChanges);
}
public class WritableOptions<T> : IWritableOptions<T> where T : class, new()
{
private readonly IHostingEnvironment _environment;
private readonly IOptionsMonitor<T> _options;
private readonly string _section;
private readonly string _file;
public WritableOptions(
IHostingEnvironment environment,
IOptionsMonitor<T> options,
string section,
string file)
{
_environment = environment;
_options = options;
_section = section;
_file = file;
}
public T Value => _options.CurrentValue;
public T Get(string name) => _options.Get(name);
public void Update(Action<T> applyChanges)
{
var fileProvider = _environment.ContentRootFileProvider;
var fileInfo = fileProvider.GetFileInfo(_file);
var physicalPath = fileInfo.PhysicalPath;
var jObject = JsonConvert.DeserializeObject<JObject>(File.ReadAllText(physicalPath));
var sectionObject = jObject.TryGetValue(_section, out JToken section) ?
JsonConvert.DeserializeObject<T>(section.ToString()) : (Value ?? new T());
applyChanges(sectionObject);
jObject[_section] = JObject.Parse(JsonConvert.SerializeObject(sectionObject));
File.WriteAllText(physicalPath, JsonConvert.SerializeObject(jObject, Formatting.Indented));
}
}
public static class ServiceCollectionExtensions
{
public static void ConfigureWritable<T>(
this IServiceCollection services,
IConfigurationSection section,
string file = "appsettings.json") where T : class, new()
{
services.Configure<T>(section);
services.AddTransient<IWritableOptions<T>>(provider =>
{
var environment = provider.GetService<IHostingEnvironment>();
var options = provider.GetService<IOptionsMonitor<T>>();
return new WritableOptions<T>(environment, options, section.Key, file);
});
}
}
Usage:
services.ConfigureWritable<MyOptions>(Configuration.GetSection("MySection"));
Then:
private readonly IWritableOptions<MyOptions> _options;
public MyClass(IWritableOptions<MyOptions> options)
{
_options = options;
}
To save the changes to the file:
_options.Update(opt => {
opt.Field1 = "value1";
opt.Field2 = "value2";
});
And you can pass a custom json file as optional parameter (it will use appsettings.json by default):
services.ConfigureWritable<MyOptions>(Configuration.GetSection("MySection"), "appsettings.custom.json");
public static void SetAppSettingValue(string key, string value, string appSettingsJsonFilePath = null) {
if (appSettingsJsonFilePath == null) {
appSettingsJsonFilePath = System.IO.Path.Combine(System.AppContext.BaseDirectory, "appsettings.json");
}
var json = System.IO.File.ReadAllText(appSettingsJsonFilePath);
dynamic jsonObj = Newtonsoft.Json.JsonConvert.DeserializeObject < Newtonsoft.Json.Linq.JObject > (json);
jsonObj[key] = value;
string output = Newtonsoft.Json.JsonConvert.SerializeObject(jsonObj, Newtonsoft.Json.Formatting.Indented);
System.IO.File.WriteAllText(appSettingsJsonFilePath, output);
}
I see a lot of answers use Newtonsoft.Json package to update appsettings. I will provide some some solutions that use System.Text.Json package (built-in on .Net Core 3 and above).
OPTION 1
Before you start updating appsettings.json file dynamically, ask yourself a question, how comlex is that part of appsettings.json that needs to be updated. If the part that needs to be updated is not very complex, you can use appsettings transformation functionality just for that part that that needs to be updated. Here's an example:
Let's say my appsettings.json file looks like that:
{
"Username": "Bro300",
"Job": {
"Title": "Programmer",
"Type": "IT"
}
}
And let's say I need to update only Job section. Instead of updating appsettings.json directly I can create a smaller file appsettings.MyOverrides.json that will look like this:
{
"Job": {
"Title": "Farmer",
"Type": "Agriculture"
}
}
And then make sure that this new file is added in my .Net Core app, and .Net Core will figure out how to load the new updated settings.
Now the next step is to create a wrapper class that will hold values from appsettings.MyOverrides.json like this:
public class OverridableSettings
{
public JobSettings Job { get; set; }
}
public class JobSettings
{
public string Title { get; set; }
public string Type { get; set; }
}
And then I can create my updater class that will look like this (notice that it takes in OverridableSettings and completely overrides appsettings.MyOverrides.json file:
public class AppSettingsUpdater
{
public void UpdateSettings(OverridableSettings settings)
{
// instead of updating appsettings.json file directly I will just write the part I need to update to appsettings.MyOverrides.json
// .Net Core in turn will read my overrides from appsettings.MyOverrides.json file
const string SettinsgOverridesFileName = "appsettings.MyOverrides.json";
var newConfig = JsonSerializer.Serialize(settings, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(SettinsgOverridesFileName, newConfig);
}
}
Finally this is the code that demonstrates how to use it:
public static class Program
{
public static void Main()
{
// Notice that appsettings.MyOverrides.json will contain only the part that we need to update, other settings will live in appsettings.json
// Also appsettings.MyOverrides.json is optional so if it doesn't exist at the program start it's not a problem
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddJsonFile("appsettings.MyOverrides.json", optional: true)
.Build();
// Here we read our current settings
var settings = configuration.Get<OverridableSettings>();
var settingsUpdater = new AppSettingsObjectUpdater();
settings.Job.Title = "Farmer";
settings.Job.Type = "Agriculture";
settingsUpdater.UpdateSettings(settings);
// Here we reload the settings so the new values from appsettings.MyOverrides.json will be read
configuration.Reload();
// and here we retrieve the new updated settings
var newJobSettings = configuration.GetSection("Job").Get<JobSettings>();
}
}
OPTION 2
If the appsetting transformation does not fit you case, and you have to update values only one level deep, you can use this simple implementation:
public void UpdateAppSetting(string key, string value)
{
var configJson = File.ReadAllText("appsettings.json");
var config = JsonSerializer.Deserialize<Dictionary<string, object>>(configJson);
config[key] = value;
var updatedConfigJson = JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText("appsettings.json", updatedConfigJson);
}
OPTION 3
Finally, if you have some complex case and you need to update appsettings, multiple levels deep, here is another implementation, that expands on the previous option, and uses recursion to update the settings at any level:
public class AppSettingsUpdater
{
private const string EmptyJson = "{}";
public void UpdateAppSetting(string key, object value)
{
// Empty keys "" are allowed in json by the way
if (key == null)
{
throw new ArgumentException("Json property key cannot be null", nameof(key));
}
const string settinsgFileName = "appsettings.json";
// We will create a new file if appsettings.json doesn't exist or was deleted
if (!File.Exists(settinsgFileName))
{
File.WriteAllText(settinsgFileName, EmptyJson);
}
var config = File.ReadAllText(settinsgFileName);
var updatedConfigDict = UpdateJson(key, value, config);
// After receiving the dictionary with updated key value pair, we serialize it back into json.
var updatedJson = JsonSerializer.Serialize(updatedConfigDict, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(settinsgFileName, updatedJson);
}
// This method will recursively read json segments separated by semicolon (firstObject:nestedObject:someProperty)
// until it reaches the desired property that needs to be updated,
// it will update the property and return json document represented by dictonary of dictionaries of dictionaries and so on.
// This dictionary structure can be easily serialized back into json
private Dictionary<string, object> UpdateJson(string key, object value, string jsonSegment)
{
const char keySeparator = ':';
var config = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonSegment);
var keyParts = key.Split(keySeparator);
var isKeyNested = keyParts.Length > 1;
if (isKeyNested)
{
var firstKeyPart = keyParts[0];
var remainingKey = string.Join(keySeparator, keyParts.Skip(1));
// If the key does not exist already, we will create a new key and append it to the json
var newJsonSegment = config.ContainsKey(firstKeyPart) && config[firstKeyPart] != null
? config[firstKeyPart].ToString()
: EmptyJson;
config[firstKeyPart] = UpdateJson(remainingKey, value, newJsonSegment);
}
else
{
config[key] = value;
}
return config;
}
}
You can use, like this:
var settingsUpdater = new AppSettingsUpdater();
settingsUpdater.UpdateAppSetting("OuterProperty:NestedProperty:PropertyToUpdate", "new value");
Update value through this code
it's simply run console application that reads application settings, adds a new setting, and updates an existing setting. and after update refresh the application on server without closed application.
For more information: See Microsoft .Net Docs, ConfigurationManager.AppSettings Property
static void AddUpdateAppSettings(string key, string value)
{
try
{
var configFile = System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration("~");
var settings = configFile.AppSettings.Settings;
if (settings[key] == null)
{
settings.Add(key, value);
}
else
{
settings[key].Value = value;
}
configFile.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection(configFile.AppSettings.SectionInformation.Name);
}
catch (ConfigurationErrorsException ex)
{
Console.WriteLine("Error writing app settings. Error: "+ ex.Message);
}
}
While there is still not a way via the Options accessor, I'd like to preset a .NET 6 class that makes it quite easy to write back to the file. You can use the JsonNode class in the System.Text.Json.Nodes class. I'm using it to write back an encrypted connection string after reading a plain text one from appsettings.json.
There are examples of using Newtonsoft.Json.JsonConvert.DeserializeObject and deserializing into a dynamic type like #Alper suggested - but System.Text.Json could not do that. Well, now you sort of can :) (though not with a dynamic type).
In my example below, I tried to be minimalistic and simple. I used JsonNode to retrieve the value instead of a Dependency Injected IConfiguration. In a real web application, I'd be using the DI method. It really doesn't matter how you retrieve the setting, writing it back still means reconstructing the Json and updating the file on disk.
MS Link for JsonNode: https://learn.microsoft.com/en-us/dotnet/api/system.text.json.nodes.jsonnode?view=net-6.0
My appsettings.json sample:
{
"sampleSection": {
"someStringSetting": "Value One",
"deeperValues": {
"someIntSetting": 23,
"someBooleanSetting": true
}
}
}
C# .NET 6 console application:
using System.Text.Json;
using System.Text.Json.Nodes;
const string AppSettingsPath = #"<PathToYourAppSettings.JsonFile>>\appsettings.json";
string appSettingsJson = File.ReadAllText(AppSettingsPath);
var jsonNodeOptions = new JsonNodeOptions { PropertyNameCaseInsensitive = true };
var node = JsonNode.Parse(appSettingsJson, jsonNodeOptions);
var options = new JsonSerializerOptions { WriteIndented = true };
Console.WriteLine("=========== Before ============");
Console.WriteLine(node.ToJsonString(options));
// Now you have access to all the structure using node["blah"] syntax
var stringSetting = (string) node["sampleSection"]["someStringSetting"];
var intSetting = (int) node["sampleSection"]["deeperValues"]["someIntSetting"];
var booleanSetting = (bool) node["sampleSection"]["deeperValues"]["someBooleanSetting"];
Console.WriteLine($"stringSetting: {stringSetting}, intSetting: {intSetting}, booleanSetting: {booleanSetting}");
// Now write new values back
node["sampleSection"]["someStringSetting"] = $"New setting at {DateTimeOffset.Now}";
node["sampleSection"]["deeperValues"]["someIntSetting"] = -6;
node["sampleSection"]["deeperValues"]["someBooleanSetting"] = false;
Console.WriteLine("=========== After ============");
Console.WriteLine(node.ToJsonString(options));
// Or, to actually write it to disk:
// File.WriteAllText(AppSettingsPath, node.ToJsonString(options));
I hope that my scenario covers your intent, I wanted to override the appsettings.json values if there are environment variables passed to the app at startup.
I made use of the ConfigureOptions method that is available in dotnet core 2.1.
Here is the Model that is used for the JSON from appsettings.json
public class Integration
{
public string FOO_API {get;set;}
}
For the services in the statup.cs:
var section = Configuration.GetSection ("integration");
services.Configure<Integration> (section);
services.ConfigureOptions<ConfigureIntegrationSettings>();
Here is the implemenation:
public class ConfigureIntegrationSettings : IConfigureOptions<Integration>
{
public void Configure(Integration options)
{
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("FOO")))
options.FOO_API = Environment.GetEnvironmentVariable("FOO_API");
}
}
so if there is no value set it falls back to the appsettings.json
I solved similar problem - I needed override appSettings like this:
For 'IConfigurationBuilder':
configurationBuilder
.AddJsonFile("appsettings.json", false, true)
.AddJsonFile($"appsettings.{environmentName}.json", false, true)
.AddConfigurationObject(TenantsTimeZoneConfigurationOverrides(configurationBuilder)); // Override Tenants TimeZone configuration due the OS platform (https://dejanstojanovic.net/aspnet/2018/july/differences-in-time-zones-in-net-core-on-windows-and-linux-host-os/)
private static Dictionary<string, string> TenantsTimeZoneConfigurationOverrides(IConfigurationBuilder configurationBuilder)
{
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
var overridesDictionary = new Dictionary<string, string>();
var configuration = configurationBuilder.Build() as IConfiguration;
var tenantsSection = configuration.GetSection(TenantsConfig.TenantsCollectionConfigSectionName).Get<Tenants>();
foreach (var tenant in tenantsSection)
{
if (!string.IsNullOrEmpty(tenant.Value.TimeZone))
{
overridesDictionary.Add($"Tenants:{tenant.Key}:TimeZone", GetSpecificTimeZoneDueOsPlatform(isWindows, tenant.Value.TimeZone));
}
}
return overridesDictionary;
}
private static string GetSpecificTimeZoneDueOsPlatform(bool isWindows, string timeZone)
{
return isWindows ? timeZone : TZConvert.WindowsToIana(timeZone);
}

Map [name,value] string values to class without reflection

I'm having a huge performance issue about mapping string property names and string property values to classes using reflection.
My issue now:
public class Person
{
public string Property1 { get; set; }
public string Property2 { get; set; }
public string Property3 { get; set; }
public string Property4 { get; set; }
// My class has around 100 properties
public string Property100 { get; set; }
}
I am mapping a key value pair collection to the class using reflection
[{"Property1": "some value"}, {"Property2": "something else"},{"Property3","Property4","value" }.....{"Property100","val"}]
It got to the point that I am now mapping around 10 000 class instances using reflection and the performance is to say it lightly bad.
Any ideas for eliminating the reflection would be greatly appreciated.
I see two options, if you need to avoid reflection for tasks like this(when code could be programatically generated).
First is Expressions I use it often, e.g. I saw some people write something like this
public class A
{
public Prop1 ...
....
public Prop100
public override ToString() => $"{nameof(Prop1)}={Prop1};...";
and so for all 100 properties, and always doing this manually.
And with Expression it can be easily automated, you just need to generate Expression for String.Concat and pass list of properties and names there.
For your example, it is not clear what are your data. How do you do lookup in the list?
Let's assume there is a dictionary<string,string>(you can transform your list of tuples to a dictionary), and all properties are strings as well.
Then we would need to generate a list assignment expressions like this
if(data.ContainsKey("Prop1")) result.Prop1 = data["Prop1"];
And the code would be complicated, anyway it would look like this
private static class CompiledDelegate<T>
{
public static Action<T, Dictionary<string, string>> initObject;
static CompiledDelegate()
{
var i = Expression.Parameter(typeof(Dictionary<string, string>), "i");
var v = Expression.Parameter(typeof(T), "v");
var propertyInfos = typeof(T).GetProperties().ToArray();
var t = new Dictionary<string, string>();
var contains = typeof(Dictionary<string, string>).GetMethod(nameof(Dictionary<string, string>.ContainsKey));
var getter = typeof(Dictionary<string, string>).GetProperties().First(x => x.GetIndexParameters().Length > 0);
var result = new List<Expression>();
foreach (var propertyInfo in propertyInfos)
{
var cst = Expression.Constant(propertyInfo.Name);
var assignExpression =
Expression.IfThen(Expression.Call(i, contains, cst),
Expression.Assign(Expression.PropertyOrField(v, propertyInfo.Name), Expression.MakeIndex(i, getter, new[] { cst })));
result.Add(assignExpression);
}
var block = Expression.Block(result);
initObject = Expression.Lambda<Action<T, Dictionary<string, string>>>(block, new ParameterExpression[] { v, i }).Compile();
}
}
It is an example, it would fail if there were non-string properties.
And it could be used like this
static void Main(string[] args)
{
var tst = new Test();
CompiledDelegate<Test>.initObject(tst, new Dictionary<string, string>
{
{ "S3", "Value3" },
{ "S2", "Value2" },
});
CompiledDelegate<Test>.initObject(tst, new Dictionary<string, string>
{
{ "S3", "Value3" },
{ "S1", "Value1" },
});
Console.ReadKey();
}
The second option is, actually, what it should be ideally imlemented like Using source generators I think such things do have to be done just in build time.
There is a lot of articles on msdn, for instance with samples. But it turned out to be not very easy to implement, even just a sample.
I can say, it didn't work for me, while I tried to do it according to samples.
In order to get it work I had to change TargetFramework to netstandard2.0, do something else...
But after all, when build was green, Visual Studio still showed an error.
Ok, it disappeared after VS restart, but still, that doesn't look very usable.
So, this is a generator, that creates a converter for every class with attribute.
It is again a sample, it doesn't check many things.
[Generator]
public class ConverterGenerator : ISourceGenerator
{
private static string mytemplate = #"using System.Collections.Generic;
using {2};
namespace GeneratedConverters
{{
public static class {0}Converter
{{
public static {0} Convert(Dictionary<string, string> data)
{{
var result = new {0}();
{1}
return result;
}}
}}
}}";
public static string GetNamespaceFrom(SyntaxNode s)
{
if (s.Parent is NamespaceDeclarationSyntax namespaceDeclarationSyntax)
{
return namespaceDeclarationSyntax.Name.ToString();
}
if (s.Parent == null)
return "";
return GetNamespaceFrom(s.Parent);
}
public void Execute(GeneratorExecutionContext context)
{
GetMenuComponents(context, context.Compilation);
}
private static void GetMenuComponents(GeneratorExecutionContext context, Compilation compilation)
{
var allNodes = compilation.SyntaxTrees.SelectMany(s => s.GetRoot().DescendantNodes());
var allClasses = allNodes.Where(d => d.IsKind(SyntaxKind.ClassDeclaration)).OfType<ClassDeclarationSyntax>();
var classes = allClasses
.Where(c => c.AttributeLists.SelectMany(a => a.Attributes).Select(a => a.Name).Any(s => s.ToString().Contains("DictionaryConverter")))
.ToImmutableArray();
foreach (var item in classes.Distinct().Take(1))
{
context.AddSource(item.Identifier.Text + "Converter", String.Format(mytemplate, item.Identifier.Text, SourceText.From(GenerateProperties(item)), GetNamespaceFrom(item)));
}
}
private static string GenerateProperties(ClassDeclarationSyntax s)
{
var properties = s.Members.OfType<PropertyDeclarationSyntax>();
return String.Join(Environment.NewLine,
properties.Select(p =>
{
var name = p.Identifier.Text;
return $"if(data.ContainsKey(\"{name}\")) result.{name} = data[\"{name}\"];";
}));
}
public void Initialize(GeneratorInitializationContext context)
{
}
}
and
static void Main(string[] args)
{
var t1 = GeneratedConverters.TestConverter.Convert(new Dictionary<string, string>
{
{ "S3", "Value3" },
{ "S2", "Value2" },
});
}
Best performance without reflection would be manual mapping.
It seems your key/value pair collection is regular JSON. So you could use the JSONTextReader from JSON.NET and read the string. Then manually map the JSON properties to the class properties.
Like so:
JsonTextReader reader = new JsonTextReader(new StringReader(jsonString));
while (reader.Read())
{
if (reader.Value != null)
{
// check reader.Value.ToString() and assign to correct class property
}
}
More info can be found on the JSON.NET website : https://www.newtonsoft.com/json/help/html/ReadingWritingJSON.htm

C# - Create JSON array of objects from Dictionary

I'm sure there is a simple solution to this, I just haven't been able to figure it out yet.
I need to return an array of objects in JSON (which I'm completely new to). The structure should look like the following:
{"files": [
{
"picture1.jpg": true
},
{
"picture2.jpg": true
}
]}
I thought I could do this by using a Dictionary but that doesn't seem to work the way I want it to either. Below is what I have so far and what the output is. Any guidance would be greatly appreciated!
This is what I have in C#:
public async Task<JsonResult> DeleteImages(List<string> ids)
{
var files = new Dictionary<string, bool>();
foreach (var id in ids)
{
var file = await _fileService.GetByIdAsync(id);
if (await AzureStorage.DeleteFile(file))
{
files.Add(file.Name, true)
}
}
return Json(JsonConvert.SerializeObject(files));
}
The problem is that this returns the following:
{
"picture1.jpg": true,
"picture2.jpg": true
}
The following solution will provide exactly what you are looking for. The real key is creating an intermediate object to hold the entries you are looking for rather than simply placing the files in a Dictionary. The other complication is that you are really looking for a list of dictionaries, where each dictionary contains one file name/deleted entry.
The file collection class:
public class FileCollection
{
[JsonProperty("files")]
public List<Dictionary<string, bool>> Files { get; set; }
public FileCollection()
{
Files = new List<Dictionary<string, bool>>();
}
}
Your existing logic, modified to use the new collection class:
public async Task<JsonResult> DeleteImages(List<string> ids)
{
var files = new FileCollection();
foreach (var id in ids)
{
var file = await _fileService.GetByIdAsync(id);
if (await AzureStorage.DeleteFile(file))
{
files.Files.Add(new Dictionary<string, bool> { { file.Name, true } });
}
}
return Json(JsonConvert.SerializeObject(files));
}

How to update values into appsetting.json?

I am using the IOptions pattern as described in the official documentation.
This works fine when I am reading values from appsetting.json, but how do I update values and save changes back to appsetting.json?
In my case, I have a few fields that can be edited from the user interface (by admin user in application). Hence I am looking for the ideal approach to update these values via the option accessor.
At the time of writing this answer it seemed that there is no component provided by the Microsoft.Extensions.Options package that has functionality to write configuration values back to appsettings.json.
In one of my ASP.NET Core projects I wanted to enable the user to change some application settings - and those setting values should be stored in appsettings.json, more precisly in an optional appsettings.custom.json file, that gets added to the configuration if present.
Like this...
public Startup(IHostingEnvironment env)
{
IConfigurationBuilder builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile("appsettings.custom.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
this.Configuration = builder.Build();
}
I declared the IWritableOptions<T> interface that extends IOptions<T>; so I can just replace IOptions<T> by IWritableOptions<T> whenever I want to read and write settings.
public interface IWritableOptions<out T> : IOptions<T> where T : class, new()
{
void Update(Action<T> applyChanges);
}
Also, I came up with IOptionsWriter, which is a component that is intended to be used by IWritableOptions<T> to update a configuration section. This is my implementation for the beforementioned interfaces...
class OptionsWriter : IOptionsWriter
{
private readonly IHostingEnvironment environment;
private readonly IConfigurationRoot configuration;
private readonly string file;
public OptionsWriter(
IHostingEnvironment environment,
IConfigurationRoot configuration,
string file)
{
this.environment = environment;
this.configuration = configuration;
this.file = file;
}
public void UpdateOptions(Action<JObject> callback, bool reload = true)
{
IFileProvider fileProvider = this.environment.ContentRootFileProvider;
IFileInfo fi = fileProvider.GetFileInfo(this.file);
JObject config = fileProvider.ReadJsonFileAsObject(fi);
callback(config);
using (var stream = File.OpenWrite(fi.PhysicalPath))
{
stream.SetLength(0);
config.WriteTo(stream);
}
this.configuration.Reload();
}
}
Since the writer is not aware about the file structure, I decided to handle sections as JObject objects. The accessor tries to find the requested section and deserializes it to an instance of T, uses the current value (if not found), or just creates a new instance of T, if the current value is null. This holder object is than passed to the caller, who will apply the changes to it. Than the changed object gets converted back to a JToken instance that is going to replace the section...
class WritableOptions<T> : IWritableOptions<T> where T : class, new()
{
private readonly string sectionName;
private readonly IOptionsWriter writer;
private readonly IOptionsMonitor<T> options;
public WritableOptions(
string sectionName,
IOptionsWriter writer,
IOptionsMonitor<T> options)
{
this.sectionName = sectionName;
this.writer = writer;
this.options = options;
}
public T Value => this.options.CurrentValue;
public void Update(Action<T> applyChanges)
{
this.writer.UpdateOptions(opt =>
{
JToken section;
T sectionObject = opt.TryGetValue(this.sectionName, out section) ?
JsonConvert.DeserializeObject<T>(section.ToString()) :
this.options.CurrentValue ?? new T();
applyChanges(sectionObject);
string json = JsonConvert.SerializeObject(sectionObject);
opt[this.sectionName] = JObject.Parse(json);
});
}
}
Finally, I implemented an extension method for IServicesCollection allowing me to easily configure a writable options accessor...
static class ServicesCollectionExtensions
{
public static void ConfigureWritable<T>(
this IServiceCollection services,
IConfigurationRoot configuration,
string sectionName,
string file) where T : class, new()
{
services.Configure<T>(configuration.GetSection(sectionName));
services.AddTransient<IWritableOptions<T>>(provider =>
{
var environment = provider.GetService<IHostingEnvironment>();
var options = provider.GetService<IOptionsMonitor<T>>();
IOptionsWriter writer = new OptionsWriter(environment, configuration, file);
return new WritableOptions<T>(sectionName, writer, options);
});
}
}
Which can be used in ConfigureServices like...
services.ConfigureWritable<CustomizableOptions>(this.Configuration,
"MySection", "appsettings.custom.json");
In my Controller class I can just demand an IWritableOptions<CustomizableOptions> instance, that has the same characteristics as IOptions<T>, but also allows to change and store configuration values.
private IWritableOptions<CustomizableOptions> options;
...
this.options.Update((opt) => {
opt.SampleOption = "...";
});
Simplified version of Matze's answer:
public interface IWritableOptions<out T> : IOptionsSnapshot<T> where T : class, new()
{
void Update(Action<T> applyChanges);
}
public class WritableOptions<T> : IWritableOptions<T> where T : class, new()
{
private readonly IHostingEnvironment _environment;
private readonly IOptionsMonitor<T> _options;
private readonly string _section;
private readonly string _file;
public WritableOptions(
IHostingEnvironment environment,
IOptionsMonitor<T> options,
string section,
string file)
{
_environment = environment;
_options = options;
_section = section;
_file = file;
}
public T Value => _options.CurrentValue;
public T Get(string name) => _options.Get(name);
public void Update(Action<T> applyChanges)
{
var fileProvider = _environment.ContentRootFileProvider;
var fileInfo = fileProvider.GetFileInfo(_file);
var physicalPath = fileInfo.PhysicalPath;
var jObject = JsonConvert.DeserializeObject<JObject>(File.ReadAllText(physicalPath));
var sectionObject = jObject.TryGetValue(_section, out JToken section) ?
JsonConvert.DeserializeObject<T>(section.ToString()) : (Value ?? new T());
applyChanges(sectionObject);
jObject[_section] = JObject.Parse(JsonConvert.SerializeObject(sectionObject));
File.WriteAllText(physicalPath, JsonConvert.SerializeObject(jObject, Formatting.Indented));
}
}
public static class ServiceCollectionExtensions
{
public static void ConfigureWritable<T>(
this IServiceCollection services,
IConfigurationSection section,
string file = "appsettings.json") where T : class, new()
{
services.Configure<T>(section);
services.AddTransient<IWritableOptions<T>>(provider =>
{
var environment = provider.GetService<IHostingEnvironment>();
var options = provider.GetService<IOptionsMonitor<T>>();
return new WritableOptions<T>(environment, options, section.Key, file);
});
}
}
Usage:
services.ConfigureWritable<MyOptions>(Configuration.GetSection("MySection"));
Then:
private readonly IWritableOptions<MyOptions> _options;
public MyClass(IWritableOptions<MyOptions> options)
{
_options = options;
}
To save the changes to the file:
_options.Update(opt => {
opt.Field1 = "value1";
opt.Field2 = "value2";
});
And you can pass a custom json file as optional parameter (it will use appsettings.json by default):
services.ConfigureWritable<MyOptions>(Configuration.GetSection("MySection"), "appsettings.custom.json");
public static void SetAppSettingValue(string key, string value, string appSettingsJsonFilePath = null) {
if (appSettingsJsonFilePath == null) {
appSettingsJsonFilePath = System.IO.Path.Combine(System.AppContext.BaseDirectory, "appsettings.json");
}
var json = System.IO.File.ReadAllText(appSettingsJsonFilePath);
dynamic jsonObj = Newtonsoft.Json.JsonConvert.DeserializeObject < Newtonsoft.Json.Linq.JObject > (json);
jsonObj[key] = value;
string output = Newtonsoft.Json.JsonConvert.SerializeObject(jsonObj, Newtonsoft.Json.Formatting.Indented);
System.IO.File.WriteAllText(appSettingsJsonFilePath, output);
}
I see a lot of answers use Newtonsoft.Json package to update appsettings. I will provide some some solutions that use System.Text.Json package (built-in on .Net Core 3 and above).
OPTION 1
Before you start updating appsettings.json file dynamically, ask yourself a question, how comlex is that part of appsettings.json that needs to be updated. If the part that needs to be updated is not very complex, you can use appsettings transformation functionality just for that part that that needs to be updated. Here's an example:
Let's say my appsettings.json file looks like that:
{
"Username": "Bro300",
"Job": {
"Title": "Programmer",
"Type": "IT"
}
}
And let's say I need to update only Job section. Instead of updating appsettings.json directly I can create a smaller file appsettings.MyOverrides.json that will look like this:
{
"Job": {
"Title": "Farmer",
"Type": "Agriculture"
}
}
And then make sure that this new file is added in my .Net Core app, and .Net Core will figure out how to load the new updated settings.
Now the next step is to create a wrapper class that will hold values from appsettings.MyOverrides.json like this:
public class OverridableSettings
{
public JobSettings Job { get; set; }
}
public class JobSettings
{
public string Title { get; set; }
public string Type { get; set; }
}
And then I can create my updater class that will look like this (notice that it takes in OverridableSettings and completely overrides appsettings.MyOverrides.json file:
public class AppSettingsUpdater
{
public void UpdateSettings(OverridableSettings settings)
{
// instead of updating appsettings.json file directly I will just write the part I need to update to appsettings.MyOverrides.json
// .Net Core in turn will read my overrides from appsettings.MyOverrides.json file
const string SettinsgOverridesFileName = "appsettings.MyOverrides.json";
var newConfig = JsonSerializer.Serialize(settings, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(SettinsgOverridesFileName, newConfig);
}
}
Finally this is the code that demonstrates how to use it:
public static class Program
{
public static void Main()
{
// Notice that appsettings.MyOverrides.json will contain only the part that we need to update, other settings will live in appsettings.json
// Also appsettings.MyOverrides.json is optional so if it doesn't exist at the program start it's not a problem
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddJsonFile("appsettings.MyOverrides.json", optional: true)
.Build();
// Here we read our current settings
var settings = configuration.Get<OverridableSettings>();
var settingsUpdater = new AppSettingsObjectUpdater();
settings.Job.Title = "Farmer";
settings.Job.Type = "Agriculture";
settingsUpdater.UpdateSettings(settings);
// Here we reload the settings so the new values from appsettings.MyOverrides.json will be read
configuration.Reload();
// and here we retrieve the new updated settings
var newJobSettings = configuration.GetSection("Job").Get<JobSettings>();
}
}
OPTION 2
If the appsetting transformation does not fit you case, and you have to update values only one level deep, you can use this simple implementation:
public void UpdateAppSetting(string key, string value)
{
var configJson = File.ReadAllText("appsettings.json");
var config = JsonSerializer.Deserialize<Dictionary<string, object>>(configJson);
config[key] = value;
var updatedConfigJson = JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText("appsettings.json", updatedConfigJson);
}
OPTION 3
Finally, if you have some complex case and you need to update appsettings, multiple levels deep, here is another implementation, that expands on the previous option, and uses recursion to update the settings at any level:
public class AppSettingsUpdater
{
private const string EmptyJson = "{}";
public void UpdateAppSetting(string key, object value)
{
// Empty keys "" are allowed in json by the way
if (key == null)
{
throw new ArgumentException("Json property key cannot be null", nameof(key));
}
const string settinsgFileName = "appsettings.json";
// We will create a new file if appsettings.json doesn't exist or was deleted
if (!File.Exists(settinsgFileName))
{
File.WriteAllText(settinsgFileName, EmptyJson);
}
var config = File.ReadAllText(settinsgFileName);
var updatedConfigDict = UpdateJson(key, value, config);
// After receiving the dictionary with updated key value pair, we serialize it back into json.
var updatedJson = JsonSerializer.Serialize(updatedConfigDict, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(settinsgFileName, updatedJson);
}
// This method will recursively read json segments separated by semicolon (firstObject:nestedObject:someProperty)
// until it reaches the desired property that needs to be updated,
// it will update the property and return json document represented by dictonary of dictionaries of dictionaries and so on.
// This dictionary structure can be easily serialized back into json
private Dictionary<string, object> UpdateJson(string key, object value, string jsonSegment)
{
const char keySeparator = ':';
var config = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonSegment);
var keyParts = key.Split(keySeparator);
var isKeyNested = keyParts.Length > 1;
if (isKeyNested)
{
var firstKeyPart = keyParts[0];
var remainingKey = string.Join(keySeparator, keyParts.Skip(1));
// If the key does not exist already, we will create a new key and append it to the json
var newJsonSegment = config.ContainsKey(firstKeyPart) && config[firstKeyPart] != null
? config[firstKeyPart].ToString()
: EmptyJson;
config[firstKeyPart] = UpdateJson(remainingKey, value, newJsonSegment);
}
else
{
config[key] = value;
}
return config;
}
}
You can use, like this:
var settingsUpdater = new AppSettingsUpdater();
settingsUpdater.UpdateAppSetting("OuterProperty:NestedProperty:PropertyToUpdate", "new value");
Update value through this code
it's simply run console application that reads application settings, adds a new setting, and updates an existing setting. and after update refresh the application on server without closed application.
For more information: See Microsoft .Net Docs, ConfigurationManager.AppSettings Property
static void AddUpdateAppSettings(string key, string value)
{
try
{
var configFile = System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration("~");
var settings = configFile.AppSettings.Settings;
if (settings[key] == null)
{
settings.Add(key, value);
}
else
{
settings[key].Value = value;
}
configFile.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection(configFile.AppSettings.SectionInformation.Name);
}
catch (ConfigurationErrorsException ex)
{
Console.WriteLine("Error writing app settings. Error: "+ ex.Message);
}
}
While there is still not a way via the Options accessor, I'd like to preset a .NET 6 class that makes it quite easy to write back to the file. You can use the JsonNode class in the System.Text.Json.Nodes class. I'm using it to write back an encrypted connection string after reading a plain text one from appsettings.json.
There are examples of using Newtonsoft.Json.JsonConvert.DeserializeObject and deserializing into a dynamic type like #Alper suggested - but System.Text.Json could not do that. Well, now you sort of can :) (though not with a dynamic type).
In my example below, I tried to be minimalistic and simple. I used JsonNode to retrieve the value instead of a Dependency Injected IConfiguration. In a real web application, I'd be using the DI method. It really doesn't matter how you retrieve the setting, writing it back still means reconstructing the Json and updating the file on disk.
MS Link for JsonNode: https://learn.microsoft.com/en-us/dotnet/api/system.text.json.nodes.jsonnode?view=net-6.0
My appsettings.json sample:
{
"sampleSection": {
"someStringSetting": "Value One",
"deeperValues": {
"someIntSetting": 23,
"someBooleanSetting": true
}
}
}
C# .NET 6 console application:
using System.Text.Json;
using System.Text.Json.Nodes;
const string AppSettingsPath = #"<PathToYourAppSettings.JsonFile>>\appsettings.json";
string appSettingsJson = File.ReadAllText(AppSettingsPath);
var jsonNodeOptions = new JsonNodeOptions { PropertyNameCaseInsensitive = true };
var node = JsonNode.Parse(appSettingsJson, jsonNodeOptions);
var options = new JsonSerializerOptions { WriteIndented = true };
Console.WriteLine("=========== Before ============");
Console.WriteLine(node.ToJsonString(options));
// Now you have access to all the structure using node["blah"] syntax
var stringSetting = (string) node["sampleSection"]["someStringSetting"];
var intSetting = (int) node["sampleSection"]["deeperValues"]["someIntSetting"];
var booleanSetting = (bool) node["sampleSection"]["deeperValues"]["someBooleanSetting"];
Console.WriteLine($"stringSetting: {stringSetting}, intSetting: {intSetting}, booleanSetting: {booleanSetting}");
// Now write new values back
node["sampleSection"]["someStringSetting"] = $"New setting at {DateTimeOffset.Now}";
node["sampleSection"]["deeperValues"]["someIntSetting"] = -6;
node["sampleSection"]["deeperValues"]["someBooleanSetting"] = false;
Console.WriteLine("=========== After ============");
Console.WriteLine(node.ToJsonString(options));
// Or, to actually write it to disk:
// File.WriteAllText(AppSettingsPath, node.ToJsonString(options));
I hope that my scenario covers your intent, I wanted to override the appsettings.json values if there are environment variables passed to the app at startup.
I made use of the ConfigureOptions method that is available in dotnet core 2.1.
Here is the Model that is used for the JSON from appsettings.json
public class Integration
{
public string FOO_API {get;set;}
}
For the services in the statup.cs:
var section = Configuration.GetSection ("integration");
services.Configure<Integration> (section);
services.ConfigureOptions<ConfigureIntegrationSettings>();
Here is the implemenation:
public class ConfigureIntegrationSettings : IConfigureOptions<Integration>
{
public void Configure(Integration options)
{
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("FOO")))
options.FOO_API = Environment.GetEnvironmentVariable("FOO_API");
}
}
so if there is no value set it falls back to the appsettings.json
I solved similar problem - I needed override appSettings like this:
For 'IConfigurationBuilder':
configurationBuilder
.AddJsonFile("appsettings.json", false, true)
.AddJsonFile($"appsettings.{environmentName}.json", false, true)
.AddConfigurationObject(TenantsTimeZoneConfigurationOverrides(configurationBuilder)); // Override Tenants TimeZone configuration due the OS platform (https://dejanstojanovic.net/aspnet/2018/july/differences-in-time-zones-in-net-core-on-windows-and-linux-host-os/)
private static Dictionary<string, string> TenantsTimeZoneConfigurationOverrides(IConfigurationBuilder configurationBuilder)
{
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
var overridesDictionary = new Dictionary<string, string>();
var configuration = configurationBuilder.Build() as IConfiguration;
var tenantsSection = configuration.GetSection(TenantsConfig.TenantsCollectionConfigSectionName).Get<Tenants>();
foreach (var tenant in tenantsSection)
{
if (!string.IsNullOrEmpty(tenant.Value.TimeZone))
{
overridesDictionary.Add($"Tenants:{tenant.Key}:TimeZone", GetSpecificTimeZoneDueOsPlatform(isWindows, tenant.Value.TimeZone));
}
}
return overridesDictionary;
}
private static string GetSpecificTimeZoneDueOsPlatform(bool isWindows, string timeZone)
{
return isWindows ? timeZone : TZConvert.WindowsToIana(timeZone);
}

Converting an anonymous object's properties and values to a Dictionary

I am creating a library for an existing API. I currently have QueryParameter classes for each request class. The QueryParameter classes are simple but they do vary (not all requests take the same query parameters).
Here is an example of a QueryParameter class:
public class ApiRequestAQueryParameters
{
public string Name { get; set; }
public int Start { get; set; }
public int Stop { get; set; }
}
I am interested in a way to convert a class like this into a Dictionary that I can feed to our Web client. I am hoping to have a reusable method like:
private Dictionary<string, string> GenerateQueryParameters(object queryParametersObject)
{
// perform conversion
}
This way I won't have to pull out the QueryParameter properties for each request (there will be dozens of requests)
The reason that I am using QueryParameter classes instead of making QueryParameter a Dictionary property of each API request class is to be developer friendly. I want to make it so that others can build these API requests by looking at the classes.
There are 2 ways: 1) use reflection and 2) serialize to json and back.
Here is the 1st method:
private Dictionary<string, string> GenerateQueryParameters(object queryParametersObject)
{
var res = new Dictionary<string, string>();
var props = queryParametersObject.GetType().GetProperties();
foreach (var prop in props)
{
res[prop.Name] = prop.GetValue(queryParametersObject).ToString();
}
return res;
}
You can do something like this:
private Dictionary<string, string> GenerateQueryParameters(object queryParameters)
{
var startStop = new StartStop() { Start = queryParameters.Start, Stop = queryParameters.Stop};
var result = new Dictionary<string, string>();
result.Add(queryParameters.Name, startStop);
return result;
}
public class StartStop
{
public int Start { get; set; }
public int Stop { get; set; }
}
This may be the perfect case to utilize ExpandoObjects. An ExpandoObject is a dynamic type, whose properties can be created at run time. ExpandoObject implements IDictionary < string, object > so it's easy to convert to a Dictionary < string, object > .
In the example below, an ExpandoObject is created and converted to a Dictionary < string, object > and then converted to a Dictionary < string, string >.
dynamic apiVar = new ExpandoObject();
apiVar.Name = "Test";
apiVar.Start = 1;
apiVar.Stop = 2;
var iDict = (IDictionary<string, object>) apiVar;
/* if you can utilize a Dictionary<string, object> */
var objectDict = iDict.ToDictionary(i => i.Key, i => i.Value);
/* if you need a Dictionary<string, string> */
var stringDict = iDict.ToDictionary( i=>i.Key, i=> i.Value.ToString());
There are also different ways of setting properties on an ExpandoObject. Below is an example of setting a property by a variable name.
dynamic apiVar = new ExpandoObject();
var propertyName = "Name";
apiVar[propertyName] = "Test";
propertyName = "Start";
apiVar[propertyName] = 1;
propertyName = "Stop";
apiVar[propertyName] = 2;
I always reuse the RouteValueDictionary class for this. It has a constructor that accepts any object and the class itself implements IDictionary.
It's available in the System.Web dll
private Dictionary<string, string> GenerateQueryParameters(object queryParametersObject)
{
return new RouteValueDictionary(queryParametersObject).ToDictionary(d => d.Key, d => Convert.ToString(d.Value));
}

Categories

Resources