Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
956b5e6
Enable IsAotCompatible
campersau Mar 23, 2026
5911a04
reduce reflection and simplify querystring converter instance factory…
campersau Mar 31, 2026
3446891
only generate json serialize attribute if model gets json serialized
campersau Apr 1, 2026
f1f6671
add all array types to DockerExtendedJsonSerializerContext
campersau Apr 1, 2026
f1aff52
move System.Text.Json.Serialization.Metadata to csproj Using
campersau Apr 1, 2026
b9fa870
remove ConcurrentDictionary and use static field instead for query st…
campersau Apr 1, 2026
dc0a331
bump xunit to 4.0.0-pre.81
campersau Apr 12, 2026
e4cc73f
chore: apply minor clean ups
HofmeisterAn Apr 17, 2026
133b799
TestFixture remove IDisposable
campersau Apr 17, 2026
bb7320f
JsonSerializable for inline and anonymous types
campersau Apr 17, 2026
858393a
Add JsonTypeInfoCache
campersau Apr 18, 2026
0c00296
Use an analyzer for checking DockerExtendedJsonSerializerContext Attr…
campersau Apr 19, 2026
2b87054
Add missing json serializer types: string, Secret[], TaskResponse[]
campersau Apr 19, 2026
a5aac93
Analyzer project cleanup
campersau Apr 19, 2026
b7bb2f9
Handle MakeRequestAsync / MonitorStreamForMessagesAsync / JsonSeriali…
campersau Apr 19, 2026
9301a81
Merge branch 'main' into aot
campersau Apr 20, 2026
dd17765
Refactor QueryStringParameterAttribute to avoid a JsonSerializer.Seri…
campersau Apr 20, 2026
3f275c0
chore: improve query string parameter attribute implementations
HofmeisterAn Apr 20, 2026
497aac5
chore: remove nullable warning
HofmeisterAn Apr 20, 2026
bbc29e8
chore: clean up analyzer
HofmeisterAn Apr 20, 2026
1f7f923
chore: remove BOM
HofmeisterAn Apr 20, 2026
c1672a5
chore: group order serializer context
HofmeisterAn Apr 20, 2026
dd5f744
feat: analyze QueryStringMapParameterAttribute
HofmeisterAn Apr 20, 2026
1c23a04
fix: add multi TFM
HofmeisterAn Apr 20, 2026
da83de1
fix: use QueryStringListParameter in specgen
HofmeisterAn Apr 20, 2026
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
10 changes: 3 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,9 @@ jobs:
exit 1

- name: Test (${{ matrix.docker.name }})
run: >-
dotnet test
--configuration Release
--framework ${{ matrix.dotnet.tfm }}
--no-restore
--no-build
--logger console
run: |
./test/Docker.DotNet.Tests/bin/Release/${{ matrix.dotnet.tfm }}/linux-x64/publish/Docker.DotNet.Tests
./test/Docker.DotNet.TestsV2/bin/Release/${{ matrix.dotnet.tfm }}/linux-x64/publish/Docker.DotNet.TestsV2
env:
DOCKER_HOST: ${{ matrix.docker.docker_host }}
DOCKER_TLS_VERIFY: ${{ matrix.docker.tls_verify }}
Expand Down
5 changes: 2 additions & 3 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@
<PackageVersion Include="System.Text.Json" Version="8.0.5" />
<PackageVersion Include="BouncyCastle.Cryptography" Version="2.5.1" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.3" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
<PackageVersion Include="xunit" Version="2.9.2" />
<PackageVersion Include="xunit.v3.mtp-v2" Version="4.0.0-pre.81" />
<PackageVersion Include="xunit.v3.aot.mtp-v2" Version="4.0.0-pre.81" />
</ItemGroup>
</Project>
5 changes: 5 additions & 0 deletions global.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"test": {
"runner": "Microsoft.Testing.Platform"
}
}
1 change: 1 addition & 0 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />

<PropertyGroup>
<IsAotCompatible>true</IsAotCompatible>
<IsPackable>true</IsPackable>
<TargetFrameworks>net8.0;net9.0;net10.0;netstandard2.0;netstandard2.1</TargetFrameworks>
<Copyright>Copyright (c) .NET Foundation and Contributors; Andre Hofmeister</Copyright>
Expand Down
6 changes: 3 additions & 3 deletions src/Docker.DotNet.NPipe/DockerHandlerFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ public ResolvedTransport CreateHandler(NPipeTransportOptions transportOptions, C

var dockerStream = new DockerPipeStream(clientStream);

#if NETSTANDARD
var namedPipeConnectTimeout = (int)transportOptions.ConnectTimeout.TotalMilliseconds;
#else
#if NET
var namedPipeConnectTimeout = transportOptions.ConnectTimeout;
#else
var namedPipeConnectTimeout = (int)transportOptions.ConnectTimeout.TotalMilliseconds;
#endif

await clientStream.ConnectAsync(namedPipeConnectTimeout, cancellationToken)
Expand Down
2 changes: 1 addition & 1 deletion src/Docker.DotNet.NativeHttp/DockerHandlerFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public ResolvedTransport CreateHandler(NativeHttpTransportOptions transportOptio
var scheme = clientOptions.AuthProvider.TlsEnabled ? Uri.UriSchemeHttps : Uri.UriSchemeHttp;
var uri = new UriBuilder(clientOptions.Endpoint) { Scheme = scheme }.Uri;

#if NET6_0_OR_GREATER
#if NET
var handler = new SocketsHttpHandler
{
MaxConnectionsPerServer = MaxConnectionsPerServer,
Expand Down
2 changes: 1 addition & 1 deletion src/Docker.DotNet.NativeHttp/NativeHttpTransportOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace Docker.DotNet.NativeHttp;
/// </summary>
public sealed record NativeHttpTransportOptions
{
#if NET6_0_OR_GREATER
#if NET
/// <summary>
/// Gets a callback that configures the created <see cref="SocketsHttpHandler"/> instance.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Docker.DotNet.X509/CertificateCredentials.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public CertificateCredentials(X509Certificate2? certificate)

public HttpMessageHandler ConfigureHandler(HttpMessageHandler handler)
{
#if NET6_0_OR_GREATER
#if NET
if (handler is SocketsHttpHandler socketsHandler)
{
if (_certificate != null)
Expand Down
8 changes: 3 additions & 5 deletions src/Docker.DotNet.X509/DockerTlsCertificates.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public static X509Certificate2 LoadCertificateFromPemFiles(string certPemPath, s
}

return certificate;
#elif NET6_0_OR_GREATER
#elif NET
var certificate = X509Certificate2.CreateFromPemFile(certPemPath, keyPemPath);

if (OperatingSystem.IsWindows())
Expand All @@ -75,10 +75,8 @@ public static X509Certificate2 LoadCertificateFromPemFiles(string certPemPath, s
}

return certificate;
#elif NETSTANDARD
return Polyfills.X509Certificate2.CreateFromPemFile(certPemPath, keyPemPath);
#else
return X509Certificate2.CreateFromPemFile(certPemPath, keyPemPath);
return Polyfills.X509Certificate2.CreateFromPemFile(certPemPath, keyPemPath);
#endif
}

Expand Down Expand Up @@ -141,7 +139,7 @@ public static RemoteCertificateValidationCallback CreateCertificateAuthorityVali
using var chain = new X509Chain();
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;

#if NET5_0_OR_GREATER
#if NET
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
chain.ChainPolicy.CustomTrustStore.Add(certificateAuthorityCertificate);
return chain.Build(serverCertificate2);
Expand Down
2 changes: 2 additions & 0 deletions src/Docker.DotNet/Docker.DotNet.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<Using Include="System.Collections.Concurrent" />
<Using Include="System.Collections.Generic" />
<Using Include="System.Diagnostics" />
<Using Include="System.Diagnostics.CodeAnalysis" />
<Using Include="System.Globalization" />
<Using Include="System.IO" />
<Using Include="System.IO.Pipelines" />
Expand All @@ -49,6 +50,7 @@
<Using Include="System.Text" />
<Using Include="System.Text.Json" />
<Using Include="System.Text.Json.Serialization" />
<Using Include="System.Text.Json.Serialization.Metadata" />
<Using Include="System.Threading" />
<Using Include="System.Threading.Tasks" />
<Using Include="Docker.DotNet" />
Expand Down
4 changes: 1 addition & 3 deletions src/Docker.DotNet/DockerClient.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
namespace Docker.DotNet;

using System;

public sealed class DockerClient : IDockerClient
{
internal readonly IEnumerable<ApiResponseErrorHandlingDelegate> NoErrorHandlers = Enumerable.Empty<ApiResponseErrorHandlingDelegate>();
Expand Down Expand Up @@ -370,7 +368,7 @@ private async Task<HttpResponseMessage> PrivateMakeRequestAsync(

if (Timeout.InfiniteTimeSpan == timeout)
{
#if NET6_0_OR_GREATER
#if NET
return await _client.SendAsync(request, completionOption, cancellationToken)
.ConfigureAwait(false);
#else
Expand Down
2 changes: 1 addition & 1 deletion src/Docker.DotNet/Endpoints/ConfigsOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ internal ConfigOperations(DockerClient client)

public async Task<IList<SwarmConfig>> ListConfigsAsync(CancellationToken cancellationToken = default)
{
return await _client.MakeRequestAsync<IList<SwarmConfig>>(_client.NoErrorHandlers, HttpMethod.Get, "configs", cancellationToken)
return await _client.MakeRequestAsync<SwarmConfig[]>(_client.NoErrorHandlers, HttpMethod.Get, "configs", cancellationToken)
.ConfigureAwait(false);
}

Expand Down
4 changes: 2 additions & 2 deletions src/Docker.DotNet/Endpoints/ISwarmOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ public interface ISwarmOperations
/// 500 - Server error.
/// 503 - Node is not part of a swarm.
/// </remarks>
Task<IEnumerable<SwarmService>> ListServicesAsync(ServiceListParameters? parameters = null, CancellationToken cancellationToken = default);
Task<IList<SwarmService>> ListServicesAsync(ServiceListParameters? parameters = null, CancellationToken cancellationToken = default);
Comment thread
HofmeisterAn marked this conversation as resolved.

/// <summary>
/// Update a service.
Expand Down Expand Up @@ -198,7 +198,7 @@ public interface ISwarmOperations
/// 503 - Node is not part of a swarm.
/// </remarks>
/// <returns></returns>
Task<IEnumerable<NodeListResponse>> ListNodesAsync(CancellationToken cancellationToken = default);
Task<IList<NodeListResponse>> ListNodesAsync(CancellationToken cancellationToken = default);

/// <summary>
/// Inspect a node.
Expand Down
4 changes: 2 additions & 2 deletions src/Docker.DotNet/Endpoints/SwarmOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ await _client.MakeRequestAsync([NotInSwarmResponseHandler], HttpMethod.Post, "sw
.ConfigureAwait(false);
}

public async Task<IEnumerable<SwarmService>> ListServicesAsync(ServiceListParameters? parameters = null, CancellationToken cancellationToken = default)
public async Task<IList<SwarmService>> ListServicesAsync(ServiceListParameters? parameters = null, CancellationToken cancellationToken = default)
{
var queryParameters = parameters == null ? null : new QueryString<ServiceListParameters>(parameters);

Expand Down Expand Up @@ -193,7 +193,7 @@ private static Dictionary<string, string> RegistryAuthHeaders(AuthConfig? authCo
};
}

public async Task<IEnumerable<NodeListResponse>> ListNodesAsync(CancellationToken cancellationToken = default)
public async Task<IList<NodeListResponse>> ListNodesAsync(CancellationToken cancellationToken = default)
{
return await _client.MakeRequestAsync<NodeListResponse[]>([NotInSwarmResponseHandler], HttpMethod.Get, "nodes", cancellationToken)
.ConfigureAwait(false);
Expand Down
6 changes: 0 additions & 6 deletions src/Docker.DotNet/IQueryStringConverterInstanceFactory.cs

This file was deleted.

52 changes: 46 additions & 6 deletions src/Docker.DotNet/JsonSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@ static JsonSerializer()

private JsonSerializer()
Comment thread
campersau marked this conversation as resolved.
{
_options.TypeInfoResolver = JsonTypeInfoResolver.Combine(
DockerModelsJsonSerializerContext.Default,
DockerExtendedJsonSerializerContext.Default);
_options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
_options.Converters.Add(new JsonEnumMemberConverter<RestartPolicyKind>());
_options.Converters.Add(new JsonEnumMemberConverter<TaskState>());
_options.Converters.Add(new JsonDateTimeConverter());
_options.Converters.Add(new JsonNullableDateTimeConverter());
_options.MakeReadOnly();
}

public static JsonSerializer Instance { get; }
Expand All @@ -38,22 +42,22 @@ public HttpContent GetHttpContent<T>(T value)

public string Serialize<T>(T value)
{
return System.Text.Json.JsonSerializer.Serialize(value, _options);
return System.Text.Json.JsonSerializer.Serialize(value, (JsonTypeInfo<T>)_options.GetTypeInfo(typeof(T)));
}

public byte[] SerializeToUtf8Bytes<T>(T value)
{
return System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(value, _options);
return System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(value, (JsonTypeInfo<T>)_options.GetTypeInfo(typeof(T)));
}

public T Deserialize<T>(byte[] json)
{
return System.Text.Json.JsonSerializer.Deserialize<T>(json, _options)!;
return System.Text.Json.JsonSerializer.Deserialize(json, (JsonTypeInfo<T>)_options.GetTypeInfo(typeof(T)))!;
}

public Task<T> DeserializeAsync<T>(HttpContent content, CancellationToken cancellationToken)
{
return content.ReadFromJsonAsync<T>(_options, cancellationToken)!;
return content.ReadFromJsonAsync((JsonTypeInfo<T>)_options.GetTypeInfo(typeof(T)), cancellationToken)!;
}

public async IAsyncEnumerable<T> DeserializeAsync<T>(Stream stream, [EnumeratorCancellation] CancellationToken cancellationToken)
Expand All @@ -69,7 +73,7 @@ public async IAsyncEnumerable<T> DeserializeAsync<T>(Stream stream, [EnumeratorC

while (!buffer.IsEmpty && TryParseJson(ref buffer, out var jsonDocument))
{
yield return jsonDocument!.Deserialize<T>(_options)!;
yield return jsonDocument!.Deserialize((JsonTypeInfo<T>)_options.GetTypeInfo(typeof(T)))!;
}

if (result.IsCompleted)
Expand All @@ -95,4 +99,40 @@ private static bool TryParseJson(ref ReadOnlySequence<byte> buffer, out JsonDocu

return false;
}
}
}

// Additional source-generated metadata for collections and dictionaries used by operations.

// Filters
[JsonSerializable(typeof(IDictionary<string, IDictionary<string, bool>>))]

// ConfigOperations.ListConfigsAsync
[JsonSerializable(typeof(SwarmConfig[]))]

// ContainerOperations.ListContainersAsync
[JsonSerializable(typeof(ContainerListResponse[]))]
// ContainerOperations.InspectChangesAsync
[JsonSerializable(typeof(ContainerFileSystemChangeResponse[]))]

// ImageOperations.ListImagesAsync
[JsonSerializable(typeof(ImagesListResponse[]))]
// ImageOperations.GetImageHistoryAsync
[JsonSerializable(typeof(ImageHistoryResponse[]))]
// ImageOperations.DeleteImageAsync
[JsonSerializable(typeof(Dictionary<string, string>[]))]
// ImageOperations.SearchImagesAsync
[JsonSerializable(typeof(ImageSearchResponse[]))]

// NetworkOperations.ListNetworksAsync
[JsonSerializable(typeof(NetworkResponse[]))]

// PluginOperations.ListPluginsAsync
[JsonSerializable(typeof(Plugin[]))]
// PluginOperations.GetPrivilegesAsync
[JsonSerializable(typeof(PluginPrivilege[]))]
Comment thread
campersau marked this conversation as resolved.
Outdated

// SwarmOperations.ListServicesAsync
[JsonSerializable(typeof(SwarmService[]))]
// SwarmOperations.ListNodesAsync
[JsonSerializable(typeof(NodeListResponse[]))]
internal sealed partial class DockerExtendedJsonSerializerContext : JsonSerializerContext { }
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ public CommitContainerChangesParameters(ContainerConfig Config)
[QueryStringParameter("author", false)]
public string? Author { get; set; }

[QueryStringParameter("changes", false, typeof(EnumerableQueryStringConverter))]
[QueryStringParameter<EnumerableQueryStringConverter>("changes", false)]
public IList<string>? Changes { get; set; }

[QueryStringParameter("pause", false, typeof(BoolQueryStringConverter))]
[QueryStringParameter<BoolQueryStringConverter>("pause", false)]
public bool? Pause { get; set; }

[JsonPropertyName("Hostname")]
Expand Down
10 changes: 5 additions & 5 deletions src/Docker.DotNet/Models/ContainerAttachParameters.Generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@ namespace Docker.DotNet.Models
{
public class ContainerAttachParameters // (main.ContainerAttachParameters)
{
[QueryStringParameter("stream", false, typeof(BoolQueryStringConverter))]
[QueryStringParameter<BoolQueryStringConverter>("stream", false)]
public bool? Stream { get; set; }

[QueryStringParameter("stdin", false, typeof(BoolQueryStringConverter))]
[QueryStringParameter<BoolQueryStringConverter>("stdin", false)]
public bool? Stdin { get; set; }

[QueryStringParameter("stdout", false, typeof(BoolQueryStringConverter))]
[QueryStringParameter<BoolQueryStringConverter>("stdout", false)]
public bool? Stdout { get; set; }

[QueryStringParameter("stderr", false, typeof(BoolQueryStringConverter))]
[QueryStringParameter<BoolQueryStringConverter>("stderr", false)]
public bool? Stderr { get; set; }

[QueryStringParameter("detachKeys", false)]
public string? DetachKeys { get; set; }

[QueryStringParameter("logs", false, typeof(BoolQueryStringConverter))]
[QueryStringParameter<BoolQueryStringConverter>("logs", false)]
public bool? Logs { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public class ContainerEventsParameters // (main.ContainerEventsParameters)
[QueryStringParameter("until", false)]
public string? Until { get; set; }

[QueryStringParameter("filters", false, typeof(MapQueryStringConverter))]
[QueryStringParameter<MapQueryStringConverter>("filters", false)]
public IDictionary<string, IDictionary<string, bool>>? Filters { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ namespace Docker.DotNet.Models
{
public class ContainerInspectParameters // (main.ContainerInspectParameters)
{
[QueryStringParameter("size", false, typeof(BoolQueryStringConverter))]
[QueryStringParameter<BoolQueryStringConverter>("size", false)]
public bool? IncludeSize { get; set; }
}
}
8 changes: 4 additions & 4 deletions src/Docker.DotNet/Models/ContainerLogsParameters.Generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ namespace Docker.DotNet.Models
{
public class ContainerLogsParameters // (main.ContainerLogsParameters)
{
[QueryStringParameter("stdout", false, typeof(BoolQueryStringConverter))]
[QueryStringParameter<BoolQueryStringConverter>("stdout", false)]
public bool? ShowStdout { get; set; }

[QueryStringParameter("stderr", false, typeof(BoolQueryStringConverter))]
[QueryStringParameter<BoolQueryStringConverter>("stderr", false)]
public bool? ShowStderr { get; set; }

[QueryStringParameter("since", false)]
Expand All @@ -15,10 +15,10 @@ public class ContainerLogsParameters // (main.ContainerLogsParameters)
[QueryStringParameter("until", false)]
public string? Until { get; set; }

[QueryStringParameter("timestamps", false, typeof(BoolQueryStringConverter))]
[QueryStringParameter<BoolQueryStringConverter>("timestamps", false)]
public bool? Timestamps { get; set; }

[QueryStringParameter("follow", false, typeof(BoolQueryStringConverter))]
[QueryStringParameter<BoolQueryStringConverter>("follow", false)]
public bool? Follow { get; set; }

[QueryStringParameter("tail", false)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ namespace Docker.DotNet.Models
{
public class ContainerRemoveParameters // (main.ContainerRemoveParameters)
{
[QueryStringParameter("v", false, typeof(BoolQueryStringConverter))]
[QueryStringParameter<BoolQueryStringConverter>("v", false)]
public bool? RemoveVolumes { get; set; }

[QueryStringParameter("link", false, typeof(BoolQueryStringConverter))]
[QueryStringParameter<BoolQueryStringConverter>("link", false)]
public bool? RemoveLinks { get; set; }

[QueryStringParameter("force", false, typeof(BoolQueryStringConverter))]
[QueryStringParameter<BoolQueryStringConverter>("force", false)]
public bool? Force { get; set; }
}
}
Loading