parse values while configuring options - c#

I'm using .NET Core and want to configure my options. Based on this sample class
public class MySettings
{
public Dictionary<int, string> Mapping { get; set; }
}
I added a mapping to the appsettings.json file
{
"MySettings": {
"Mapping": {
"9454545": "agf51528gfhdfg",
"13544": "bfds28745hfghsdfghd"
}
}
}
but as you can see the keys are of type string, not integers. It is not possible for me to change the property names to numbers because then I get the error
JSON property names should be strings. Numeric property names are not
allowed in JSON
So when calling this in the startup file
IConfigurationSection mySettingsSection = configuration.GetSection("MySettings");
services.Configure<MySettings>(mySettingsSection);
the field Mapping will have 0 items because it is not able to parse the JSON keys to integers automatically. Is there a way I can tell .NET Core to parse those string keys to integers and fill up the dictionary?

Hmm I think you need workaround because cannot allow int at json file.
Something like this:
var mySettingsSection =
configuration.GetSection("MySettings").Get<Dictionary<string, string>>().Select(x => new KeyValuePair<int, string>(int.Parse(x.Key), x.Value));

Is there a way I can tell .NET Core to parse those string keys to integers and fill up the dictionary?
Unfortunately, there is no way to achieve that.
Reason
First, internally ConfigurationProvider use a IDictionary<string, string> to hold key-value pair of configuration settings (source code).
/// <summary>
/// The configuration key value pairs for this provider.
/// </summary>
protected IDictionary<string, string> Data { get; set; }
Second, when JsonConfigurationProvider try to parse JSON file (appsettings.json in your case) and populate that Data property, the parsing result, without doubt, is IDictionary<string, string> type (source code).
public static IDictionary<string, string> Parse(Stream input)
=> new JsonConfigurationFileParser().ParseStream(input);
private IDictionary<string, string> ParseStream(Stream input)
{
// Exclude implementation
}
Last, when you call services.Configure<MySettings>(mySettingsSection), under the hood it calls Bind method to populate MySettings object by matching property name (string) against configuration key (string) (source code).
/// <summary>
/// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.
/// </summary>
/// <param name="configuration">The configuration instance to bind.</param>
/// <param name="instance">The object to bind.</param>
/// <param name="configureOptions">Configures the binder options.</param>
public static void Bind(this IConfiguration configuration, object instance, Action<BinderOptions> configureOptions)
So, inside .NET Core framework, from building configuration key-value pairs to binding configuration to custom object, the data flow is always around IDictionary<string, string> type. Moreover, there is no custom binding options to let you do any kind of data type conversion.

You have two options,
First one is to change the config file to int,string. For example:
{
"MySettings": {
"Mapping": {
1: "a",
2: "b"
}
}
}
Second possibility is to create another properties in MySettings class that will be with correct type (Dictionary<string,string>) and in Mapping field you can do a kind of conversion in the getter.
public class MySettings
{
public Dictionary<int, string> Mapping => Config.ToDictionary(a => Int32.Parse(a.Key), a => a.Value);
public Dictionary<int, string> Config { get; set; }
}
If you will call Mapping multiple type you can save the conversion result once and not recalculate it in every call.

Related

JSON deserialization after property type changed

I've been working on a C# program that serializes and deserializes some data in JSON format: however, now I have to radically change the type of a pre-existing property, from string to Dictionary(string,string[]).
Is there a way to make it so that, if the program tries to deserialize a document where the field is still a string, it performs some operations to convert it to a Dictionary? Or will the old documents become completely unusable?
For reference, the functions I've been using for reading/writing are the following:
Serialization:
JsonConvert.SerializeObject(this, Formatting.Indented);
Deserialization:
JsonConvert.DeserializeObject<T>(content);
EDIT:
As a simplified version of what I have to do, I have a class with a structure similar to this:
class ExampleClass // V1
{
public string CustomProperty { get; set; }
}
And I need it, after the update, to look like this:
class ExampleClass // V2
{
public Dictionary<string, string[]> CustomProperty { get; set; }
}
What I want is, if I try to deserialize a JSON with the V1 version of the class, instead of raising an exception, that string value will be used to populate the Dictionary (the basic plan is to put a default key and for the array the V1 value split by a default character: I doubt the exact details of the conversion would make much of a difference though). Afterwards, if I try to serialize the resulting object, it will only show the Dictionary, and not the original string.
You could keep two versions of the class you are deserializing into, or you could use a custom JsonConverter and JsonConverterAttribute to deal with the two different ways of deserializing your data.
Either way you choose, you will need some way of identifying what type of document or data type you have, and then you will need an if statement to switch to the appropriate deserialization method. The easiest way of determining this really depends on your situation.
There are some ways on how to handle such a contract change
Keep an old string property, create a new one with another name for the dictionary. Implement the lazy population for the dictionary property
Change the type of the existing property but create a new one and provide [JsonPropertyAttribute] with an old name. Implement the lazy population for the dictionary property
Use JsonConverterAttribute
Have multiple versions of the class (my preferred way)
In the end, I've managed to solve the issue by explicitly writing the deserialization function, like this:
public static ExampleClass DeserializeData(string content)
{
try
{
return JsonConvert.DeserializeObject<ExampleClass>(content);
}
catch
{
// If it's in this section, it's trying to deconvert outdated JSON
Dictionary<string, dynamic> objectMap = JsonConvert.DeserializeObject<Dictionary<string, dynamic>>(content);
Dictionary<string, PropertyInfo> propertyMap = new Dictionary<string, PropertyInfo>();
ExampleClass data = new ExampleClass();
foreach (PropertyInfo property in typeof(ExampleClass).GetProperties())
propertyMap.Add(property.Name, property);
foreach (KeyValuePair<string, dynamic> kvp in objectMap)
{
Type propertyType = propertyMap[kvp.Key].PropertyType;
if (kvp.Key != "CustomProperty")
{
if (kvp.Value is Array || kvp.Value is JArray)
{
Type recipientType = propertyType.GetElementType();
Type listType = typeof(List<>).MakeGenericType(recipientType);
System.Collections.IList itemList = (System.Collections.IList)Activator.CreateInstance(listType);
foreach (var item in (kvp.Value as JArray))
itemList.Add(Convert.ChangeType(item, recipientType));
Array itemArray = Array.CreateInstance(recipientType, itemList.Count);
itemList.CopyTo(itemArray, 0);
propertyMap[kvp.Key].SetValue(data, itemArray);
}
else if (Nullable.GetUnderlyingType(propertyType) != null)
{
propertyMap[kvp.Key].SetValue(data, Convert.ChangeType(kvp.Value, Nullable.GetUnderlyingType(propertyType)));
}
else
propertyMap[kvp.Key].SetValue(data, Convert.ChangeType(kvp.Value, propertyType));
}
else
{
if (kvp.Value is string)
{
propertyMap[kvp.Key].SetValue(data, new Dictionary<string, string[]>() { { "DEFAULT", (kvp.Value as string).Split('&') } });
}
else
propertyMap[kvp.Key].SetValue(data, kvp.Value);
}
}
return data;
}
}
With this semi-generic function I can replicate the normal DeserializeObject, and have it elaborate the value depending on its type when it reaches a specific property: not only that, but by having it attempt to use DeserializeObject first, it ensures the fastest alternative is used on properly formatted JSON. It's probably not the most elegant solution, but it works, so I'll take it.
Thank you all for your answers, they were a great help in reaching this solution! Hope this helps others with the same issue as well!

Static class with non-static members shared across instances of ASP.NET MVC app?

I have a solution with several projects, including an ASP.NET MVC project and a WPF application. In the DB, I have some general settings which I want to use in both applications. To do that, I've created a class library Foo which loads the settings into a dictionary and provides a Get(string key) method for accessing specific settings out of the dictionary.
Since the settings can be overridden by user, I've added a property containing the UserId. The Get() method automatically takes care of checking and using the UserId property. This way, I don't need to pass the UserId as a param each time I call the Get() method.
For the WPF application, this works just fine, since there is just one instance running. However for the web project, I'd like to have the dictionary filled only once (in Application_Start()) and be accessible to all users visiting the site. This works fine if I make the class instance static. However, that does not allow me to have different UserIds, as this would be overridden for everyone with every user that accesses the site. What's the best way to solve this?
Here's what I tried so far (very simplified):
Class Library:
public class Foo ()
{
private Dictionary<string, string> Res;
private int UserId;
public Foo ()
{
Res = DoSomeMagicAndGetMyDbValues();
}
public void SetUser (int userId)
{
UserId = userId;
}
public string Get(string key)
{
var res = Res[key];
// do some magic stuff with the UserId
return res;
}
}
Global.asax:
public static Foo MyFoo;
protected void Application_Start()
{
MyFoo = new Foo();
}
UserController.cs:
public ActionResult Login(int userId)
{
MvcApplication.MyFoo.SetUser(userId); // <-- this sets the same UserId for all instances
}
What about storing the settings in a Dictionary<int<Dictionary<string, string>>, where the Key of the outer dictionary is the UserId, with key 0 saved for the default settings? Of course this means you'd have to pass the user id to the Get and Set methods...
Then, you could possibly do something like this:
public static class Foo
{
private static Dictionary<int, Dictionary<string, string>> settings;
/// <summary>
/// Populates settings[0] with the default settings for the application
/// </summary>
public static void LoadDefaultSettings()
{
if (!settings.ContainsKey(0))
{
settings.Add(0, new Dictionary<string, string>());
}
// Some magic that loads the default settings into settings[0]
settings[0] = GetDefaultSettings();
}
/// <summary>
/// Adds a user-defined key or overrides a default key value with a User-specified value
/// </summary>
/// <param name="key">The key to add or override</param>
/// <param name="value">The key's value</param>
public static void Set(string key, string value, int userId)
{
if (!settings.ContainsKey(userId))
{
settings.Add(userId, new Dictionary<string, string>());
}
settings[userId][key] = value;
}
/// <summary>
/// Gets the User-defined value for the specified key if it exists,
/// otherwise the default value is returned.
/// </summary>
/// <param name="key">The key to search for</param>
/// <returns>The value of specified key, or empty string if it doens't exist</returns>
public static string Get(string key, int userId)
{
if (settings.ContainsKey(userId) && settings[userId].ContainsKey(key))
{
return settings[userId][key];
}
return settings[0].ContainsKey(key) ? settings[0][key] : string.Empty;
}
}

How to disable Nancy's JSON to C# class auto case conversion for Dictionary<string, T>?

How can Nancy's automatic conversion between camel and Pascal casing during serialization to and deserialization from JSON be disabled for objects representing dictionaries in C# and JavaScript?
In my case the keys of these dictionaries are IDs that must not be changed by the automatic case conversion.
Additionally, these dictionaries themselves are values of other object's property names / keys.
Here is an example JavaScript object, where I want the auto case conversion for the object (.customers to .Customers and .addresses to .Addresses), but not for the ID-value sub-objects' keys (ID33100a00, abc433D123, etc.):
{
customers: {
ID33100a00: 'Percy',
abc433D123: 'Nancy'
},
addresses: {
abc12kkhID: 'Somewhere over the rainbow',
JGHBj45nkc: 'Programmer\'s hell',
jaf44vJJcn: 'Desert'
}
}
These dictionary objects are all represented by Dictionary<string, T> in C#, e.g.:
Dictionary<string, Customer> Customers;
Dictionary<string, Address> Addresses;
Unfortunately setting
JsonSettings.RetainCasing = true;
would result in no auto case conversion at all.
I also tried to solve the problem by writing my own JavaScriptConverter
as described in Nancy documentation,
but the actual serialization/deserialization to/from strings
for the keys of objects takes place somewhere else
(because the converter does not handle JSON strings directly,
but IDictionary<string, object> objects).
I read about a related "bug" (or behavior) here.
In our projects we usually depend on Newtonsoft.Json for our serializing needs.
And how we get the proper casing, is by creating a new class inheriting from JsonSerializer, like so:
public sealed class CustomJsonSerializer : JsonSerializer
{
public CustomJsonSerializer()
{
ContractResolver = new CamelCasePropertyNamesContractResolver();
}
}
And then register it with the Application like this:
protected override void ConfigureApplicationContainer(TinyIoCContainer container)
{
base.ConfigureApplicationContainer(container);
container.Register<JsonSerializer,CustomJsonSerializer>().AsSingleton();
}
It also allows you to customize other bits of serialization, Like getting enums Serialized as strings:
public CustomJsonSerializer()
{
Converters.Add(new StringEnumConverter());
ContractResolver = new CamelCasePropertyNamesContractResolver();
}

Document key usage in Dictionary

How can I document the key usage in a Dictionary so that it shows in Visual studio when coding using that object?
I'm looking for something like:
/// <param name="SpecialName">That Special Name</param>
public Dictionary<string,string> bar;
So far the best attempt has been to write my own class:
public class SpecialNameDictionary : IDictionary<string, string>
{
private Dictionary<string, string> data = new Dictionary<string, string>();
/// <param name="specialName">That Special Name</param>
public string this[string specialName]
{
get
{
return data[specialName];
}
}
}
But It adds a lot of code that doesn't do anything. Additionally I must retype every Dictionary method to make it compile.
Is there a better way to achive the above?
You can define, dictionary like this:
public class SpecialNameDictionary : Dictionary<string, string>
{
/// <param name="specialName">That Special Name</param>
public new string this[string specialName]
{
get
{
return base[specialName];
}
}
}
Instead of deriving from IDictionary derive from Dictionary, and make new implementation of indexer.
Document the field like this:
/// <summary>
/// A map from the special name to the frongulator.
/// </summary>
public Dictionary<string,string> bar;
(I assume that in reality it's either not public or not a field - but the same would apply for private fields or public properties.)
You won't get IntelliSense on the indexer itself, but any usage of bar should make it reasonably clear.
Three other alternatives:
Use types which make the usage clearer (a string could be anything, but a FrongulatorSpecialName is clearer)
Make the name of the field/property itself clearer
Hide the dictionary, but add a method such as "GetFrongulatorBySpecialName"
You could inherit directly from Dictionary<> instead of IDictionary<>, that way you only need to re-implement the indexer.

Store Dictionary<string,string> in application settings

I have a dictionary of strings that i want the user to be able to add/remove info from then store it for them so it they can access it the next time the program restarts
I am unclear on how i can store a dictionary as a setting. I see that under system.collections.special there is a thing called a stringdictionary but ive read that SD are outdated and shouldn't be used.
also in the future i may have need to store a dictionary that is not strings only (int string)
how would you store a dictionary in the settings file for a .net application?
You can use this class derived from StringDictionary. To be useful for application settings it implements IXmlSerializable.
Or you can use similar approach to implement your own XmlSerializable class.
public class SerializableStringDictionary : System.Collections.Specialized.StringDictionary, System.Xml.Serialization.IXmlSerializable
{
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
while (reader.Read() &&
!(reader.NodeType == System.Xml.XmlNodeType.EndElement && reader.LocalName == this.GetType().Name))
{
var name = reader["Name"];
if (name == null)
throw new FormatException();
var value = reader["Value"];
this[name] = value;
}
}
public void WriteXml(System.Xml.XmlWriter writer)
{
foreach (System.Collections.DictionaryEntry entry in this)
{
writer.WriteStartElement("Pair");
writer.WriteAttributeString("Name", (string)entry.Key);
writer.WriteAttributeString("Value", (string)entry.Value);
writer.WriteEndElement();
}
}
}
Resulting XML fragment will look similar to:
...
<setting name="PluginSettings" serializeAs="Xml">
<value>
<SerializableStringDictionary>
<Pair Name="property1" Value="True" />
<Pair Name="property2" Value="05/01/2011 0:00:00" />
</SerializableStringDictionary>
</value>
</setting>
...
The simplest answer would be to use a row & column delimiter to convert your dictionary to a single string. Then you just need to store 1 string in the settings file.
If you don't need to use the settings designer or edit your settings with a text editor, you can create a simple class that derives from ApplicationSettingsBase:
namespace MyNamespace
{
using System.Collections.Generic;
using System.Configuration;
/// <summary>
/// Persistent store for my parameters.
/// </summary>
public class MySettings : ApplicationSettingsBase
{
/// <summary>
/// The instance lock.
/// </summary>
private static readonly object InstanceLock = new object();
/// <summary>
/// The instance.
/// </summary>
private static MySettings instance;
/// <summary>
/// Prevents a default instance of the <see cref="MySettings"/> class
/// from being created.
/// </summary>
private MySettings()
{
// don't need to do anything
}
/// <summary>
/// Gets the singleton.
/// </summary>
public static MySettings Instance
{
get
{
lock (InstanceLock)
{
if (instance == null)
{
instance = new MySettings();
}
}
return instance;
}
}
/// <summary>
/// Gets or sets the parameters.
/// </summary>
[UserScopedSetting]
[SettingsSerializeAs(SettingsSerializeAs.Binary)]
public Dictionary<string, string> Parameters
{
get
{
return (Dictionary<string, string>)this["Parameters"];
}
set
{
this["Parameters"] = value;
}
}
}
}
The real trick is the [SettingsSerializeAs(SettingsSerializeAs.Binary)] attribute. Most (all?) classes can get serialized this way where SettingsSerializeAs.String or SettingsSerializeAs.Xml wont work for a Dictionary.
Use this in your code as you would normal settings:
// this code untested...
MySettings.Instance.Parameters["foo"] = "bar";
MySettings.Instance.Parameters.Save();
MySettings.Instance.Parameters.Reload();
string bar;
if (!MySettings.Instance.Parameters.TryGetValue("foo", out bar))
{
throw new Exception("Foobar");
}
If you want the Dictionary to serialize into something user editable, you must derive from Dictionary and play with TypeConverter (see Using Custom Classes with Application Settings).
Other than doing something like David's suggests, I would look into alternate storage for the Dictionary. Ultimately the Settings object serializes to disk.
Have you considered using XML to store your dictionary? That would provide a certain amount of extensibility if in the future you decide you want to be able to store other types of dictionaries. You might do something like:
<dictionary>
<entry key="myKey">
[whatever data you like]
</entry>
</dictionary>
Might be overkill, but you'd also be prepared in the case that you wanted to store more complex data, like custom objects.
You can also use a System.Collections.Specialized.StringCollection by putting key on even index and values on odd index.
/// <summary>
/// Emulate a Dictionary (Serialization pb)
/// </summary>
private static string getValue(System.Collections.Specialized.StringCollection list, string key)
{
for (int i = 0; i * 2 < list.Count; i++)
{
if (list[i] == key)
{
return list[i + 1];
}
}
return null;
}
/// <summary>
/// Emulate a Dictionary (Serialization pb)
/// </summary>
private static void setValue(System.Collections.Specialized.StringCollection list, string key, string value)
{
for (int i = 0; i * 2 < list.Count; i++)
{
if (list[i] == key)
{
list[i + 1] = value;
return;
}
}
list.Add(key);
list.Add(value);
}
You could create a custom class that exposes a Dictionary as a public property. Then you can specify this custom type as the type for your setting.
Edit:
I have just read that, for some reason, a generic dictionary cannot be XML-serialized, so my solution will probably not work (I haven't tested it though...). That's strange, because a generic list can be serialized without any problem.
You could still create a custom class that can be set as a user setting, but you will need to have a list exposed as a property instead of a dictionary.
Edit: This will return a Hashtable (for whatever reason, despite being a 'DictionarySectionHandler'). However, being that Hashtables and Dictionaries are so similar, it shouldn't be a large issue (though I realize Dictionaries are newer, parameterized, etc; I would have preferred dicitonaries myself, but this is what .NET gives us).
The best answer I just found for this is here. It returns a typesafe collection witout any muddling in code to transform it, and you create an obvious (and simple) collection in your .config file. I'm using this and it's quite straight forward for any future programmer (including yourself). It allows for stronger typing and more flexibility, without any overly-complicated and unnecessary parsing.
You can store a StringCollection. It is similar to this solution.
I made 2 extension methods to convert between StringCollection and a Dictionary. This is the easiest way I could think of.
public static class Extender
{
public static Dictionary<string, string> ToDictionary(this StringCollection sc)
{
if (sc.Count % 2 != 0) throw new InvalidDataException("Broken dictionary");
var dic = new Dictionary<string, string>();
for (var i = 0; i < sc.Count; i += 2)
{
dic.Add(sc[i], sc[i + 1]);
}
return dic;
}
public static StringCollection ToStringCollection(this Dictionary<string, string> dic)
{
var sc = new StringCollection();
foreach (var d in dic)
{
sc.Add(d.Key);
sc.Add(d.Value);
}
return sc;
}
}
class Program
{
static void Main(string[] args)
{
//var sc = new StringCollection();
//sc.Add("Key01");
//sc.Add("Val01");
//sc.Add("Key02");
//sc.Add("Val02");
var sc = Settings.Default.SC;
var dic = sc.ToDictionary();
var sc2 = dic.ToStringCollection();
Settings.Default.SC = sc2;
Settings.Default.Save();
}
}

Categories