• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

net-daemon / netdaemon / 6594659689

19 Oct 2023 05:58PM UTC coverage: 80.941% (-0.3%) from 81.222%
6594659689

push

github

web-flow
Fix alot of warnings (#956)

811 of 1148 branches covered (0.0%)

Branch coverage included in aggregate %.

28 of 28 new or added lines in 15 files covered. (100.0%)

2939 of 3485 relevant lines covered (84.33%)

50.62 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.19
/src/AppModel/NetDaemon.AppModel/Internal/Config/ConfigurationBinding.cs
1
using System.ComponentModel;
2
using System.Globalization;
3
using System.Reflection;
4
using Microsoft.Extensions.Configuration;
5
using Microsoft.VisualBasic.CompilerServices;
6

7
namespace NetDaemon.AppModel.Internal.Config;
8

9
internal interface IConfigurationBinding
10
{
11
    T? ToObject<T>(IConfiguration configuration);
12
}
13

14
/// <summary>
15
///     Class that allows binding strongly typed objects to configuration values.
16
/// </summary>
17
/// <remarks>
18
///     This is a scaled down and modified version of .NET 7 implementation.
19
/// </remarks>
20
internal class ConfigurationBinding : IConfigurationBinding
21
{
22
    private readonly IServiceProvider _provider;
23

24
    public ConfigurationBinding(IServiceProvider provider)
29✔
25
    {
26
        _provider = provider;
29✔
27
    }
29✔
28

29
    /// <summary>
30
    ///     Attempts to bind the configuration instance to a new instance of type T.
31
    ///     If  configuration section has a value, that will be used.
32
    ///     Otherwise binding by matching property names against configuration keys recursively.
33
    /// </summary>
34
    /// <typeparam name="T">The type of the new instance to bind.</typeparam>
35
    /// <param name="configuration">The configuration instance to bind.</param>
36
    /// <returns>The new instance of T if successful, default(T) otherwise.</returns>
37
    public T? ToObject<T>(IConfiguration configuration)
38
    {
39
        var result = GetObject(configuration, typeof(T));
28✔
40
        if (result == null) return default;
19!
41
        return (T)result;
19✔
42
    }
43

44
    /// <summary>
45
    ///     Attempts to bind the configuration instance to a new instance of type T.
46
    ///     If  configuration section has a value, that will be used.
47
    ///     Otherwise binding by matching property names against configuration keys recursively.
48
    /// </summary>
49
    /// <param name="configuration">The configuration instance to bind.</param>
50
    /// <param name="type">The type of the new instance to bind.</param>
51
    /// <returns>The new instance if successful, null otherwise.</returns>
52
    private object? GetObject(IConfiguration configuration, Type type)
53
    {
54
        return BindInstance(type, null, configuration);
28✔
55
    }
56

57
    private void BindNonScalar(IConfiguration configuration, object? instance)
58
    {
59
        if (instance == null) return;
20!
60

61
        // If we came this far and it is readonly collection
62
        // it means that the collection was already initialized
63
        // that is not a valid operation
64
        ThrowIfReadonlyCollection(instance);
20✔
65

66
        foreach (var property in GetAllProperties(instance.GetType().GetTypeInfo()))
114✔
67
            BindProperty(property, instance, configuration);
43✔
68
    }
12✔
69

70
    private static void ThrowIfReadonlyCollection(object? instance)
71
    {
72
        var typ = instance?.GetType()!;
20!
73

74
        if (FindOpenGenericInterface(typeof(IEnumerable<>), typ) != null)
20✔
75
        {
76
            throw new InvalidOperationException("Read-only collections cannot be initialized and needs to be nullable!");
4✔
77
        }
78

79
        if (FindOpenGenericInterface(typeof(IReadOnlyCollection<>), typ) != null)
16!
80
        {
81
            throw new InvalidOperationException("Read-only collections cannot be initialized and needs to be nullable!");
×
82
        }
83
    }
16✔
84

85
    private void BindProperty(PropertyInfo property, object? instance, IConfiguration config)
86
    {
87
        // We don't support set only, non public, or indexer properties
88
        if (property.GetMethod?.IsPublic != true ||
43!
89
            property.GetMethod.GetParameters().Length > 0)
43✔
90
            return;
5✔
91

92
        var propertyValue = property.GetValue(instance);
38✔
93
        var hasSetter = property.SetMethod?.IsPublic == true;
38!
94

95
        if (propertyValue == null && !hasSetter)
38!
96
            // Property doesn't have a value and we cannot set it so there is no
97
            // point in going further down the graph
98
            return;
×
99

100
        propertyValue = BindInstance(property.PropertyType, propertyValue, config.GetSection(property.Name));
38✔
101

102
        if (propertyValue != null && hasSetter) property.SetValue(instance, propertyValue);
68✔
103
    }
34✔
104

105
    private object BindToCollection(TypeInfo typeInfo, IConfiguration config)
106
    {
107
        var type = typeof(List<>).MakeGenericType(typeInfo.GenericTypeArguments[0]);
9✔
108
        var instance = Activator.CreateInstance(type) ??
9!
109
                       throw new InvalidOperationException();
9✔
110
        BindCollection(instance, type, config);
9✔
111
        return instance;
9✔
112
    }
113

114
    // Try to create an array/dictionary instance to back various collection interfaces
115
    private object? AttemptBindToCollectionInterfaces(Type type, IConfiguration config)
116
    {
117
        var typeInfo = type.GetTypeInfo();
39✔
118

119
        if (!typeInfo.IsInterface) return null;
61✔
120

121
        var collectionInterface = FindOpenGenericInterface(typeof(IReadOnlyList<>), type);
17✔
122
        if (collectionInterface != null)
17✔
123
            // IEnumerable<T> is guaranteed to have exactly one parameter
124
            return BindToCollection(typeInfo, config);
1✔
125

126
        collectionInterface = FindOpenGenericInterface(typeof(IReadOnlyDictionary<,>), type);
16✔
127
        if (collectionInterface != null)
16✔
128
        {
129
            var dictionaryType =
1✔
130
                typeof(Dictionary<,>).MakeGenericType(typeInfo.GenericTypeArguments[0],
1✔
131
                    typeInfo.GenericTypeArguments[1]);
1✔
132
            var instance = Activator.CreateInstance(dictionaryType);
1✔
133
            BindDictionary(instance, dictionaryType, config);
1✔
134
            return instance;
1✔
135
        }
136

137
        collectionInterface = FindOpenGenericInterface(typeof(IDictionary<,>), type);
15✔
138
        if (collectionInterface != null)
15✔
139
        {
140
            var instance = Activator.CreateInstance(
6✔
141
                typeof(Dictionary<,>).MakeGenericType(typeInfo.GenericTypeArguments[0],
6✔
142
                    typeInfo.GenericTypeArguments[1]));
6✔
143
            BindDictionary(instance, collectionInterface, config);
6✔
144
            return instance;
6✔
145
        }
146

147
        collectionInterface = FindOpenGenericInterface(typeof(IReadOnlyCollection<>), type);
9✔
148
        if (collectionInterface != null)
9✔
149
            // IReadOnlyCollection<T> is guaranteed to have exactly one parameter
150
            return BindToCollection(typeInfo, config);
1✔
151

152
        collectionInterface = FindOpenGenericInterface(typeof(ICollection<>), type);
8✔
153
        if (collectionInterface != null)
8✔
154
            // ICollection<T> is guaranteed to have exactly one parameter
155
            return BindToCollection(typeInfo, config);
4✔
156

157
        collectionInterface = FindOpenGenericInterface(typeof(IEnumerable<>), type);
4✔
158
        return collectionInterface != null ? BindToCollection(typeInfo, config) : null;
4✔
159
    }
160

161
    private object? BindInstance(Type type, object? instance, IConfiguration config)
162
    {
163
        // if binding IConfigurationSection, break early
164
        if (type == typeof(IConfigurationSection)) return config;
102!
165

166
        var section = config as IConfigurationSection;
102✔
167
        var configValue = section?.Value;
102!
168
        if (configValue != null && TryConvertValue(type, configValue, out var convertedValue, out var error))
102✔
169
        {
170
            if (error != null) throw error;
59!
171

172
            // Leaf nodes are always reinitialized
173
            return convertedValue;
59✔
174
        }
175

176
        if (config.GetChildren().Any() != true) return instance;
43!
177

178
        // If we don't have an instance, try to create one
179
        if (instance == null)
43✔
180
        {
181
            // We are already done if binding to a new collection instance worked
182
            instance = AttemptBindToCollectionInterfaces(type, config);
39✔
183
            if (instance != null) return instance;
55✔
184

185
            instance = CreateInstance(type);
23✔
186
        }
187

188
        // See if its a Dictionary
189
        var collectionInterface = FindOpenGenericInterface(typeof(IDictionary<,>), type);
22✔
190
        if (collectionInterface != null)
22!
191
        {
192
            BindDictionary(instance, collectionInterface, config);
×
193
        }
194
        else if (type.IsArray)
22✔
195
        {
196
            instance = BindArray((Array)instance, config);
1✔
197
        }
198
        else
199
        {
200
            // See if its an ICollection
201
            collectionInterface = FindOpenGenericInterface(typeof(ICollection<>), type);
21✔
202
            if (collectionInterface != null)
21✔
203
                BindCollection(instance, collectionInterface, config);
1✔
204
            // Something else
205
            else
206
                BindNonScalar(config, instance);
20✔
207
        }
208

209
        return instance;
14✔
210
    }
211

212
    private static object CreateInstance(Type type)
213
    {
214
        var typeInfo = type.GetTypeInfo();
23✔
215

216
        if (typeInfo.IsInterface || typeInfo.IsAbstract) throw new InvalidOperationException();
25✔
217

218
        if (type.IsArray)
21✔
219
        {
220
            if (typeInfo.GetArrayRank() > 1) throw new InvalidOperationException();
3✔
221
            var typ = typeInfo.GetElementType()!;
1✔
222
            return Array.CreateInstance(typ, 0);
1✔
223
        }
224

225
        var hasDefaultConstructor =
19✔
226
            typeInfo.DeclaredConstructors.Any(ctor => ctor.IsPublic && ctor.GetParameters().Length == 0);
43✔
227
        if (!hasDefaultConstructor) throw new InvalidOperationException();
20✔
228

229
        try
230
        {
231
            return Activator.CreateInstance(type)!;
18✔
232
        }
233
        catch (Exception)
1✔
234
        {
235
            throw new InvalidOperationException();
1✔
236
        }
237
    }
17✔
238

239
    private void BindDictionary(object? dictionary, Type dictionaryType, IConfiguration config)
240
    {
241
        var typeInfo = dictionaryType.GetTypeInfo();
7✔
242

243
        // IDictionary<K,V> is guaranteed to have exactly two parameters
244
        var keyType = typeInfo.GenericTypeArguments[0];
7✔
245
        var valueType = typeInfo.GenericTypeArguments[1];
7✔
246
        var keyTypeIsEnum = keyType.GetTypeInfo().IsEnum;
7✔
247

248
        if (keyType != typeof(string) && !keyTypeIsEnum)
7✔
249
            // We only support string and enum keys
250
            return;
1✔
251

252
        var setter = typeInfo.GetDeclaredProperty("Item") ??
6!
253
                     throw new InvalidOperationException();
6✔
254

255
        foreach (var child in config.GetChildren())
38✔
256
        {
257
            var item = BindInstance(
13✔
258
                valueType,
13✔
259
                null,
13✔
260
                child);
13✔
261
            if (item == null) continue;
13✔
262
            if (keyType == typeof(string))
13!
263
            {
264
                var key = child.Key;
13✔
265
                setter.SetValue(dictionary, item, new object[] { key });
13✔
266
            }
267
            else if (keyTypeIsEnum)
×
268
            {
269
                var key = Convert.ToInt32(Enum.Parse(keyType, child.Key), CultureInfo.InvariantCulture);
×
270
                setter.SetValue(dictionary, item, new object[] { key });
×
271
            }
272
        }
273
    }
6✔
274

275
    private void BindCollection(object collection, Type collectionType, IConfiguration config)
276
    {
277
        var typeInfo = collectionType.GetTypeInfo();
10✔
278

279
        // ICollection<T> is guaranteed to have exactly one parameter
280
        var itemType = typeInfo.GenericTypeArguments[0];
10✔
281
        var addMethod = typeInfo.GetDeclaredMethod("Add") ??
10!
282
                        throw new InvalidOperationException();
10✔
283

284
        foreach (var section in config.GetChildren())
62✔
285
            try
286
            {
287
                var item = BindInstance(
21✔
288
                    itemType,
21✔
289
                    null,
21✔
290
                    section);
21✔
291
                if (item != null) addMethod.Invoke(collection, new[] { item });
42✔
292
            }
21✔
293
            catch
×
294
            {
295
                // ignored
296
            }
×
297
    }
10✔
298

299
    private Array BindArray(Array source, IConfiguration config)
300
    {
301
        var children = config.GetChildren().ToArray();
1✔
302
        var arrayLength = source.Length;
1✔
303
        var elementType = source.GetType().GetElementType() ??
1!
304
                          throw new InvalidOperationException();
1✔
305

306
        var newArray = Array.CreateInstance(elementType, arrayLength + children.Length);
1✔
307

308
        // binding to array has to preserve already initialized arrays with values
309
        if (arrayLength > 0) Array.Copy(source, newArray, arrayLength);
1!
310

311
        for (var i = 0; i < children.Length; i++)
6✔
312
            try
313
            {
314
                var item = BindInstance(
2✔
315
                    elementType,
2✔
316
                    null,
2✔
317
                    children[i]);
2✔
318
                if (item != null) newArray.SetValue(item, arrayLength + i);
4✔
319
            }
2✔
320
            catch
×
321
            {
322
                // ignored
323
            }
×
324

325
        return newArray;
1✔
326
    }
327

328
    private bool TryConvertValue(Type type, string value, out object? result, out Exception? error)
329
    {
330
        error = null;
59✔
331
        result = null;
59✔
332
        if (type == typeof(object))
59!
333
        {
334
            result = value;
×
335
            return true;
×
336
        }
337

338
        if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
59!
339
        {
340
            if (string.IsNullOrEmpty(value)) return true;
×
341
            var typ = Nullable.GetUnderlyingType(type) ??
×
342
                      throw new InvalidOperationException();
×
343

344
            return TryConvertValue(typ, value, out result, out error);
×
345
        }
346

347
        var converter = TypeDescriptor.GetConverter(type);
59✔
348
        if (converter.CanConvertFrom(typeof(string)))
59✔
349
        {
350
            try
351
            {
352
                result = converter.ConvertFromInvariantString(value);
43✔
353
            }
43✔
354
            catch (Exception)
×
355
            {
356
                error = new InvalidOperationException();
×
357
            }
×
358

359
            return true;
43✔
360
        }
361

362
        // No standard converter is available so lets“s try find
363
        // a registered service that converts to the type from string
364

365
        try
366
        {
367
            result = ActivatorUtilities.CreateInstance(_provider, type, value);
16✔
368
        }
16✔
369
        catch (Exception e)
×
370
        {
371
            throw new InvalidOperationException($"Failed to convert from string to type {type.FullName}, please check you have one string in constructor!", e);
×
372
        }
373

374
        return true;
16✔
375
    }
376

377
    private static Type? FindOpenGenericInterface(Type expected, Type actual)
378
    {
379
        var actualTypeInfo = actual.GetTypeInfo();
148✔
380
        if (actualTypeInfo.IsGenericType &&
148✔
381
            actual.GetGenericTypeDefinition() == expected)
148✔
382
            return actual;
16✔
383

384
        return actualTypeInfo.ImplementedInterfaces.FirstOrDefault(interfaceType =>
132✔
385
            interfaceType.GetTypeInfo().IsGenericType && interfaceType.GetGenericTypeDefinition() == expected);
295✔
386
    }
387

388
    private static IEnumerable<PropertyInfo> GetAllProperties(TypeInfo type)
389
    {
390
        var allProperties = new List<PropertyInfo>();
16✔
391

392
        var currentType = type;
16✔
393
        do
394
        {
395
            allProperties.AddRange(currentType.DeclaredProperties);
16✔
396
            currentType = currentType.BaseType?.GetTypeInfo() ??
16!
397
                          throw new InvalidOperationException();
16✔
398
        } while (currentType != typeof(object).GetTypeInfo());
16✔
399

400
        return allProperties;
16✔
401
    }
402
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc