Skip to content

8.0.1 Release #422

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

Merged
merged 18 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
15abae9
Dev version bump [skip ci]
nblumhardt Nov 15, 2023
e3088a1
Disable deterministic source paths in the tests
0xced Nov 15, 2023
18db073
Revert "Use the regular dotnet test command invocation"
0xced Nov 15, 2023
75cbe35
Restore allowPrerelease=false and rollForward=latestFeature in global…
0xced Nov 15, 2023
1d0e7bf
Merge pull request #403 from 0xced/restore-parallel-tests
nblumhardt Nov 15, 2023
9cd610f
Improve test cases for ObjectArgumentValue
ChristofferGersen Jan 14, 2024
1e5f6cb
Reformat all JSON with raw literal strings for better readability
0xced Feb 19, 2024
67afedd
Merge pull request #409 from ChristofferGersen/object-argument-value-…
0xced Feb 19, 2024
a6fe8ec
More minor code cleanups
0xced Feb 19, 2024
d0a1ba0
Support collections as constructor arguments
ChristofferGersen Feb 24, 2024
64a0130
Place JSON strings in variable before use
ChristofferGersen Feb 24, 2024
5847ea7
Update README.md
erichiller Feb 24, 2024
19f2a4a
Merge pull request #412 from erichiller/patch-1
nblumhardt Feb 26, 2024
eca00c2
Clarification of the location of the 'Serilog' section in appsettings…
eleazarcelis Mar 20, 2024
8051c6b
Merge pull request #416 from eleazarcelis/clarification-serilog-secti…
nblumhardt Mar 21, 2024
fda80d9
Merge pull request #405 from ChristofferGersen/support-collection-arg…
nblumhardt May 13, 2024
39c07a9
Minor version bump - new features incoming
nblumhardt May 13, 2024
cfe1b52
Revert minor version bump - forgot about the versioning policy used h…
nblumhardt May 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -201,4 +201,3 @@ FakesAssemblies/
project.lock.json

artifacts/
/test/TestApp-*
7 changes: 5 additions & 2 deletions Build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ if($LASTEXITCODE -ne 0) { throw 'pack failed' }

Write-Output "build: Testing"

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

if($LASTEXITCODE -ne 0) { throw 'unit tests failed' }
if($LASTEXITCODE -ne 0) { throw 'unit tests failed' }
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

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.

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

```json
{
Expand Down Expand Up @@ -328,13 +328,13 @@ Destructuring means extracting pieces of information from an object and create p
{
"Name": "With",
"Args": {
"policy": "policy": "MySecondNamespace.SecondDestructuringPolicy, MySecondAssembly"
"policy": "MySecondNamespace.SecondDestructuringPolicy, MySecondAssembly"
}
},
{
"Name": "With",
"Args": {
"policy": "policy": "MyThirdNamespace.ThirdDestructuringPolicy, MyThirdAssembly"
"policy": "MyThirdNamespace.ThirdDestructuringPolicy, MyThirdAssembly"
}
},
],
Expand Down
4 changes: 3 additions & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{
"sdk": {
"version": "8.0.100"
"version": "8.0.100",
"allowPrerelease": false,
"rollForward": "latestFeature"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<Description>Microsoft.Extensions.Configuration (appsettings.json) support for Serilog.</Description>
<!-- This must match the major and minor components of the referenced Microsoft.Extensions.Logging package. -->
<VersionPrefix>8.0.0</VersionPrefix>
<VersionPrefix>8.0.1</VersionPrefix>
<Authors>Serilog Contributors</Authors>
<!-- These must match the Dependencies tab in https://www.nuget.org/packages/microsoft.settings.configuration at
the target version. -->
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reflection;

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

// Only try to call ctor when type is explicitly specified in _section
if (TryCallCtorExplicit(_section, resolutionContext, out var ctorResult))
return ctorResult;

if (IsContainer(toType, out var elementType) && TryCreateContainer(out var container))
return container;

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

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

if (toType.GetConstructor(Type.EmptyTypes) == null)
return false;

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

var configurationElements = _section.GetChildren().ToArray();
result = Activator.CreateInstance(toType) ?? throw new InvalidOperationException($"Activator.CreateInstance returned null for {toType}");
foreach (var section in _section.GetChildren())
{
var argumentValue = ConfigurationReader.GetArgumentValue(section, _configurationAssemblies);
var key = new StringArgumentValue(section.Key).ConvertTo(keyType, resolutionContext);
var value = argumentValue.ConvertTo(valueType, resolutionContext);
addMethod.Invoke(result, new[] { key, value });
}
return true;
}
else if (IsConstructableContainer(toType, elementType, out concreteType, out addMethod))
{
result = Activator.CreateInstance(concreteType) ?? throw new InvalidOperationException($"Activator.CreateInstance returned null for {concreteType}");

for (int i = 0; i < configurationElements.Length; ++i)
foreach (var section in _section.GetChildren())
{
var argumentValue = ConfigurationReader.GetArgumentValue(section, _configurationAssemblies);
var value = argumentValue.ConvertTo(elementType, resolutionContext);
addMethod.Invoke(result, new[] { value });
}
return true;
}
else
{
var argumentValue = ConfigurationReader.GetArgumentValue(configurationElements[i], _configurationAssemblies);
var value = argumentValue.ConvertTo(elementType, resolutionContext);
addMethod.Invoke(result, new[] { value });
return false;
}

return true;
}
}

internal static bool TryBuildCtorExpression(
IConfigurationSection section, Type parameterType, ResolutionContext resolutionContext, [NotNullWhen(true)] out NewExpression? ctorExpression)
bool TryCallCtorExplicit(
IConfigurationSection section, ResolutionContext resolutionContext, [NotNullWhen(true)] out object? value)
{
ctorExpression = null;

var typeDirective = section.GetValue<string>("$type") switch
{
not null => "$type",
Expand All @@ -116,21 +126,39 @@ internal static bool TryBuildCtorExpression(
var type = typeDirective switch
{
not null => Type.GetType(section.GetValue<string>(typeDirective)!, throwOnError: false),
null => parameterType,
null => null,
};

if (type is null or { IsAbstract: true })
{
value = null;
return false;
}
else
{
var suppliedArguments = section.GetChildren().Where(s => s.Key != typeDirective)
.ToDictionary(s => s.Key, StringComparer.OrdinalIgnoreCase);
return TryCallCtor(type, suppliedArguments, resolutionContext, out value);
}

var suppliedArguments = section.GetChildren().Where(s => s.Key != typeDirective)
}

bool TryCallCtorImplicit(
IConfigurationSection section, Type parameterType, ResolutionContext resolutionContext, out object? value)
{
var suppliedArguments = section.GetChildren()
.ToDictionary(s => s.Key, StringComparer.OrdinalIgnoreCase);
return TryCallCtor(parameterType, suppliedArguments, resolutionContext, out value);
}

bool TryCallCtor(Type type, Dictionary<string, IConfigurationSection> suppliedArguments, ResolutionContext resolutionContext, [NotNullWhen(true)] out object? value)
{
value = null;

if (suppliedArguments.Count == 0 &&
type.GetConstructor(Type.EmptyTypes) is ConstructorInfo parameterlessCtor)
{
ctorExpression = Expression.New(parameterlessCtor);
value = parameterlessCtor.Invoke([]);
return true;
}

Expand Down Expand Up @@ -163,76 +191,126 @@ where gr.All(z => z.argumentBindResult.success)
return false;
}

var ctorArguments = new List<Expression>();
foreach (var argumentValue in ctor.ArgumentValues)
var ctorArguments = new object?[ctor.ArgumentValues.Count];
for (var i = 0; i < ctor.ArgumentValues.Count; i++)
{
if (TryBindToCtorArgument(argumentValue.Value, argumentValue.Type, resolutionContext, out var argumentExpression))
var argument = ctor.ArgumentValues[i];
var valueValue = argument.Value;
if (valueValue is IConfigurationSection s)
{
ctorArguments.Add(argumentExpression);
}
else
{
return false;
var argumentValue = ConfigurationReader.GetArgumentValue(s, _configurationAssemblies);
valueValue = argumentValue.ConvertTo(argument.Type, resolutionContext);
}
ctorArguments[i] = valueValue;
}

ctorExpression = Expression.New(ctor.ConstructorInfo, ctorArguments);
value = ctor.ConstructorInfo.Invoke(ctorArguments);
return true;
}

static bool TryBindToCtorArgument(object value, Type type, ResolutionContext resolutionContext, [NotNullWhen(true)] out Expression? argumentExpression)
static bool IsContainer(Type type, [NotNullWhen(true)] out Type? elementType)
{
elementType = null;
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
argumentExpression = null;

if (value is IConfigurationSection s)
elementType = type.GetGenericArguments()[0];
return true;
}
foreach (var iface in type.GetInterfaces())
{
if (iface.IsGenericType)
{
if (s.Value is string argValue)
if (iface.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
var stringArgumentValue = new StringArgumentValue(argValue);
try
{
argumentExpression = Expression.Constant(
stringArgumentValue.ConvertTo(type, resolutionContext),
type);

return true;
}
catch (Exception)
{
return false;
}
elementType = iface.GetGenericArguments()[0];
return true;
}
else if (s.GetChildren().Any())
{
if (TryBuildCtorExpression(s, type, resolutionContext, out var ctorExpression))
{
argumentExpression = ctorExpression;
return true;
}
}
}

return false;
return false;
}

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)
{
concreteType = null;
keyType = null;
valueType = null;
addMethod = null;
if (!elementType.IsGenericType || elementType.GetGenericTypeDefinition() != typeof(KeyValuePair<,>))
{
return false;
}
var argumentTypes = elementType.GetGenericArguments();
keyType = argumentTypes[0];
valueType = argumentTypes[1];
if (type.IsAbstract)
{
concreteType = typeof(Dictionary<,>).MakeGenericType(argumentTypes);
if (!type.IsAssignableFrom(concreteType))
{
return false;
}
}
else
{
concreteType = type;
}
if (concreteType.GetConstructor(Type.EmptyTypes) == null)
{
return false;
}
foreach (var method in concreteType.GetMethods())
{
if (!method.IsStatic && method.Name == "Add")
{
var parameters = method.GetParameters();
if (parameters.Length == 2 && parameters[0].ParameterType == keyType && parameters[1].ParameterType == valueType)
{
addMethod = method;
return true;
}
}

argumentExpression = Expression.Constant(value, type);
return true;
}
return false;
}

static bool IsContainer(Type type, [NotNullWhen(true)] out Type? elementType)
static bool IsConstructableContainer(Type type, Type elementType, [NotNullWhen(true)] out Type? concreteType, [NotNullWhen(true)] out MethodInfo? addMethod)
{
elementType = null;
foreach (var iface in type.GetInterfaces())
addMethod = null;
if (type.IsAbstract)
{
if (iface.IsGenericType)
concreteType = typeof(List<>).MakeGenericType(elementType);
if (!type.IsAssignableFrom(concreteType))
{
if (iface.GetGenericTypeDefinition() == typeof(IEnumerable<>))
concreteType = typeof(HashSet<>).MakeGenericType(elementType);
if (!type.IsAssignableFrom(concreteType))
{
elementType = iface.GetGenericArguments()[0];
concreteType = null;
return false;
}
}
}
else
{
concreteType = type;
}
if (concreteType.GetConstructor(Type.EmptyTypes) == null)
{
return false;
}
foreach (var method in concreteType.GetMethods())
{
if (!method.IsStatic && method.Name == "Add")
{
var parameters = method.GetParameters();
if (parameters.Length == 1 && parameters[0].ParameterType == elementType)
{
addMethod = method;
return true;
}
}
}

return false;
}
}
Loading