Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ConfigurationBinder fails to bind class that implements IDictionary #77246

Closed
Tracked by #77390
svkuhn opened this issue Oct 20, 2022 · 4 comments · Fixed by #77582
Closed
Tracked by #77390

ConfigurationBinder fails to bind class that implements IDictionary #77246

svkuhn opened this issue Oct 20, 2022 · 4 comments · Fixed by #77582

Comments

@svkuhn
Copy link

svkuhn commented Oct 20, 2022

Description

Binding a Configuration to a class that implements IDictionary<string,T> always results in an empty Dictionary.
The set function throws a System.Reflection.TargetException. I think the problem is that the setter is reflected by Dictionary and not the actual type.

Reproduction Steps

Small test class:

	public class TestDictionaryBinding
	{
		public class CustomDictionary<TKey, TValue> : Dictionary<TKey, TValue> where TKey : notnull
		{

		}

		public class IDictionaryImplementationClass<TKey, TValue> : IDictionary<TKey, TValue> where TKey : notnull
		{
			private Dictionary<TKey, TValue> m_dict = new();

			public TValue this[TKey key] { get => m_dict[key]; set => m_dict[key] = value; }

			public ICollection<TKey> Keys => m_dict.Keys;

			public ICollection<TValue> Values => m_dict.Values;

			public int Count => m_dict.Count;

			public bool IsReadOnly => false;

			public void Add(TKey key, TValue value) => m_dict.Add(key, value);

			public void Add(KeyValuePair<TKey, TValue> item) => m_dict.Add(item.Key, item.Value);

			public void Clear() => m_dict.Clear();

			public bool Contains(KeyValuePair<TKey, TValue> item) => m_dict.Contains(item);

			public bool ContainsKey(TKey key) => m_dict.ContainsKey(key);

			public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) => throw new NotImplementedException();

			public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => m_dict.GetEnumerator();

			public bool Remove(TKey key) => m_dict.Remove(key);

			public bool Remove(KeyValuePair<TKey, TValue> item) => m_dict.Remove(item.Key);
			
			public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) => m_dict.TryGetValue(key, out value);

			IEnumerator IEnumerable.GetEnumerator() => m_dict.GetEnumerator();
		}

		[Fact]
		public void Test_InterfaceDictionary()
		{
			var _cfg = new Dictionary<string, string>()
			{
				{ "key", "value" }
			};

			var _config = new ConfigurationBuilder()
				.AddInMemoryCollection(_cfg!)
				.Build();

			var _dictionary = _config.Get<Dictionary<string, string>>(); // success
			var _dictionaryInterface = _config.Get<IDictionary<string, string>>(); // success
			var _customDictionaryClass = _config.Get<CustomDictionary<string, string>>(); // success
			var _customIDictionaryImplementation = _config.Get<IDictionaryImplementationClass<string, string>>(); // empty

			Assert.Single(_dictionary);
			Assert.Single(_dictionaryInterface);
			Assert.Single(_customDictionaryClass);
			Assert.Single(_customIDictionaryImplementation);
		}
	}

Expected behavior

Items from Configuration should be added to a IDictionary implementation

Actual behavior

Custom IDictionary implementation is empty

Regression?

Was working in
7.0.0-preview.7.22375.6

Known Workarounds

No response

Configuration

Used packages for Test:

Other information

No response

@ghost ghost added the untriaged New issue has not been triaged by the area owner label Oct 20, 2022
@ghost
Copy link

ghost commented Oct 20, 2022

Tagging subscribers to this area: @dotnet/area-system-collections
See info in area-owners.md if you want to be subscribed.

Issue Details

Description

Binding a Configuration to a class that implements IDictionary<string,T> always results in an empty Dictionary.
The set function throws as System.Reflection.TargetException. I think the problem is that the setter is reflected by Dictionary and not the actual type.

Reproduction Steps

public class TestDictionaryBinding
{
	public class CustomDictionary<TKey, TValue> : Dictionary<TKey, TValue> where TKey : notnull
	{

	}

	public class IDictionaryImplementationClass<TKey, TValue> : IDictionary<TKey, TValue> where TKey : notnull
	{
		private Dictionary<TKey, TValue> m_dict = new();

		public TValue this[TKey key] { get => m_dict[key]; set => m_dict[key] = value; }

		public ICollection<TKey> Keys => m_dict.Keys;

		public ICollection<TValue> Values => m_dict.Values;

		public int Count => m_dict.Count;

		public bool IsReadOnly => false;

		public void Add(TKey key, TValue value) => m_dict.Add(key, value);

		public void Add(KeyValuePair<TKey, TValue> item) => m_dict.Add(item.Key, item.Value);

		public void Clear() => m_dict.Clear();

		public bool Contains(KeyValuePair<TKey, TValue> item) => m_dict.Contains(item);

		public bool ContainsKey(TKey key) => m_dict.ContainsKey(key);

		public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) => throw new NotImplementedException();

		public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => m_dict.GetEnumerator();

		public bool Remove(TKey key) => m_dict.Remove(key);

		public bool Remove(KeyValuePair<TKey, TValue> item) => m_dict.Remove(item.Key);
		
		public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) => m_dict.TryGetValue(key, out value);

		IEnumerator IEnumerable.GetEnumerator() => m_dict.GetEnumerator();
	}

	[Fact]
	public void Test_InterfaceDictionary()
	{
		var _cfg = new Dictionary<string, string>()
		{
			{ "key", "value" }
		};

		var _config = new ConfigurationBuilder()
			.AddInMemoryCollection(_cfg!)
			.Build();

		var _dictionary = _config.Get<Dictionary<string, string>>(); // success
		var _dictionaryInterface = _config.Get<IDictionary<string, string>>(); // success
		var _customDictionaryClass = _config.Get<CustomDictionary<string, string>>(); // success
		var _customIDictionaryImplementation = _config.Get<IDictionaryImplementationClass<string, string>>(); // empty

		Assert.Single(_dictionary);
		Assert.Single(_dictionaryInterface);
		Assert.Single(_customDictionaryClass);
		Assert.Single(_customIDictionaryImplementation);
	}
}

Expected behavior

Items from Configuration should be added to a IDictionary implementation

Actual behavior

Custom IDictionary implementation is empty

Regression?

Was working in
7.0.0-preview.7.22375.6

Known Workarounds

No response

Configuration

Used packages for Test:

Other information

No response

Author: svkuhn
Assignees: -
Labels:

area-System.Collections

Milestone: -

@ghost
Copy link

ghost commented Oct 20, 2022

Tagging subscribers to this area: @dotnet/area-extensions-configuration
See info in area-owners.md if you want to be subscribed.

Issue Details

Description

Binding a Configuration to a class that implements IDictionary<string,T> always results in an empty Dictionary.
The set function throws a System.Reflection.TargetException. I think the problem is that the setter is reflected by Dictionary and not the actual type.

Reproduction Steps

Small test class:

public class TestDictionaryBinding
{
	public class CustomDictionary<TKey, TValue> : Dictionary<TKey, TValue> where TKey : notnull
	{

	}

	public class IDictionaryImplementationClass<TKey, TValue> : IDictionary<TKey, TValue> where TKey : notnull
	{
		private Dictionary<TKey, TValue> m_dict = new();

		public TValue this[TKey key] { get => m_dict[key]; set => m_dict[key] = value; }

		public ICollection<TKey> Keys => m_dict.Keys;

		public ICollection<TValue> Values => m_dict.Values;

		public int Count => m_dict.Count;

		public bool IsReadOnly => false;

		public void Add(TKey key, TValue value) => m_dict.Add(key, value);

		public void Add(KeyValuePair<TKey, TValue> item) => m_dict.Add(item.Key, item.Value);

		public void Clear() => m_dict.Clear();

		public bool Contains(KeyValuePair<TKey, TValue> item) => m_dict.Contains(item);

		public bool ContainsKey(TKey key) => m_dict.ContainsKey(key);

		public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) => throw new NotImplementedException();

		public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => m_dict.GetEnumerator();

		public bool Remove(TKey key) => m_dict.Remove(key);

		public bool Remove(KeyValuePair<TKey, TValue> item) => m_dict.Remove(item.Key);
		
		public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) => m_dict.TryGetValue(key, out value);

		IEnumerator IEnumerable.GetEnumerator() => m_dict.GetEnumerator();
	}

	[Fact]
	public void Test_InterfaceDictionary()
	{
		var _cfg = new Dictionary<string, string>()
		{
			{ "key", "value" }
		};

		var _config = new ConfigurationBuilder()
			.AddInMemoryCollection(_cfg!)
			.Build();

		var _dictionary = _config.Get<Dictionary<string, string>>(); // success
		var _dictionaryInterface = _config.Get<IDictionary<string, string>>(); // success
		var _customDictionaryClass = _config.Get<CustomDictionary<string, string>>(); // success
		var _customIDictionaryImplementation = _config.Get<IDictionaryImplementationClass<string, string>>(); // empty

		Assert.Single(_dictionary);
		Assert.Single(_dictionaryInterface);
		Assert.Single(_customDictionaryClass);
		Assert.Single(_customIDictionaryImplementation);
	}
}

Expected behavior

Items from Configuration should be added to a IDictionary implementation

Actual behavior

Custom IDictionary implementation is empty

Regression?

Was working in
7.0.0-preview.7.22375.6

Known Workarounds

No response

Configuration

Used packages for Test:

Other information

No response

Author: svkuhn
Assignees: -
Labels:

area-System.Collections, untriaged, area-Extensions-Configuration

Milestone: -

@eerhardt
Copy link
Member

fyi @SteveDunn - this looks like another regression from #68133

@Nick-Stanton Nick-Stanton added this to the 8.0.0 milestone Oct 20, 2022
@ghost ghost removed the untriaged New issue has not been triaged by the area owner label Oct 20, 2022
@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Oct 28, 2022
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Oct 30, 2022
@tarekgh
Copy link
Member

tarekgh commented Nov 11, 2022

This fix will be included in the next 7.0 servicing release. #78118

@ghost ghost locked as resolved and limited conversation to collaborators Dec 11, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants