Skip to content

Commit 0ab5073

Browse files
authored
Merge pull request #422 from serilog/dev
8.0.1 Release
2 parents 014b42c + cfe1b52 commit 0ab5073

12 files changed

+1559
-157
lines changed

Diff for: .gitignore

-1
Original file line numberDiff line numberDiff line change
@@ -201,4 +201,3 @@ FakesAssemblies/
201201
project.lock.json
202202

203203
artifacts/
204-
/test/TestApp-*

Diff for: Build.ps1

+5-2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ if($LASTEXITCODE -ne 0) { throw 'pack failed' }
3030

3131
Write-Output "build: Testing"
3232

33-
& dotnet test test\Serilog.Settings.Configuration.Tests\Serilog.Settings.Configuration.Tests.csproj
33+
# Dotnet test doesn't run separate TargetFrameworks in parallel: https://github.com/dotnet/sdk/issues/19147
34+
# Workaround: use `dotnet test` on dlls directly in order to pass the `--parallel` option to vstest.
35+
# The _reported_ runtime is wrong but the _actual_ used runtime is correct, see https://github.com/microsoft/vstest/issues/2037#issuecomment-720549173
36+
& dotnet test test\Serilog.Settings.Configuration.Tests\bin\Release\*\Serilog.Settings.Configuration.Tests.dll --parallel
3437

35-
if($LASTEXITCODE -ne 0) { throw 'unit tests failed' }
38+
if($LASTEXITCODE -ne 0) { throw 'unit tests failed' }

Diff for: README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
A Serilog settings provider that reads from [Microsoft.Extensions.Configuration](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.1) sources, including .NET Core's `appsettings.json` file.
44

5-
By default, configuration is read from the `Serilog` section.
5+
By default, configuration is read from the `Serilog` section that should be at the **top level** of the configuration file.
66

77
```json
88
{
@@ -328,13 +328,13 @@ Destructuring means extracting pieces of information from an object and create p
328328
{
329329
"Name": "With",
330330
"Args": {
331-
"policy": "policy": "MySecondNamespace.SecondDestructuringPolicy, MySecondAssembly"
331+
"policy": "MySecondNamespace.SecondDestructuringPolicy, MySecondAssembly"
332332
}
333333
},
334334
{
335335
"Name": "With",
336336
"Args": {
337-
"policy": "policy": "MyThirdNamespace.ThirdDestructuringPolicy, MyThirdAssembly"
337+
"policy": "MyThirdNamespace.ThirdDestructuringPolicy, MyThirdAssembly"
338338
}
339339
},
340340
],

Diff for: global.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
{
22
"sdk": {
3-
"version": "8.0.100"
3+
"version": "8.0.100",
4+
"allowPrerelease": false,
5+
"rollForward": "latestFeature"
46
}
57
}

Diff for: src/Serilog.Settings.Configuration/Serilog.Settings.Configuration.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<PropertyGroup>
44
<Description>Microsoft.Extensions.Configuration (appsettings.json) support for Serilog.</Description>
55
<!-- This must match the major and minor components of the referenced Microsoft.Extensions.Logging package. -->
6-
<VersionPrefix>8.0.0</VersionPrefix>
6+
<VersionPrefix>8.0.1</VersionPrefix>
77
<Authors>Serilog Contributors</Authors>
88
<!-- These must match the Dependencies tab in https://www.nuget.org/packages/microsoft.settings.configuration at
99
the target version. -->

Diff for: src/Serilog.Settings.Configuration/Settings/Configuration/ObjectArgumentValue.cs

+150-72
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System.Diagnostics.CodeAnalysis;
2-
using System.Linq.Expressions;
32
using System.Reflection;
43

54
using Microsoft.Extensions.Configuration;
@@ -46,13 +45,16 @@ public ObjectArgumentValue(IConfigurationSection section, IReadOnlyCollection<As
4645
if (toType.IsArray)
4746
return CreateArray();
4847

48+
// Only try to call ctor when type is explicitly specified in _section
49+
if (TryCallCtorExplicit(_section, resolutionContext, out var ctorResult))
50+
return ctorResult;
51+
4952
if (IsContainer(toType, out var elementType) && TryCreateContainer(out var container))
5053
return container;
5154

52-
if (TryBuildCtorExpression(_section, toType, resolutionContext, out var ctorExpression))
53-
{
54-
return Expression.Lambda<Func<object>>(ctorExpression).Compile().Invoke();
55-
}
55+
// Without a type explicitly specified, attempt to call ctor of toType
56+
if (TryCallCtorImplicit(_section, toType, resolutionContext, out ctorResult))
57+
return ctorResult;
5658

5759
// MS Config binding can work with a limited set of primitive types and collections
5860
return _section.Get(toType);
@@ -76,33 +78,41 @@ bool TryCreateContainer([NotNullWhen(true)] out object? result)
7678
{
7779
result = null;
7880

79-
if (toType.GetConstructor(Type.EmptyTypes) == null)
80-
return false;
81-
82-
// https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/object-and-collection-initializers#collection-initializers
83-
var addMethod = toType.GetMethods().FirstOrDefault(m => !m.IsStatic && m.Name == "Add" && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == elementType);
84-
if (addMethod == null)
85-
return false;
81+
if (IsConstructableDictionary(toType, elementType, out var concreteType, out var keyType, out var valueType, out var addMethod))
82+
{
83+
result = Activator.CreateInstance(concreteType) ?? throw new InvalidOperationException($"Activator.CreateInstance returned null for {concreteType}");
8684

87-
var configurationElements = _section.GetChildren().ToArray();
88-
result = Activator.CreateInstance(toType) ?? throw new InvalidOperationException($"Activator.CreateInstance returned null for {toType}");
85+
foreach (var section in _section.GetChildren())
86+
{
87+
var argumentValue = ConfigurationReader.GetArgumentValue(section, _configurationAssemblies);
88+
var key = new StringArgumentValue(section.Key).ConvertTo(keyType, resolutionContext);
89+
var value = argumentValue.ConvertTo(valueType, resolutionContext);
90+
addMethod.Invoke(result, new[] { key, value });
91+
}
92+
return true;
93+
}
94+
else if (IsConstructableContainer(toType, elementType, out concreteType, out addMethod))
95+
{
96+
result = Activator.CreateInstance(concreteType) ?? throw new InvalidOperationException($"Activator.CreateInstance returned null for {concreteType}");
8997

90-
for (int i = 0; i < configurationElements.Length; ++i)
98+
foreach (var section in _section.GetChildren())
99+
{
100+
var argumentValue = ConfigurationReader.GetArgumentValue(section, _configurationAssemblies);
101+
var value = argumentValue.ConvertTo(elementType, resolutionContext);
102+
addMethod.Invoke(result, new[] { value });
103+
}
104+
return true;
105+
}
106+
else
91107
{
92-
var argumentValue = ConfigurationReader.GetArgumentValue(configurationElements[i], _configurationAssemblies);
93-
var value = argumentValue.ConvertTo(elementType, resolutionContext);
94-
addMethod.Invoke(result, new[] { value });
108+
return false;
95109
}
96-
97-
return true;
98110
}
99111
}
100112

101-
internal static bool TryBuildCtorExpression(
102-
IConfigurationSection section, Type parameterType, ResolutionContext resolutionContext, [NotNullWhen(true)] out NewExpression? ctorExpression)
113+
bool TryCallCtorExplicit(
114+
IConfigurationSection section, ResolutionContext resolutionContext, [NotNullWhen(true)] out object? value)
103115
{
104-
ctorExpression = null;
105-
106116
var typeDirective = section.GetValue<string>("$type") switch
107117
{
108118
not null => "$type",
@@ -116,21 +126,39 @@ internal static bool TryBuildCtorExpression(
116126
var type = typeDirective switch
117127
{
118128
not null => Type.GetType(section.GetValue<string>(typeDirective)!, throwOnError: false),
119-
null => parameterType,
129+
null => null,
120130
};
121131

122132
if (type is null or { IsAbstract: true })
123133
{
134+
value = null;
124135
return false;
125136
}
137+
else
138+
{
139+
var suppliedArguments = section.GetChildren().Where(s => s.Key != typeDirective)
140+
.ToDictionary(s => s.Key, StringComparer.OrdinalIgnoreCase);
141+
return TryCallCtor(type, suppliedArguments, resolutionContext, out value);
142+
}
126143

127-
var suppliedArguments = section.GetChildren().Where(s => s.Key != typeDirective)
144+
}
145+
146+
bool TryCallCtorImplicit(
147+
IConfigurationSection section, Type parameterType, ResolutionContext resolutionContext, out object? value)
148+
{
149+
var suppliedArguments = section.GetChildren()
128150
.ToDictionary(s => s.Key, StringComparer.OrdinalIgnoreCase);
151+
return TryCallCtor(parameterType, suppliedArguments, resolutionContext, out value);
152+
}
153+
154+
bool TryCallCtor(Type type, Dictionary<string, IConfigurationSection> suppliedArguments, ResolutionContext resolutionContext, [NotNullWhen(true)] out object? value)
155+
{
156+
value = null;
129157

130158
if (suppliedArguments.Count == 0 &&
131159
type.GetConstructor(Type.EmptyTypes) is ConstructorInfo parameterlessCtor)
132160
{
133-
ctorExpression = Expression.New(parameterlessCtor);
161+
value = parameterlessCtor.Invoke([]);
134162
return true;
135163
}
136164

@@ -163,76 +191,126 @@ where gr.All(z => z.argumentBindResult.success)
163191
return false;
164192
}
165193

166-
var ctorArguments = new List<Expression>();
167-
foreach (var argumentValue in ctor.ArgumentValues)
194+
var ctorArguments = new object?[ctor.ArgumentValues.Count];
195+
for (var i = 0; i < ctor.ArgumentValues.Count; i++)
168196
{
169-
if (TryBindToCtorArgument(argumentValue.Value, argumentValue.Type, resolutionContext, out var argumentExpression))
197+
var argument = ctor.ArgumentValues[i];
198+
var valueValue = argument.Value;
199+
if (valueValue is IConfigurationSection s)
170200
{
171-
ctorArguments.Add(argumentExpression);
172-
}
173-
else
174-
{
175-
return false;
201+
var argumentValue = ConfigurationReader.GetArgumentValue(s, _configurationAssemblies);
202+
valueValue = argumentValue.ConvertTo(argument.Type, resolutionContext);
176203
}
204+
ctorArguments[i] = valueValue;
177205
}
178206

179-
ctorExpression = Expression.New(ctor.ConstructorInfo, ctorArguments);
207+
value = ctor.ConstructorInfo.Invoke(ctorArguments);
180208
return true;
209+
}
181210

182-
static bool TryBindToCtorArgument(object value, Type type, ResolutionContext resolutionContext, [NotNullWhen(true)] out Expression? argumentExpression)
211+
static bool IsContainer(Type type, [NotNullWhen(true)] out Type? elementType)
212+
{
213+
elementType = null;
214+
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
183215
{
184-
argumentExpression = null;
185-
186-
if (value is IConfigurationSection s)
216+
elementType = type.GetGenericArguments()[0];
217+
return true;
218+
}
219+
foreach (var iface in type.GetInterfaces())
220+
{
221+
if (iface.IsGenericType)
187222
{
188-
if (s.Value is string argValue)
223+
if (iface.GetGenericTypeDefinition() == typeof(IEnumerable<>))
189224
{
190-
var stringArgumentValue = new StringArgumentValue(argValue);
191-
try
192-
{
193-
argumentExpression = Expression.Constant(
194-
stringArgumentValue.ConvertTo(type, resolutionContext),
195-
type);
196-
197-
return true;
198-
}
199-
catch (Exception)
200-
{
201-
return false;
202-
}
225+
elementType = iface.GetGenericArguments()[0];
226+
return true;
203227
}
204-
else if (s.GetChildren().Any())
205-
{
206-
if (TryBuildCtorExpression(s, type, resolutionContext, out var ctorExpression))
207-
{
208-
argumentExpression = ctorExpression;
209-
return true;
210-
}
228+
}
229+
}
211230

212-
return false;
231+
return false;
232+
}
233+
234+
static bool IsConstructableDictionary(Type type, Type elementType, [NotNullWhen(true)] out Type? concreteType, [NotNullWhen(true)] out Type? keyType, [NotNullWhen(true)] out Type? valueType, [NotNullWhen(true)] out MethodInfo? addMethod)
235+
{
236+
concreteType = null;
237+
keyType = null;
238+
valueType = null;
239+
addMethod = null;
240+
if (!elementType.IsGenericType || elementType.GetGenericTypeDefinition() != typeof(KeyValuePair<,>))
241+
{
242+
return false;
243+
}
244+
var argumentTypes = elementType.GetGenericArguments();
245+
keyType = argumentTypes[0];
246+
valueType = argumentTypes[1];
247+
if (type.IsAbstract)
248+
{
249+
concreteType = typeof(Dictionary<,>).MakeGenericType(argumentTypes);
250+
if (!type.IsAssignableFrom(concreteType))
251+
{
252+
return false;
253+
}
254+
}
255+
else
256+
{
257+
concreteType = type;
258+
}
259+
if (concreteType.GetConstructor(Type.EmptyTypes) == null)
260+
{
261+
return false;
262+
}
263+
foreach (var method in concreteType.GetMethods())
264+
{
265+
if (!method.IsStatic && method.Name == "Add")
266+
{
267+
var parameters = method.GetParameters();
268+
if (parameters.Length == 2 && parameters[0].ParameterType == keyType && parameters[1].ParameterType == valueType)
269+
{
270+
addMethod = method;
271+
return true;
213272
}
214273
}
215-
216-
argumentExpression = Expression.Constant(value, type);
217-
return true;
218274
}
275+
return false;
219276
}
220277

221-
static bool IsContainer(Type type, [NotNullWhen(true)] out Type? elementType)
278+
static bool IsConstructableContainer(Type type, Type elementType, [NotNullWhen(true)] out Type? concreteType, [NotNullWhen(true)] out MethodInfo? addMethod)
222279
{
223-
elementType = null;
224-
foreach (var iface in type.GetInterfaces())
280+
addMethod = null;
281+
if (type.IsAbstract)
225282
{
226-
if (iface.IsGenericType)
283+
concreteType = typeof(List<>).MakeGenericType(elementType);
284+
if (!type.IsAssignableFrom(concreteType))
227285
{
228-
if (iface.GetGenericTypeDefinition() == typeof(IEnumerable<>))
286+
concreteType = typeof(HashSet<>).MakeGenericType(elementType);
287+
if (!type.IsAssignableFrom(concreteType))
229288
{
230-
elementType = iface.GetGenericArguments()[0];
289+
concreteType = null;
290+
return false;
291+
}
292+
}
293+
}
294+
else
295+
{
296+
concreteType = type;
297+
}
298+
if (concreteType.GetConstructor(Type.EmptyTypes) == null)
299+
{
300+
return false;
301+
}
302+
foreach (var method in concreteType.GetMethods())
303+
{
304+
if (!method.IsStatic && method.Name == "Add")
305+
{
306+
var parameters = method.GetParameters();
307+
if (parameters.Length == 1 && parameters[0].ParameterType == elementType)
308+
{
309+
addMethod = method;
231310
return true;
232311
}
233312
}
234313
}
235-
236314
return false;
237315
}
238316
}

0 commit comments

Comments
 (0)