diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..acd3bc6
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,41 @@
+# If this file is renamed, the incrementing run attempt number will be reset.
+
+name: CI
+
+on:
+ push:
+ branches: [ "dev", "main" ]
+ pull_request:
+ branches: [ "dev", "main" ]
+
+env:
+ CI_BUILD_NUMBER_BASE: ${{ github.run_number }}
+ CI_TARGET_BRANCH: ${{ github.head_ref || github.ref_name }}
+
+jobs:
+ build:
+
+ # The build must run on Windows so that .NET Framework targets can be built and tested.
+ runs-on: windows-latest
+
+ permissions:
+ contents: write
+
+ steps:
+ - uses: actions/checkout@v4
+ - name: Setup
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 9.0.x
+ - name: Compute build number
+ shell: bash
+ run: |
+ echo "CI_BUILD_NUMBER=$(($CI_BUILD_NUMBER_BASE+2300))" >> $GITHUB_ENV
+ - name: Build and Publish
+ env:
+ DOTNET_CLI_TELEMETRY_OPTOUT: true
+ NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ shell: pwsh
+ run: |
+ ./Build.ps1
diff --git a/.gitignore b/.gitignore
index 80de031..318ac62 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,5 @@ bin/
obj/
test/
artifacts/
+
+.DS_Store
diff --git a/Build.ps1 b/Build.ps1
index 1513382..e798284 100644
--- a/Build.ps1
+++ b/Build.ps1
@@ -1,38 +1,79 @@
+Write-Output "build: Tool versions follow"
+
+dotnet --version
+dotnet --list-sdks
+
Write-Output "build: Build started"
Push-Location $PSScriptRoot
+try {
+ if(Test-Path .\artifacts) {
+ Write-Output "build: Cleaning ./artifacts"
+ Remove-Item ./artifacts -Force -Recurse
+ }
-if(Test-Path .\artifacts) {
- Write-Output "build: Cleaning .\artifacts"
- Remove-Item .\artifacts -Force -Recurse
-}
+ & dotnet restore --no-cache
-$branch = @{ $true = $env:APPVEYOR_REPO_BRANCH; $false = $(git symbolic-ref --short -q HEAD) }[$env:APPVEYOR_REPO_BRANCH -ne $NULL];
-$revision = @{ $true = "{0:00000}" -f [convert]::ToInt32("0" + $env:APPVEYOR_BUILD_NUMBER, 10); $false = "local" }[$env:APPVEYOR_BUILD_NUMBER -ne $NULL];
-$suffix = @{ $true = ""; $false = "$($branch.Substring(0, [math]::Min(10,$branch.Length)))-$revision"}[$branch -eq "main" -and $revision -ne "local"]
-$commitHash = $(git rev-parse --short HEAD)
-$buildSuffix = @{ $true = "$($suffix)-$($commitHash)"; $false = "$($branch)-$($commitHash)" }[$suffix -ne ""]
+ $dbp = [Xml] (Get-Content .\Directory.Version.props)
+ $versionPrefix = $dbp.Project.PropertyGroup.VersionPrefix
-Write-Output "build: Package version suffix is $suffix"
-Write-Output "build: Build version suffix is $buildSuffix"
+ Write-Output "build: Package version prefix is $versionPrefix"
-& dotnet build --configuration Release --version-suffix=$buildSuffix /p:ContinuousIntegrationBuild=true
+ $branch = @{ $true = $env:CI_TARGET_BRANCH; $false = $(git symbolic-ref --short -q HEAD) }[$NULL -ne $env:CI_TARGET_BRANCH];
+ $revision = @{ $true = "{0:00000}" -f [convert]::ToInt32("0" + $env:CI_BUILD_NUMBER, 10); $false = "local" }[$NULL -ne $env:CI_BUILD_NUMBER];
+ $suffix = @{ $true = ""; $false = "$($branch.Substring(0, [math]::Min(10,$branch.Length)) -replace '([^a-zA-Z0-9\-]*)', '')-$revision"}[$branch -eq "main" -and $revision -ne "local"]
+ $commitHash = $(git rev-parse --short HEAD)
+ $buildSuffix = @{ $true = "$($suffix)-$($commitHash)"; $false = "$($branch)-$($commitHash)" }[$suffix -ne ""]
-if($LASTEXITCODE -ne 0) { throw 'build failed' }
+ Write-Output "build: Package version suffix is $suffix"
+ Write-Output "build: Build version suffix is $buildSuffix"
-if($suffix) {
- & dotnet pack src\Serilog.Settings.Configuration --configuration Release --no-build --no-restore -o artifacts --version-suffix=$suffix
-} else {
- & dotnet pack src\Serilog.Settings.Configuration --configuration Release --no-build --no-restore -o artifacts
-}
+ & dotnet build -c Release --version-suffix=$buildSuffix /p:ContinuousIntegrationBuild=true
+ if($LASTEXITCODE -ne 0) { throw "Build failed" }
+
+ foreach ($src in Get-ChildItem src/*) {
+ Push-Location $src
+
+ Write-Output "build: Packaging project in $src"
+
+ if ($suffix) {
+ & dotnet pack -c Release --no-build --no-restore -o ../../artifacts --version-suffix=$suffix
+ } else {
+ & dotnet pack -c Release --no-build --no-restore -o ../../artifacts
+ }
+ if($LASTEXITCODE -ne 0) { throw "Packaging failed" }
-if($LASTEXITCODE -ne 0) { throw 'pack failed' }
+ Pop-Location
+ }
-Write-Output "build: Testing"
+ foreach ($test in Get-ChildItem test/*.Tests) {
+ Push-Location $test
-# 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
+ Write-Output "build: Testing project in $test"
-if($LASTEXITCODE -ne 0) { throw 'unit tests failed' }
\ No newline at end of file
+ & dotnet test -c Release --no-build --no-restore
+ if($LASTEXITCODE -ne 0) { throw "Testing failed" }
+
+ Pop-Location
+ }
+
+ if ($env:NUGET_API_KEY) {
+ # GitHub Actions will only supply this to branch builds and not PRs. We publish
+ # builds from any branch this action targets (i.e. main and dev).
+
+ Write-Output "build: Publishing NuGet packages"
+
+ foreach ($nupkg in Get-ChildItem artifacts/*.nupkg) {
+ & dotnet nuget push -k $env:NUGET_API_KEY -s https://api.nuget.org/v3/index.json "$nupkg"
+ if($LASTEXITCODE -ne 0) { throw "Publishing failed" }
+ }
+
+ if (!($suffix)) {
+ Write-Output "build: Creating release for version $versionPrefix"
+
+ iex "gh release create v$versionPrefix --title v$versionPrefix --generate-notes $(get-item ./artifacts/*.nupkg) $(get-item ./artifacts/*.snupkg)"
+ }
+ }
+} finally {
+ Pop-Location
+}
diff --git a/Directory.Build.props b/Directory.Build.props
index 736814b..c114992 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,11 +1,25 @@
+
+
latest
True
- true
+
+ true
$(MSBuildThisFileDirectory)assets/Serilog.snk
- enable
false
enable
+ enable
+ true
+ true
+ true
+ true
+ snupkg
+
+
+
+
+
diff --git a/Directory.Version.props b/Directory.Version.props
new file mode 100644
index 0000000..8c3cc16
--- /dev/null
+++ b/Directory.Version.props
@@ -0,0 +1,6 @@
+
+
+
+ 9.0.0
+
+
diff --git a/README.md b/README.md
index 8fb3e07..17d3910 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
-# Serilog.Settings.Configuration [![Build status](https://ci.appveyor.com/api/projects/status/r2bgfimd9ocr61px/branch/master?svg=true)](https://ci.appveyor.com/project/serilog/serilog-settings-configuration/branch/master) [![NuGet Version](http://img.shields.io/nuget/v/Serilog.Settings.Configuration.svg?style=flat)](https://www.nuget.org/packages/Serilog.Settings.Configuration/)
+# Serilog.Settings.Configuration [![Build status](https://github.com/serilog/serilog-settings-configuration/actions/workflows/ci.yml/badge.svg?branch=dev)](https://github.com/serilog/serilog-settings-configuration/actions) [![NuGet Version](http://img.shields.io/nuget/v/Serilog.Settings.Configuration.svg?style=flat)](https://www.nuget.org/packages/Serilog.Settings.Configuration/)
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
{
@@ -349,7 +349,7 @@ public record MyDto(int Id, int Name);
public class FirstDestructuringPolicy : IDestructuringPolicy
{
- public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory,
+ public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory,
[NotNullWhen(true)] out LogEventPropertyValue? result)
{
if (value is not MyDto dto)
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644
index eb9d04e..0000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,52 +0,0 @@
-version: '{build}'
-
-skip_tags: true
-
-image:
- - Visual Studio 2022
- - Ubuntu
- - macOS
-
-build_script:
-- pwsh: |
- if ($isWindows) {
- Invoke-WebRequest "https://dot.net/v1/dotnet-install.ps1" -OutFile "./dotnet-install.ps1"
- ./dotnet-install.ps1 -JSonFile global.json -Architecture x64 -InstallDir 'C:\Program Files\dotnet'
- ./Build.ps1
- }
- if ($isLinux) {
- Invoke-WebRequest "https://dot.net/v1/dotnet-install.sh" -OutFile "./dotnet-install.sh"
- sudo chmod u+x dotnet-install.sh
- sudo ./dotnet-install.sh --jsonfile global.json --architecture x64 --install-dir '/usr/share/dotnet'
- ./Build.ps1
- }
- if ($isMacOS) {
- Invoke-WebRequest "https://dot.net/v1/dotnet-install.sh" -OutFile "./dotnet-install.sh"
- sudo chmod u+x dotnet-install.sh
- sudo ./dotnet-install.sh --jsonfile global.json --architecture x64 --install-dir '/usr/local/share/dotnet'
- ./Build.ps1
- }
-
-test: off
-
-artifacts:
-- path: artifacts/Serilog.*.nupkg
-- path: artifacts/Serilog.*.snupkg
-
-deploy:
-- provider: NuGet
- api_key:
- secure: JIfNMRv3l/2dmM/i//mpeEKqgxyEcnGr8XFlEoSDgp2JDVmRP8nUxc4gYznBvXQV
- on:
- branch: /^(main|dev)$/
- OS: Windows_NT
-- provider: GitHub
- auth_token:
- secure: p4LpVhBKxGS5WqucHxFQ5c7C8cP74kbNB0Z8k9Oxx/PMaDQ1+ibmoexNqVU5ZlmX
- artifacts:
- /Serilog.*\.nupkg/
- /Serilog.*\.snupkg/
- tag: v$(appveyor_build_version)
- on:
- branch: main
- OS: Windows_NT
diff --git a/global.json b/global.json
index b7e3357..db8627a 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "8.0.100",
+ "version": "9.0.100",
"allowPrerelease": false,
"rollForward": "latestFeature"
}
diff --git a/sample/Sample/Sample.csproj b/sample/Sample/Sample.csproj
index 4e8277a..1c8a2a7 100644
--- a/sample/Sample/Sample.csproj
+++ b/sample/Sample/Sample.csproj
@@ -1,8 +1,9 @@
- net462;net6.0;net7.0;net8.0
+ net462;net8.0;net9.0
Exe
+ false
@@ -14,15 +15,15 @@
-
-
-
-
-
+
+
+
+
+
-
+
-
+
diff --git a/serilog-settings-configuration.sln b/serilog-settings-configuration.sln
index f0209fd..4e06158 100644
--- a/serilog-settings-configuration.sln
+++ b/serilog-settings-configuration.sln
@@ -9,7 +9,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{62D0B9
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.gitignore = .gitignore
- appveyor.yml = appveyor.yml
Build.ps1 = Build.ps1
CHANGES.md = CHANGES.md
Directory.Build.props = Directory.Build.props
@@ -18,6 +17,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{62D0B9
README.md = README.md
serilog-settings-configuration.sln.DotSettings = serilog-settings-configuration.sln.DotSettings
global.json = global.json
+ Directory.Version.props = Directory.Version.props
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{D551DCB0-7771-4D01-BEBD-F7B57D1CF0E3}"
@@ -34,6 +34,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestDummies", "test\TestDum
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestApp", "test\TestApp\TestApp.csproj", "{1B6E08F3-16C9-4912-BEEE-57DB78C92A12}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{21A409AD-2C11-41A8-88C8-360062EA8E48}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{391B84C5-A7BA-4BE1-95A0-E459FA61D971}"
+ ProjectSection(SolutionItems) = preProject
+ .github\workflows\ci.yml = .github\workflows\ci.yml
+ EndProjectSection
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -68,6 +75,7 @@ Global
{A00E5E32-54F9-401A-BBA1-2F6FCB6366CD} = {D24872B9-57F3-42A7-BC8D-F9DA222FCE1B}
{B7CF5068-DD19-4868-A268-5280BDE90361} = {D551DCB0-7771-4D01-BEBD-F7B57D1CF0E3}
{1B6E08F3-16C9-4912-BEEE-57DB78C92A12} = {D551DCB0-7771-4D01-BEBD-F7B57D1CF0E3}
+ {391B84C5-A7BA-4BE1-95A0-E459FA61D971} = {21A409AD-2C11-41A8-88C8-360062EA8E48}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {485F8843-42D7-4267-B5FB-20FE9181DEE9}
diff --git a/serilog-settings-configuration.sln.DotSettings b/serilog-settings-configuration.sln.DotSettings
index c946077..d47fb00 100644
--- a/serilog-settings-configuration.sln.DotSettings
+++ b/serilog-settings-configuration.sln.DotSettings
@@ -514,6 +514,21 @@ II.2.12 <HandlesEvent />
<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
<Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
+ <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy>
+ <Policy><Descriptor Staticness="Any" AccessRightKinds="Private" Description="Constant fields (private)"><ElementKinds><Kind Name="CONSTANT_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy>
+ <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Type parameters"><ElementKinds><Kind Name="TYPE_PARAMETER" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /></Policy>
+ <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy>
+ <Policy><Descriptor Staticness="Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Instance fields (not private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy>
+ <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Local variables"><ElementKinds><Kind Name="LOCAL_VARIABLE" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy>
+ <Policy><Descriptor Staticness="Any" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Constant fields (not private)"><ElementKinds><Kind Name="CONSTANT_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy>
+ <Policy><Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static fields (not private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy>
+ <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Parameters"><ElementKinds><Kind Name="PARAMETER" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy>
+ <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Enum members"><ElementKinds><Kind Name="ENUM_MEMBER" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy>
+ <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"><ElementKinds><Kind Name="NAMESPACE" /><Kind Name="CLASS" /><Kind Name="STRUCT" /><Kind Name="ENUM" /><Kind Name="DELEGATE" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy>
+ <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Local constants"><ElementKinds><Kind Name="LOCAL_CONSTANT" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy>
+ <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Interfaces"><ElementKinds><Kind Name="INTERFACE" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /></Policy>
+ <Policy><Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static readonly fields (not private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy>
+ <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy>
<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
@@ -550,6 +565,7 @@ II.2.12 <HandlesEvent />
True
True
True
+ True
True
True
True
diff --git a/src/Serilog.Settings.Configuration/Serilog.Settings.Configuration.csproj b/src/Serilog.Settings.Configuration/Serilog.Settings.Configuration.csproj
index 826faf7..0b8d72a 100644
--- a/src/Serilog.Settings.Configuration/Serilog.Settings.Configuration.csproj
+++ b/src/Serilog.Settings.Configuration/Serilog.Settings.Configuration.csproj
@@ -2,36 +2,34 @@
Microsoft.Extensions.Configuration (appsettings.json) support for Serilog.
-
- 8.0.1
Serilog Contributors
-
- net462;netstandard2.0;net6.0;net7.0;net8.0
+ net462;netstandard2.0;net8.0;net9.0
true
- serilog;json
+ serilog;json;appsettings
icon.png
README.md
Apache-2.0
https://github.com/serilog/serilog-settings-configuration
$(PackageProjectUrl)/releases
Serilog
- true
- true
- true
- snupkg
-
-
+
+
+
+
+
+
-
-
+
+
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationArgumentValue.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationArgumentValue.cs
new file mode 100644
index 0000000..239853e
--- /dev/null
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationArgumentValue.cs
@@ -0,0 +1,34 @@
+using System.Reflection;
+using Microsoft.Extensions.Configuration;
+
+namespace Serilog.Settings.Configuration;
+
+abstract class ConfigurationArgumentValue
+{
+ public abstract object? ConvertTo(Type toType, ResolutionContext resolutionContext);
+
+ public static ConfigurationArgumentValue FromSection(IConfigurationSection argumentSection, IReadOnlyCollection configurationAssemblies)
+ {
+ ConfigurationArgumentValue argumentValue;
+
+ // Reject configurations where an element has both scalar and complex
+ // values as a result of reading multiple configuration sources.
+ if (argumentSection.Value != null && argumentSection.GetChildren().Any())
+ throw new InvalidOperationException(
+ $"The value for the argument '{argumentSection.Path}' is assigned different value " +
+ "types in more than one configuration source. Ensure all configurations consistently " +
+ "use either a scalar (int, string, boolean) or a complex (array, section, list, " +
+ "POCO, etc.) type for this argument value.");
+
+ if (argumentSection.Value != null)
+ {
+ argumentValue = new StringArgumentValue(argumentSection.Value);
+ }
+ else
+ {
+ argumentValue = new ObjectArgumentValue(argumentSection, configurationAssemblies);
+ }
+
+ return argumentValue;
+ }
+}
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs
index e1f0c84..3b86170 100644
--- a/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs
@@ -200,12 +200,12 @@ void ApplyMinimumLevelConfiguration(IConfigurationSection directive, Action> GetMethodCalls(IConfiguration directive)
+ internal ILookup> GetMethodCalls(IConfiguration directive)
{
var children = directive.GetChildren().ToList();
var result =
(from child in children
where child.Value != null // Plain string
- select new { Name = child.Value, Args = new Dictionary() })
+ select new { Name = child.Value, Args = new Dictionary() })
.Concat(
(from child in children
where child.Value == null
@@ -319,7 +319,7 @@ internal ILookup> GetMet
select new
{
Name = argument.Key,
- Value = GetArgumentValue(argument, _configurationAssemblies)
+ Value = ConfigurationArgumentValue.FromSection(argument, _configurationAssemblies)
}).ToDictionary(p => p.Name, p => p.Value)
select new { Name = name, Args = callArgs }))
.ToLookup(p => p.Name, p => p.Args);
@@ -336,31 +336,6 @@ static string GetSectionName(IConfigurationSection s)
}
}
- internal static IConfigurationArgumentValue GetArgumentValue(IConfigurationSection argumentSection, IReadOnlyCollection configurationAssemblies)
- {
- IConfigurationArgumentValue argumentValue;
-
- // Reject configurations where an element has both scalar and complex
- // values as a result of reading multiple configuration sources.
- if (argumentSection.Value != null && argumentSection.GetChildren().Any())
- throw new InvalidOperationException(
- $"The value for the argument '{argumentSection.Path}' is assigned different value " +
- "types in more than one configuration source. Ensure all configurations consistently " +
- "use either a scalar (int, string, boolean) or a complex (array, section, list, " +
- "POCO, etc.) type for this argument value.");
-
- if (argumentSection.Value != null)
- {
- argumentValue = new StringArgumentValue(argumentSection.Value);
- }
- else
- {
- argumentValue = new ObjectArgumentValue(argumentSection, configurationAssemblies);
- }
-
- return argumentValue;
- }
-
static IReadOnlyCollection LoadConfigurationAssemblies(IConfiguration section, AssemblyFinder assemblyFinder)
{
var serilogAssembly = typeof(ILogger).Assembly;
@@ -404,7 +379,7 @@ This is most likely because the application is published as single-file.
return assemblies;
}
- void CallConfigurationMethods(ILookup> methods, IReadOnlyCollection configurationMethods, object receiver)
+ void CallConfigurationMethods(ILookup> methods, IReadOnlyCollection configurationMethods, object receiver)
{
foreach (var method in methods.SelectMany(g => g.Select(x => new { g.Key, Value = x })))
{
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/IConfigurationArgumentValue.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/IConfigurationArgumentValue.cs
deleted file mode 100644
index 8a1e86f..0000000
--- a/src/Serilog.Settings.Configuration/Settings/Configuration/IConfigurationArgumentValue.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Serilog.Settings.Configuration;
-
-interface IConfigurationArgumentValue
-{
- object? ConvertTo(Type toType, ResolutionContext resolutionContext);
-}
diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/ObjectArgumentValue.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/ObjectArgumentValue.cs
index 8ab46a7..85783b4 100644
--- a/src/Serilog.Settings.Configuration/Settings/Configuration/ObjectArgumentValue.cs
+++ b/src/Serilog.Settings.Configuration/Settings/Configuration/ObjectArgumentValue.cs
@@ -1,14 +1,14 @@
using System.Diagnostics.CodeAnalysis;
-using System.Linq.Expressions;
using System.Reflection;
using Microsoft.Extensions.Configuration;
using Serilog.Configuration;
+using Serilog.Debugging;
namespace Serilog.Settings.Configuration;
-class ObjectArgumentValue : IConfigurationArgumentValue
+class ObjectArgumentValue : ConfigurationArgumentValue
{
readonly IConfigurationSection _section;
readonly IReadOnlyCollection _configurationAssemblies;
@@ -21,7 +21,7 @@ public ObjectArgumentValue(IConfigurationSection section, IReadOnlyCollection logger/sink config parameter?
var typeInfo = toType.GetTypeInfo();
if (typeInfo.IsGenericType &&
- typeInfo.GetGenericTypeDefinition() is Type genericType && genericType == typeof(Action<>))
+ typeInfo.GetGenericTypeDefinition() is {} genericType && genericType == typeof(Action<>))
{
var configType = typeInfo.GenericTypeArguments[0];
IConfigurationReader configReader = new ConfigurationReader(_section, _configurationAssemblies, resolutionContext);
@@ -39,20 +39,23 @@ public ObjectArgumentValue(IConfigurationSection section, IReadOnlyCollection new Action(configReader.Configure),
_ when configType == typeof(LoggerSinkConfiguration) => new Action(configReader.ApplySinks),
_ when configType == typeof(LoggerEnrichmentConfiguration) => new Action(configReader.ApplyEnrichment),
- _ => throw new ArgumentException($"Configuration resolution for Action<{configType.Name}> parameter type at the path {_section.Path} is not implemented.")
+ _ => throw new ArgumentException($"Configuration resolution for `Action<{configType.Name}>` parameter type at the path `{_section.Path}` is not implemented.")
};
}
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>(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);
@@ -62,9 +65,9 @@ object CreateArray()
var arrayElementType = toType.GetElementType()!;
var configurationElements = _section.GetChildren().ToArray();
var array = Array.CreateInstance(arrayElementType, configurationElements.Length);
- for (int i = 0; i < configurationElements.Length; ++i)
+ for (var i = 0; i < configurationElements.Length; ++i)
{
- var argumentValue = ConfigurationReader.GetArgumentValue(configurationElements[i], _configurationAssemblies);
+ var argumentValue = FromSection(configurationElements[i], _configurationAssemblies);
var value = argumentValue.ConvertTo(arrayElementType, resolutionContext);
array.SetValue(value, i);
}
@@ -76,33 +79,40 @@ 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 = FromSection(section, _configurationAssemblies);
+ var key = new StringArgumentValue(section.Key).ConvertTo(keyType, resolutionContext);
+ var value = argumentValue.ConvertTo(valueType, resolutionContext);
+ addMethod.Invoke(result, [key, value]);
+ }
+ return true;
+ }
- for (int i = 0; i < configurationElements.Length; ++i)
+ if (IsConstructableContainer(toType, elementType, out concreteType, out addMethod))
{
- var argumentValue = ConfigurationReader.GetArgumentValue(configurationElements[i], _configurationAssemblies);
- var value = argumentValue.ConvertTo(elementType, resolutionContext);
- addMethod.Invoke(result, [value]);
+ result = Activator.CreateInstance(concreteType) ?? throw new InvalidOperationException($"Activator.CreateInstance returned null for {concreteType}");
+
+ foreach (var section in _section.GetChildren())
+ {
+ var argumentValue = FromSection(section, _configurationAssemblies);
+ var value = argumentValue.ConvertTo(elementType, resolutionContext);
+ addMethod.Invoke(result, [value]);
+ }
+ return true;
}
- return true;
+ return false;
}
}
- 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("$type") switch
{
not null => "$type",
@@ -116,111 +126,128 @@ internal static bool TryBuildCtorExpression(
var type = typeDirective switch
{
not null => Type.GetType(section.GetValue(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);
+ }
- if (suppliedArguments.Count == 0 &&
- type.GetConstructor(Type.EmptyTypes) is ConstructorInfo parameterlessCtor)
- {
- ctorExpression = Expression.New(parameterlessCtor);
- return true;
- }
+ bool TryCallCtor(Type type, Dictionary suppliedArguments, ResolutionContext resolutionContext, [NotNullWhen(true)] out object? value)
+ {
+ var binding = type.GetConstructors()
+ .Select(ci =>
+ {
+ var args = new List