Reputation: 800
I am having trouble using Microsoft.Extensions.Configuration
to bind a dictionary that contains keys that have colon :
in them.
I have done an example that has a dictionary "GoodErrorMappings" which do not contain any colon in the key. These are mapped correctly.
I have created another dictionary "BadErrorMappings" that has a colon in the key. This dictionary seems to not be mapped correctly after it sees the first colon in the dictionary key.
I have had a quick look in the source and cannot see an obvious way to override the colon as a delimiter.
Any help would be appreciated.
Doco: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration
Assembly version: "Microsoft.NETCore.App" "1.1.0"
C# Code:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
namespace OptionsTest
{
public class Program
{
public static void Main(string[] args)
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json");
var config = builder.Build();
var services = new ServiceCollection().AddOptions();
services.Configure<ApiConfig>(x => config.GetSection("ApiConfig").Bind(x));
var apiConfig = services.BuildServiceProvider().GetService<IOptions<ApiConfig>>().Value;
Debug.WriteLine(string.Format("\r\nGoodErrorMappings: {0}", JsonConvert.SerializeObject(apiConfig.GoodErrorMappings, Formatting.Indented)));
Debug.WriteLine(string.Format("\r\nBadErrorMappings: {0}", JsonConvert.SerializeObject(apiConfig.BadErrorMappings, Formatting.Indented)));
Console.ReadLine();
}
}
public class ApiConfig
{
public Dictionary<string, ErrorMapping> GoodErrorMappings { get; set; } = new Dictionary<string, ErrorMapping>();
public Dictionary<string, ErrorMapping> BadErrorMappings { get; set; } = new Dictionary<string, ErrorMapping>();
}
public class ErrorMapping
{
public int HttpStatusCode { get; set; }
public int ErrorCode { get; set; }
public string Description { get; set; }
}
}
AppSettings.json:
{
"ApiConfig": {
"GoodErrorMappings": {
"/SOMEVALUE/BLAH.123": {
"httpStatusCode": "500",
"errorCode": "110012",
"description": "Invalid error description 1"
},
"/SOMEVALUE/BLAH.456": {
"httpStatusCode": "500",
"errorCode": "110013",
"description": "Invalid error description 2"
}
},
"BadErrorMappings": {
"/SOMEVALUE/BLAH:123": {
"httpStatusCode": "500",
"errorCode": "110012",
"description": "Invalid error description 1"
},
"/SOMEVALUE/BLAH:456": {
"httpStatusCode": "500",
"errorCode": "110013",
"description": "Invalid error description 2"
}
}
}
}
Output:
GoodErrorMappings: {
"/SOMEVALUE/BLAH.123": {
"HttpStatusCode": 500,
"ErrorCode": 110012,
"Description": "Invalid error description 1"
},
"/SOMEVALUE/BLAH.456": {
"HttpStatusCode": 500,
"ErrorCode": 110013,
"Description": "Invalid error description 2"
}
}
BadErrorMappings: {
"/SOMEVALUE/BLAH": {
"HttpStatusCode": 0,
"ErrorCode": 0,
"Description": null
}
}
Upvotes: 5
Views: 6464
Reputation: 1593
The reason for what is happening is the colon has special meaning in configuration binding.
The colon can be used to identify collections when provided within a key string. I've demonstrated it in the following code change to your sample app. I also updated your BadErrorMappings to be an array in your binding since that is what the colon separator is doing.
program.cs
public class Program
{
public static void Main(string[] args)
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json");
var config = builder.Build();
var services = new ServiceCollection().AddOptions();
services.Configure<ApiConfig>(x => config.GetSection("ApiConfig").Bind(x));
services.Configure<Fruit>(x => config.GetSection("Fruit").Bind(x));
var serviceProvider = services.BuildServiceProvider();
var apiConfig = serviceProvider.GetService<IOptions<ApiConfig>>().Value;
var fruit = serviceProvider.GetService<IOptions<Fruit>>().Value;
Console.WriteLine(string.Format("\r\nGoodErrorMappings: {0}", JsonConvert.SerializeObject(apiConfig.GoodErrorMappings, Formatting.Indented)));
Console.WriteLine(string.Format("\r\nBadErrorMappings: {0}", JsonConvert.SerializeObject(apiConfig.BadErrorMappings, Formatting.Indented)));
Console.WriteLine(string.Format("\r\nFruit: {0}", JsonConvert.SerializeObject(fruit, Formatting.Indented)));
Console.ReadLine();
}
}
public class Fruit : List<string>
{
}
public class ApiConfig
{
public Dictionary<string, ErrorMapping> GoodErrorMappings { get; set; } = new Dictionary<string, ErrorMapping>();
public Dictionary<string, ErrorMapping[]> BadErrorMappings { get; set; } = new Dictionary<string, ErrorMapping[]>();
}
public class ErrorMapping
{
public int HttpStatusCode { get; set; }
public int ErrorCode { get; set; }
public string Description { get; set; }
}
appsettings.json
{
"ApiConfig": {
"GoodErrorMappings": {
"/SOMEVALUE/BLAH.123": {
"httpStatusCode": "500",
"errorCode": "110012",
"description": "Invalid error description 1"
},
"/SOMEVALUE/BLAH.456": {
"httpStatusCode": "500",
"errorCode": "110013",
"description": "Invalid error description 2"
}
},
"BadErrorMappings": {
"/SOMEVALUE/BLAH:123": {
"httpStatusCode": "500",
"errorCode": "110012",
"description": "Invalid error description 1"
},
"/SOMEVALUE/BLAH:456": {
"httpStatusCode": "500",
"errorCode": "110013",
"description": "Invalid error description 2"
}
}
},
"Fruit:0": "Apple",
"Fruit:1": "Orange"
}
You can see the aspnet team leveraging this within their unit tests as well so this is intended behavior.
Upvotes: 5
Reputation: 7069
The simplest solution I found is to convert your Dictionary<string, string>
to a List<KeyValuePair<string, ErrorMapping>>
. JSON.NET then converts your List into an array of objects with the form { Key: 'keyname', Value: 'value' }
. This works well if you accept the required model change.
Use List<KeyValuePair<string, ErrorMapping>>
instead of Dictionary<string, ErrorMapping>
.
public class ApiConfig
{
public List<KeyValuePair<string, ErrorMapping>> GoodErrorMappings { get; set; } = new List<KeyValuePair<string, ErrorMapping>>();
public List<KeyValuePair<string, ErrorMapping>> BadErrorMappings { get; set; } = new List<KeyValuePair<string, ErrorMapping>>();
}
Upvotes: 1