Skip to content

Commit 406fa3d

Browse files
committed
Add support for custom types in arrays and custom collections
add new test platforms and unify msbuild conditional properties minor code cleanup PackageIcon instead of PackageIconUrl fix for simplified syntax on netcore3.0 fix SinkWithConfigurationBindingArgument test (wrong argument type)
1 parent 02d559c commit 406fa3d

18 files changed

+259
-71
lines changed

Build.ps1

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ echo "build: Build started"
22

33
Push-Location $PSScriptRoot
44

5-
if(Test-Path .\artifacts) {
5+
if (Test-Path .\artifacts) {
66
echo "build: Cleaning .\artifacts"
77
Remove-Item .\artifacts -Force -Recurse
88
}
@@ -21,7 +21,7 @@ foreach ($src in ls src/*) {
2121
echo "build: Packaging project in $src"
2222

2323
& dotnet pack -c Release -o ..\..\artifacts --version-suffix=$suffix --include-source
24-
if($LASTEXITCODE -ne 0) { exit 1 }
24+
if ($LASTEXITCODE -ne 0) { exit 1 }
2525

2626
Pop-Location
2727
}
@@ -32,7 +32,7 @@ foreach ($test in ls test/*.PerformanceTests) {
3232
echo "build: Building performance test project in $test"
3333

3434
& dotnet build -c Release
35-
if($LASTEXITCODE -ne 0) { exit 2 }
35+
if ($LASTEXITCODE -ne 0) { exit 2 }
3636

3737
Pop-Location
3838
}
@@ -43,7 +43,7 @@ foreach ($test in ls test/*.Tests) {
4343
echo "build: Testing project in $test"
4444

4545
& dotnet test -c Release
46-
if($LASTEXITCODE -ne 0) { exit 3 }
46+
if ($LASTEXITCODE -ne 0) { exit 3 }
4747

4848
Pop-Location
4949
}

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ If a Serilog package requires additional external configuration information (for
126126

127127
### Complex parameter value binding
128128

129-
When the configuration specifies a discrete value for a parameter (such as a string literal), the package will attempt to convert that value to the target method's declared CLR type of the parameter. Additional explicit handling is provided for parsing strings to `Uri` and `TimeSpan` objects and `enum` elements.
129+
When the configuration specifies a discrete value for a parameter (such as a string literal), the package will attempt to convert that value to the target method's declared CLR type of the parameter. Additional explicit handling is provided for parsing strings to `Uri`, `TimeSpan`, `enum` and arrays.
130130

131131
If the parameter value is not a discrete value, the package will use the configuration binding system provided by _Microsoft.Extensions.Options.ConfigurationExtensions_ to attempt to populate the parameter. Almost anything that can be bound by `IConfiguration.Get<T>` should work with this package. An example of this is the optional `List<Column>` parameter used to configure the .NET Standard version of the _Serilog.Sinks.MSSqlServer_ package.
132132

appveyor.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
version: '{build}'
22
skip_tags: true
3-
image: Visual Studio 2017
3+
image: Visual Studio 2019
44
configuration: Release
55
build_script:
66
- ps: ./Build.ps1

assets/icon.png

22.5 KB
Loading
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<Description>Microsoft.Extensions.Configuration (appsettings.json) support for Serilog.</Description>
55
<VersionPrefix>3.1.1</VersionPrefix>
6+
<LangVersion>latest</LangVersion>
67
<Authors>Serilog Contributors</Authors>
7-
<TargetFrameworks>netstandard2.0;net451;net461</TargetFrameworks>
8+
<TargetFrameworks>netstandard2.0;net451;net452;net461</TargetFrameworks>
89
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
910
<GenerateDocumentationFile>true</GenerateDocumentationFile>
1011
<AssemblyName>Serilog.Settings.Configuration</AssemblyName>
@@ -13,28 +14,29 @@
1314
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
1415
<PackageId>Serilog.Settings.Configuration</PackageId>
1516
<PackageTags>serilog;json</PackageTags>
16-
<PackageIconUrl>https://serilog.net/images/serilog-configuration-nuget.png</PackageIconUrl>
17+
<PackageIcon>icon.png</PackageIcon>
1718
<PackageProjectUrl>https://github.com/serilog/serilog-settings-configuration</PackageProjectUrl>
1819
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
1920
<RepositoryUrl>https://github.com/serilog/serilog-settings-configuration</RepositoryUrl>
2021
<RepositoryType>git</RepositoryType>
2122
<RootNamespace>Serilog</RootNamespace>
2223
</PropertyGroup>
2324

24-
<PropertyGroup Condition="('$(TargetFramework)' == 'net451') Or ('$(TargetFramework)' == 'net461')">
25+
<PropertyGroup Condition="'$(TargetFramework)' == 'net451' or '$(TargetFramework)' == 'net452' or '$(TargetFramework)' == 'net461'">
2526
<DefineConstants>$(DefineConstants);PRIVATE_BIN</DefineConstants>
2627
</PropertyGroup>
2728

2829
<ItemGroup>
2930
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="2.0.4" />
3031
<PackageReference Include="Serilog" Version="2.6.0" />
32+
<None Include="..\..\assets\icon.png" Pack="true" PackagePath=""/>
3133
</ItemGroup>
3234

33-
<ItemGroup Condition="'$(TargetFramework)' == 'net451'">
34-
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="1.1.2" />
35+
<ItemGroup Condition="'$(TargetFramework)' == 'net451' or '$(TargetFramework)' == 'net452'">
36+
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="1.1.1" />
3537
</ItemGroup>
3638

37-
<ItemGroup Condition="('$(TargetFramework)' == 'netstandard2.0') Or ('$(TargetFramework)' == 'net461')">
39+
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0' Or '$(TargetFramework)' == 'net461'">
3840
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.0.0" />
3941
</ItemGroup>
4042
</Project>

src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/AssemblyFinder.cs

+5-8
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,12 @@ public static AssemblyFinder Auto()
2727

2828
public static AssemblyFinder ForSource(ConfigurationAssemblySource configurationAssemblySource)
2929
{
30-
switch (configurationAssemblySource)
30+
return configurationAssemblySource switch
3131
{
32-
case ConfigurationAssemblySource.UseLoadedAssemblies:
33-
return Auto();
34-
case ConfigurationAssemblySource.AlwaysScanDllFiles:
35-
return new DllScanningAssemblyFinder();
36-
default:
37-
throw new ArgumentOutOfRangeException(nameof(configurationAssemblySource), configurationAssemblySource, null);
38-
}
32+
ConfigurationAssemblySource.UseLoadedAssemblies => Auto(),
33+
ConfigurationAssemblySource.AlwaysScanDllFiles => new DllScanningAssemblyFinder(),
34+
_ => throw new ArgumentOutOfRangeException(nameof(configurationAssemblySource), configurationAssemblySource, null),
35+
};
3936
}
4037

4138
public static AssemblyFinder ForDependencyContext(DependencyContext dependencyContext)

src/Serilog.Settings.Configuration/Settings/Configuration/Assemblies/DllScanningAssemblyFinder.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ where IsCaseInsensitiveMatch(assemblyFileName, nameToFind)
5050

5151
return query.ToList().AsReadOnly();
5252

53-
AssemblyName TryGetAssemblyNameFrom(string path)
53+
static AssemblyName TryGetAssemblyNameFrom(string path)
5454
{
5555
try
5656
{

src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs

+27-27
Original file line numberDiff line numberDiff line change
@@ -220,39 +220,14 @@ internal ILookup<string, Dictionary<string, IConfigurationArgumentValue>> GetMet
220220
select new
221221
{
222222
Name = argument.Key,
223-
Value = GetArgumentValue(argument)
223+
Value = GetArgumentValue(argument, _configurationAssemblies)
224224
}).ToDictionary(p => p.Name, p => p.Value)
225225
select new { Name = name, Args = callArgs }))
226226
.ToLookup(p => p.Name, p => p.Args);
227227

228228
return result;
229229

230-
IConfigurationArgumentValue GetArgumentValue(IConfigurationSection argumentSection)
231-
{
232-
IConfigurationArgumentValue argumentValue;
233-
234-
// Reject configurations where an element has both scalar and complex
235-
// values as a result of reading multiple configuration sources.
236-
if (argumentSection.Value != null && argumentSection.GetChildren().Any())
237-
throw new InvalidOperationException(
238-
$"The value for the argument '{argumentSection.Path}' is assigned different value " +
239-
"types in more than one configuration source. Ensure all configurations consistently " +
240-
"use either a scalar (int, string, boolean) or a complex (array, section, list, " +
241-
"POCO, etc.) type for this argument value.");
242-
243-
if (argumentSection.Value != null)
244-
{
245-
argumentValue = new StringArgumentValue(argumentSection.Value);
246-
}
247-
else
248-
{
249-
argumentValue = new ObjectArgumentValue(argumentSection, _configurationAssemblies);
250-
}
251-
252-
return argumentValue;
253-
}
254-
255-
string GetSectionName(IConfigurationSection s)
230+
static string GetSectionName(IConfigurationSection s)
256231
{
257232
var name = s.GetSection("Name");
258233
if (name.Value == null)
@@ -262,6 +237,31 @@ string GetSectionName(IConfigurationSection s)
262237
}
263238
}
264239

240+
internal static IConfigurationArgumentValue GetArgumentValue(IConfigurationSection argumentSection, IReadOnlyCollection<Assembly> configurationAssemblies)
241+
{
242+
IConfigurationArgumentValue argumentValue;
243+
244+
// Reject configurations where an element has both scalar and complex
245+
// values as a result of reading multiple configuration sources.
246+
if (argumentSection.Value != null && argumentSection.GetChildren().Any())
247+
throw new InvalidOperationException(
248+
$"The value for the argument '{argumentSection.Path}' is assigned different value " +
249+
"types in more than one configuration source. Ensure all configurations consistently " +
250+
"use either a scalar (int, string, boolean) or a complex (array, section, list, " +
251+
"POCO, etc.) type for this argument value.");
252+
253+
if (argumentSection.Value != null)
254+
{
255+
argumentValue = new StringArgumentValue(argumentSection.Value);
256+
}
257+
else
258+
{
259+
argumentValue = new ObjectArgumentValue(argumentSection, configurationAssemblies);
260+
}
261+
262+
return argumentValue;
263+
}
264+
265265
static IReadOnlyCollection<Assembly> LoadConfigurationAssemblies(IConfigurationSection section, AssemblyFinder assemblyFinder)
266266
{
267267
var assemblies = new Dictionary<string, Assembly>();

src/Serilog.Settings.Configuration/Settings/Configuration/ObjectArgumentValue.cs

+67-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
using Microsoft.Extensions.Configuration;
2+
using Serilog.Configuration;
23
using System;
34
using System.Collections.Generic;
5+
using System.Linq;
46
using System.Reflection;
57

6-
using Serilog.Configuration;
7-
88
namespace Serilog.Settings.Configuration
99
{
1010
class ObjectArgumentValue : IConfigurationArgumentValue
@@ -47,8 +47,72 @@ public object ConvertTo(Type toType, ResolutionContext resolutionContext)
4747
}
4848
}
4949

50-
// MS Config binding
50+
if (toType.IsArray)
51+
return CreateArray();
52+
53+
if (IsContainer(toType, out var elementType) && TryCreateContainer(out var result))
54+
return result;
55+
56+
// MS Config binding can work with a limited set of primitive types and collections
5157
return _section.Get(toType);
58+
59+
object CreateArray()
60+
{
61+
var elementType = toType.GetElementType();
62+
var configurationElements = _section.GetChildren().ToArray();
63+
var result = Array.CreateInstance(elementType, configurationElements.Length);
64+
for (int i = 0; i < configurationElements.Length; ++i)
65+
{
66+
var argumentValue = ConfigurationReader.GetArgumentValue(configurationElements[i], _configurationAssemblies);
67+
var value = argumentValue.ConvertTo(elementType, resolutionContext);
68+
result.SetValue(value, i);
69+
}
70+
71+
return result;
72+
}
73+
74+
bool TryCreateContainer(out object result)
75+
{
76+
result = null;
77+
78+
if (toType.GetConstructor(Type.EmptyTypes) == null)
79+
return false;
80+
81+
// https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/object-and-collection-initializers#collection-initializers
82+
var addMethod = toType.GetMethods().FirstOrDefault(m => !m.IsStatic && m.Name == "Add" && m.GetParameters()?.Length == 1 && m.GetParameters()[0].ParameterType == elementType);
83+
if (addMethod == null)
84+
return false;
85+
86+
var configurationElements = _section.GetChildren().ToArray();
87+
result = Activator.CreateInstance(toType);
88+
89+
for (int i = 0; i < configurationElements.Length; ++i)
90+
{
91+
var argumentValue = ConfigurationReader.GetArgumentValue(configurationElements[i], _configurationAssemblies);
92+
var value = argumentValue.ConvertTo(elementType, resolutionContext);
93+
addMethod.Invoke(result, new object[] { value });
94+
}
95+
96+
return true;
97+
}
98+
}
99+
100+
private static bool IsContainer(Type type, out Type elementType)
101+
{
102+
elementType = null;
103+
foreach (var iface in type.GetInterfaces())
104+
{
105+
if (iface.IsGenericType)
106+
{
107+
if (iface.GetGenericTypeDefinition() == typeof(IEnumerable<>))
108+
{
109+
elementType = iface.GetGenericArguments()[0];
110+
return true;
111+
}
112+
}
113+
}
114+
115+
return false;
52116
}
53117
}
54118
}

test/Serilog.Settings.Configuration.Tests/ConfigurationSettingsTests.cs

+40
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,46 @@ public void SinkWithStringArrayArgument()
615615
Assert.Equal(1, DummyRollingFileSink.Emitted.Count);
616616
}
617617

618+
[Fact]
619+
public void DestructureWithCollectionsOfTypeArgument()
620+
{
621+
var json = @"{
622+
""Serilog"": {
623+
""Using"": [ ""TestDummies"" ],
624+
""Destructure"": [{
625+
""Name"": ""DummyArrayOfType"",
626+
""Args"": {
627+
""list"": [
628+
""System.Byte"",
629+
""System.Int16""
630+
],
631+
""array"" : [
632+
""System.Int32"",
633+
""System.String""
634+
],
635+
""type"" : ""System.TimeSpan"",
636+
""custom"" : [
637+
""System.Int64""
638+
],
639+
""customString"" : [
640+
""System.UInt32""
641+
]
642+
}
643+
}]
644+
}
645+
}";
646+
647+
DummyPolicy.Current = null;
648+
649+
ConfigFromJson(json);
650+
651+
Assert.Equal(typeof(TimeSpan), DummyPolicy.Current.Type);
652+
Assert.Equal(new[] { typeof(int), typeof(string) }, DummyPolicy.Current.Array);
653+
Assert.Equal(new[] { typeof(byte), typeof(short) }, DummyPolicy.Current.List);
654+
Assert.Equal(typeof(long), DummyPolicy.Current.Custom.First);
655+
Assert.Equal("System.UInt32", DummyPolicy.Current.CustomStrings.First);
656+
}
657+
618658
[Fact]
619659
public void SinkWithIntArrayArgument()
620660
{

test/Serilog.Settings.Configuration.Tests/DllScanningAssemblyFinderTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public void ShouldProbePrivateBinPath()
6565
AppDomain.Unload(ad);
6666
}
6767

68-
void DoTestInner()
68+
static void DoTestInner()
6969
{
7070
var assemblyNames = new DllScanningAssemblyFinder().FindAssembliesContainingName("customSink");
7171
Assert.Equal(2, assemblyNames.Count);

test/Serilog.Settings.Configuration.Tests/Serilog.Settings.Configuration.Tests.csproj

+10-6
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@
1212
-->
1313

1414
<PropertyGroup>
15-
<TargetFrameworks>net452;netcoreapp2.0</TargetFrameworks>
15+
<LangVersion>latest</LangVersion>
16+
<TargetFrameworks>netcoreapp3.0;netcoreapp2.2;netcoreapp2.0;net452;net451</TargetFrameworks>
1617
<AssemblyName>Serilog.Settings.Configuration.Tests</AssemblyName>
1718
<AssemblyOriginatorKeyFile>../../assets/Serilog.snk</AssemblyOriginatorKeyFile>
1819
<SignAssembly>true</SignAssembly>
1920
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
2021
</PropertyGroup>
2122

22-
<PropertyGroup Condition="'$(TargetFramework)' == 'net452'">
23+
<PropertyGroup Condition="'$(TargetFramework)' == 'net451' or '$(TargetFramework)' == 'net452'">
2324
<DefineConstants>$(DefineConstants);PRIVATE_BIN</DefineConstants>
2425
</PropertyGroup>
2526

@@ -28,14 +29,17 @@
2829
<ProjectReference Include="..\TestDummies\TestDummies.csproj" />
2930
</ItemGroup>
3031

31-
<ItemGroup Condition="'$(TargetFramework)' == 'net452'">
32+
<ItemGroup Condition="'$(TargetFramework)' == 'net451' or '$(TargetFramework)' == 'net452'">
3233
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.1.1" />
33-
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="1.1.2" />
34+
3435
</ItemGroup>
3536

36-
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.0'">
37+
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.0' or '$(TargetFramework)' == 'netcoreapp2.2'">
3738
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.0.1" />
38-
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.0.1" />
39+
</ItemGroup>
40+
41+
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.0'">
42+
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.0.0" />
3943
</ItemGroup>
4044

4145
<ItemGroup>

test/Serilog.Settings.Configuration.Tests/Support/DelegatingSink.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ public class DelegatingSink : ILogEventSink
1010

1111
public DelegatingSink(Action<LogEvent> write)
1212
{
13-
if (write == null) throw new ArgumentNullException(nameof(write));
14-
_write = write;
13+
_write = write ?? throw new ArgumentNullException(nameof(write));
1514
}
1615

1716
public void Emit(LogEvent logEvent)

0 commit comments

Comments
 (0)