|
1 | 1 | using System;
|
2 | 2 | using System.Collections.Generic;
|
3 | 3 | using System.Linq;
|
| 4 | +using System.Linq.Expressions; |
4 | 5 | using System.Reflection;
|
5 | 6 |
|
6 | 7 | using Microsoft.Extensions.Configuration;
|
@@ -50,6 +51,11 @@ public object ConvertTo(Type toType, ResolutionContext resolutionContext)
|
50 | 51 | if (IsContainer(toType, out var elementType) && TryCreateContainer(out var result))
|
51 | 52 | return result;
|
52 | 53 |
|
| 54 | + if (TryBuildCtorExpression(_section, toType, resolutionContext, out var ctorExpression)) |
| 55 | + { |
| 56 | + return Expression.Lambda<Func<object>>(ctorExpression).Compile().Invoke(); |
| 57 | + } |
| 58 | + |
53 | 59 | // MS Config binding can work with a limited set of primitive types and collections
|
54 | 60 | return _section.Get(toType);
|
55 | 61 |
|
@@ -94,6 +100,126 @@ bool TryCreateContainer(out object result)
|
94 | 100 | }
|
95 | 101 | }
|
96 | 102 |
|
| 103 | + internal static bool TryBuildCtorExpression( |
| 104 | + IConfigurationSection section, Type parameterType, ResolutionContext resolutionContext, out NewExpression ctorExpression) |
| 105 | + { |
| 106 | + ctorExpression = null; |
| 107 | + |
| 108 | + var typeDirective = section.GetValue<string>("$type") switch |
| 109 | + { |
| 110 | + not null => "$type", |
| 111 | + null => section.GetValue<string>("type") switch |
| 112 | + { |
| 113 | + not null => "type", |
| 114 | + null => null, |
| 115 | + }, |
| 116 | + }; |
| 117 | + |
| 118 | + var type = typeDirective switch |
| 119 | + { |
| 120 | + not null => Type.GetType(section.GetValue<string>(typeDirective), throwOnError: false), |
| 121 | + null => parameterType, |
| 122 | + }; |
| 123 | + |
| 124 | + if (type is null or { IsAbstract: true }) |
| 125 | + { |
| 126 | + return false; |
| 127 | + } |
| 128 | + |
| 129 | + var suppliedArguments = section.GetChildren().Where(s => s.Key != typeDirective) |
| 130 | + .ToDictionary(s => s.Key, StringComparer.OrdinalIgnoreCase); |
| 131 | + |
| 132 | + if (suppliedArguments.Count == 0 && |
| 133 | + type.GetConstructor(Type.EmptyTypes) is ConstructorInfo parameterlessCtor) |
| 134 | + { |
| 135 | + ctorExpression = Expression.New(parameterlessCtor); |
| 136 | + return true; |
| 137 | + } |
| 138 | + |
| 139 | + var ctor = |
| 140 | + (from c in type.GetConstructors() |
| 141 | + from p in c.GetParameters() |
| 142 | + let argumentBindResult = suppliedArguments.TryGetValue(p.Name, out var argValue) switch |
| 143 | + { |
| 144 | + true => new { success = true, hasMatch = true, value = (object)argValue }, |
| 145 | + false => p.HasDefaultValue switch |
| 146 | + { |
| 147 | + true => new { success = true, hasMatch = false, value = p.DefaultValue }, |
| 148 | + false => new { success = false, hasMatch = false, value = (object)null }, |
| 149 | + }, |
| 150 | + } |
| 151 | + group new { argumentBindResult, p.ParameterType } by c into gr |
| 152 | + where gr.All(z => z.argumentBindResult.success) |
| 153 | + let matchedArgs = gr.Where(z => z.argumentBindResult.hasMatch).ToList() |
| 154 | + orderby matchedArgs.Count descending, |
| 155 | + matchedArgs.Count(p => p.ParameterType == typeof(string)) descending |
| 156 | + select new |
| 157 | + { |
| 158 | + ConstructorInfo = gr.Key, |
| 159 | + ArgumentValues = gr.Select(z => new { Value = z.argumentBindResult.value, Type = z.ParameterType }) |
| 160 | + .ToList() |
| 161 | + }).FirstOrDefault(); |
| 162 | + |
| 163 | + if (ctor is null) |
| 164 | + { |
| 165 | + return false; |
| 166 | + } |
| 167 | + |
| 168 | + var ctorArguments = new List<Expression>(); |
| 169 | + foreach (var argumentValue in ctor.ArgumentValues) |
| 170 | + { |
| 171 | + if (TryBindToCtorArgument(argumentValue.Value, argumentValue.Type, resolutionContext, out var argumentExpression)) |
| 172 | + { |
| 173 | + ctorArguments.Add(argumentExpression); |
| 174 | + } |
| 175 | + else |
| 176 | + { |
| 177 | + return false; |
| 178 | + } |
| 179 | + } |
| 180 | + |
| 181 | + ctorExpression = Expression.New(ctor.ConstructorInfo, ctorArguments); |
| 182 | + return true; |
| 183 | + |
| 184 | + static bool TryBindToCtorArgument(object value, Type type, ResolutionContext resolutionContext, out Expression argumentExpression) |
| 185 | + { |
| 186 | + argumentExpression = null; |
| 187 | + |
| 188 | + if (value is IConfigurationSection s) |
| 189 | + { |
| 190 | + if (s.Value is string argValue) |
| 191 | + { |
| 192 | + var stringArgumentValue = new StringArgumentValue(argValue); |
| 193 | + try |
| 194 | + { |
| 195 | + argumentExpression = Expression.Constant( |
| 196 | + stringArgumentValue.ConvertTo(type, resolutionContext), |
| 197 | + type); |
| 198 | + |
| 199 | + return true; |
| 200 | + } |
| 201 | + catch (Exception) |
| 202 | + { |
| 203 | + return false; |
| 204 | + } |
| 205 | + } |
| 206 | + else if (s.GetChildren().Any()) |
| 207 | + { |
| 208 | + if (TryBuildCtorExpression(s, type, resolutionContext, out var ctorExpression)) |
| 209 | + { |
| 210 | + argumentExpression = ctorExpression; |
| 211 | + return true; |
| 212 | + } |
| 213 | + |
| 214 | + return false; |
| 215 | + } |
| 216 | + } |
| 217 | + |
| 218 | + argumentExpression = Expression.Constant(value, type); |
| 219 | + return true; |
| 220 | + } |
| 221 | + } |
| 222 | + |
97 | 223 | static bool IsContainer(Type type, out Type elementType)
|
98 | 224 | {
|
99 | 225 | elementType = null;
|
|
0 commit comments