-
Notifications
You must be signed in to change notification settings - Fork 25
Convert LinqPad examples to .NET 8 console apps with basic auth middleware #66
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
Conversation
- Created 9 new console app examples in /examples directory - Converted all LinqPad scripts to standalone console applications - Updated all examples to use latest API (v2.8.1) - WithDimensions() → WithPageProperties() - PdfFormats → LibrePdfFormats - Removed deprecated SetUserAgent() and SetEmulatedMediaType() - Removed obsolete EnableNativePdfFormat() - Updated LibrePdfFormats.A1a → A2b (A1a deprecated in Gotenberg 7.6+) - Added centralized configuration via Directory.Build.props - Common PropertyGroup settings (target framework, nullable, etc.) - Shared package references (Microsoft.Extensions.*) - Shared project reference to Gotenberg.Sharp.Api.Client - Automatic resource copying to output directories - Added shared appsettings.json for all examples - Moved and renamed resources to lowercase /examples/resources/ for cross-platform compatibility - Created comprehensive examples/README.md with usage instructions - Updated main README.md to reference examples instead of linqpad folder - All examples build successfully with 0 errors and 0 warnings 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Library changes: - Created BasicAuthHandler as a DelegatingHandler for HttpClient pipeline - Created PassThroughHandler for when no auth is configured - Modified AddGotenbergSharpClient to use AddHttpMessageHandler - Handler reads credentials from GotenbergSharpClientOptions - Properly integrates with HttpClient factory and handler pipeline Example updates: - All examples now load configuration from appsettings.json - Use GotenbergSharpClientOptions instead of magic strings - Non-DI examples manually create BasicAuthHandler from options - DI example automatically uses BasicAuthHandler via middleware - Added BasicAuthUsername and BasicAuthPassword to appsettings.json - Updated examples/README.md to document basic auth configuration Benefits of this approach: - Reusable BasicAuthHandler across all HttpClient instances - Configuration-driven (appsettings.json) instead of hardcoded values - Properly integrates with DI and HttpClientFactory - Auth header added automatically to all requests via middleware - Clean separation of concerns - Type-safe with GotenbergSharpClientOptions - Testable and maintainable All examples build successfully with 0 warnings and 0 errors. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Warning Rate limit exceeded@Jaben has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 17 minutes and 19 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (2)
WalkthroughAdds an Examples solution folder with multiple runnable example projects and shared configs, updates .gitignore and README references, and refactors DI HttpClient registration to use either a new BasicAuthHandler or PassThroughHandler via AddHttpMessageHandler. No public API surface changes. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor ExampleApp as Example App
participant DI as ServiceCollection
participant HCF as HttpClientFactory
participant BH as BasicAuthHandler
participant PH as PassThroughHandler
participant GC as GotenbergSharpClient
participant API as Gotenberg Server
ExampleApp->>DI: AddGotenbergSharpClient(options)
DI->>HCF: Configure typed HttpClient
alt BasicAuth credentials present
HCF->>BH: Add BasicAuthHandler (inject Authorization)
else No credentials
HCF->>PH: Add PassThroughHandler (no-op)
end
ExampleApp->>GC: Resolve client from DI
ExampleApp->>GC: Invoke UrlToPdf/HtmlToPdf/Merge/etc.
GC->>API: HTTP request (with/without Authorization)
API-->>GC: Response stream
GC-->>ExampleApp: PDF/ZIP stream
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Resolved conflicts: - .gitignore: Kept examples/**/output ignore pattern - TypedClientServiceCollectionExtensions.cs: Kept XML documentation from develop and BasicAuthHandler implementation from feature branch All examples build successfully after merge. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 9
♻️ Duplicate comments (7)
examples/HtmlConvert/HtmlConvert.csproj (1)
1-2
: Verify that required project properties are inherited from Directory.Build.props.Same as OfficeMerge.csproj - this minimal project file must inherit essential properties from a parent Directory.Build.props file. Ensure the shared configuration includes TargetFramework (net8.0), OutputType (Exe), and other console app settings.
examples/PdfMerge/PdfMerge.csproj (1)
1-2
: Verify that required project properties are inherited from Directory.Build.props.Same concern as other example projects - ensure Directory.Build.props provides the necessary .NET 8 console application configuration.
examples/HtmlWithMarkdown/HtmlWithMarkdown.csproj (1)
1-2
: Verify that required project properties are inherited from Directory.Build.props.Same concern as other example projects - ensure Directory.Build.props provides the necessary .NET 8 console application configuration.
examples/PdfConvert/PdfConvert.csproj (1)
1-2
: Same configuration concern as UrlsToMergedPdf.csproj.This file has the same minimal structure and relies on Directory.Build.props for configuration. The verification requested for UrlsToMergedPdf.csproj applies here as well.
examples/UrlConvert/UrlConvert.csproj (1)
1-2
: Same configuration concern as previously reviewed .csproj files.This file follows the same minimal structure pattern. The verification requested for UrlsToMergedPdf.csproj applies here as well.
examples/Webhook/Webhook.csproj (1)
1-2
: Same configuration concern as previously reviewed .csproj files.This file follows the same minimal structure pattern. The verification requested for UrlsToMergedPdf.csproj applies here as well.
examples/PdfConvert/Program.cs (1)
59-59
: Use materialized count instead of re-enumerating.This line calls
Count()
on the items collection again. If the earlier suggestion to materialize the collection with.ToList()
is applied, change this to use theCount
property instead of theCount()
method to avoid re-enumeration.Change (after applying the previous refactor):
- var extension = items.Count() > 1 ? "zip" : "pdf"; + var extension = items.Count > 1 ? "zip" : "pdf";
🧹 Nitpick comments (9)
src/Gotenberg.Sharp.Api.Client/Infrastructure/Pipeline/PassThroughHandler.cs (1)
21-28
: Consider avoiding the PassThroughHandler entirely when no auth is configured.The PassThroughHandler adds a no-op delegation step to the HTTP pipeline when authentication is not needed. While minimal, this introduces unnecessary overhead.
Consider refactoring
TypedClientServiceCollectionExtensions.cs
to conditionally callAddHttpMessageHandler
only when credentials are present:- .AddHttpMessageHandler(sp => - { - var ops = GetOptions(sp); - - // Add basic auth handler if credentials are configured - if (!string.IsNullOrWhiteSpace(ops.BasicAuthUsername) && - !string.IsNullOrWhiteSpace(ops.BasicAuthPassword)) - { - return new BasicAuthHandler(ops.BasicAuthUsername, ops.BasicAuthPassword); - } - - // Return a pass-through handler if no auth is configured - return new PassThroughHandler(); - })Replace with conditional handler registration:
// Add basic auth handler only if credentials are configured var ops = services.BuildServiceProvider().GetRequiredService<IOptions<GotenbergSharpClientOptions>>().Value; if (!string.IsNullOrWhiteSpace(ops.BasicAuthUsername) && !string.IsNullOrWhiteSpace(ops.BasicAuthPassword)) { builder.AddHttpMessageHandler(sp => new BasicAuthHandler( GetOptions(sp).BasicAuthUsername!, GetOptions(sp).BasicAuthPassword!)); }This would eliminate the PassThroughHandler class entirely and avoid the delegation overhead in non-auth scenarios.
examples/PdfConvert/Program.cs (4)
11-17
: Consider adding configuration validation.The configuration loading pattern is correct, but there's no validation that the required settings are present or valid. If appsettings.json is missing or the GotenbergSharpClient section is not configured, the application will fail later with less clear error messages.
Consider adding validation after binding:
var options = new GotenbergSharpClientOptions(); config.GetSection(nameof(GotenbergSharpClient)).Bind(options); + +if (options.ServiceUrl == null) +{ + Console.Error.WriteLine("Error: GotenbergSharpClient configuration is missing or incomplete in appsettings.json"); + return; +}
19-21
: Validate source directory existence.The code creates the destination directory but doesn't verify that the source directory exists. If the source path is missing or invalid, the error will occur later in
DoConversion
when trying to enumerate files, resulting in a less clear error message.Add validation after setting the paths:
var sourcePath = args.Length > 0 ? args[0] : Path.Combine(Directory.GetCurrentDirectory(), "pdfs"); var destinationPath = args.Length > 1 ? args[1] : Path.Combine(Directory.GetCurrentDirectory(), "output"); + +if (!Directory.Exists(sourcePath)) +{ + Console.Error.WriteLine($"Error: Source directory not found: {sourcePath}"); + return; +} + Directory.CreateDirectory(destinationPath);
38-47
: Improve file enumeration efficiency and add empty directory check.Two concerns:
- The code doesn't check if the source directory contains any PDF files, which will result in an unclear error later when trying to build the request.
- Multiple calls to
Count()
enumerate the collection each time, which is inefficient.Apply this diff to address both issues:
var items = Directory.GetFiles(sourcePath, "*.pdf", SearchOption.TopDirectoryOnly) .Select(p => new { Info = new FileInfo(p), Path = p }) .OrderBy(item => item.Info.CreationTime) - .Take(2); + .Take(2) + .ToList(); + if (items.Count == 0) + { + throw new InvalidOperationException($"No PDF files found in {sourcePath}"); + } + - Console.WriteLine($"Converting {items.Count()} PDFs:"); + Console.WriteLine($"Converting {items.Count} PDFs:"); foreach (var item in items) { Console.WriteLine($" - {item.Info.Name}"); } var toConvert = items.Select(item => KeyValuePair.Create(item.Info.Name, File.ReadAllBytes(item.Path)));
49-68
: Add disposal for response stream and consider error handling.Two issues:
- The response stream from
ConvertPdfDocumentsAsync
is not explicitly disposed, though it may be consumed byCopyToAsync
. For clarity and to ensure cleanup, wrap it in a using statement.- There's no error handling for API failures, network issues, or file I/O errors. While this is an example, basic error handling would make it more robust.
var request = builder.Build(); - var response = await sharpClient.ConvertPdfDocumentsAsync(request); + using var response = await sharpClient.ConvertPdfDocumentsAsync(request); // If you send one in -- the result is PDF. var extension = items.Count() > 1 ? "zip" : "pdf"; var outPath = Path.Combine(destinationPath, $"GotenbergConvertResult.{extension}"); using (var destinationStream = File.Create(outPath)) { await response.CopyToAsync(destinationStream, CancellationToken.None); } return outPath; }Additionally, consider wrapping the main logic in a try-catch for better error reporting:
-var result = await DoConversion(sourcePath, destinationPath, options); -Console.WriteLine($"Converted PDF created: {result}"); +try +{ + var result = await DoConversion(sourcePath, destinationPath, options); + Console.WriteLine($"Converted PDF created: {result}"); +} +catch (Exception ex) +{ + Console.Error.WriteLine($"Error during conversion: {ex.Message}"); + return 1; +} + +return 0;examples/HtmlConvert/Program.cs (1)
51-57
: Dispose the response stream
HtmlToPdfAsync
returns an open HTTP/content stream. Because it is never disposed, the underlying connection can linger, especially if you call this more than once. Wrap it in anawait using
(and the destinationFileStream
as well) so the handles are released promptly.- var response = await sharpClient.HtmlToPdfAsync(request); - - using (var destinationStream = File.Create(resultPath)) - { - await response.CopyToAsync(destinationStream); - } + await using var response = await sharpClient.HtmlToPdfAsync(request); + await using var destinationStream = File.Create(resultPath); + await response.CopyToAsync(destinationStream);examples/OfficeMerge/Program.cs (1)
41-48
: Dispose the merge response stream
MergeOfficeDocsAsync
hands back a live stream that never gets disposed, so sockets/file handles stick around. Useawait using
for both the response and the created destination stream.- var response = await client.MergeOfficeDocsAsync(builder).ConfigureAwait(false); - - var mergeResultPath = Path.Combine(destinationDirectory, $"GotenbergOfficeMerge-{DateTime.Now:yyyyMMddHHmmss}.pdf"); - - using (var destinationStream = File.Create(mergeResultPath)) - { - await response.CopyToAsync(destinationStream).ConfigureAwait(false); - } + await using var response = await client.MergeOfficeDocsAsync(builder).ConfigureAwait(false); + + var mergeResultPath = Path.Combine(destinationDirectory, $"GotenbergOfficeMerge-{DateTime.Now:yyyyMMddHHmmss}.pdf"); + + await using var destinationStream = File.Create(mergeResultPath); + await response.CopyToAsync(destinationStream).ConfigureAwait(false);examples/PdfMerge/Program.cs (1)
54-61
: Dispose the merged PDF streamThe stream returned from
MergePdfsAsync
is left undisposed, keeping the HTTP connection open longer than necessary. Wrap both the response and destination streams inawait using
to clean up deterministically.- var response = await sharpClient.MergePdfsAsync(request); - - var outPath = Path.Combine(destinationPath, "GotenbergMergeResult.pdf"); - - using (var destinationStream = File.Create(outPath)) - { - await response.CopyToAsync(destinationStream, CancellationToken.None); - } + await using var response = await sharpClient.MergePdfsAsync(request); + + var outPath = Path.Combine(destinationPath, "GotenbergMergeResult.pdf"); + + await using var destinationStream = File.Create(outPath); + await response.CopyToAsync(destinationStream, CancellationToken.None);examples/UrlConvert/Program.cs (1)
28-31
: Consider extracting the BasicAuth setup pattern.The conditional BasicAuth handler setup is duplicated across three example files (UrlConvert, HtmlWithMarkdown, UrlsToMergedPdf). Consider extracting this into a shared helper method to reduce duplication and improve maintainability.
Example shared helper:
static HttpMessageHandler CreateAuthHandler(GotenbergSharpClientOptions options) { var handler = new HttpClientHandler(); if (!string.IsNullOrWhiteSpace(options.BasicAuthUsername) && !string.IsNullOrWhiteSpace(options.BasicAuthPassword)) { return new BasicAuthHandler(options.BasicAuthUsername, options.BasicAuthPassword) { InnerHandler = handler }; } return handler; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (9)
examples/resources/Html/ConvertExample/ear-on-beach.jpg
is excluded by!**/*.jpg
examples/resources/Html/font.woff
is excluded by!**/*.woff
examples/resources/Html/img.gif
is excluded by!**/*.gif
examples/resources/Markdown/font.woff
is excluded by!**/*.woff
examples/resources/Markdown/img.gif
is excluded by!**/*.gif
examples/resources/OfficeDocs/Visual-Studio-NOTICE.docx
is excluded by!**/*.docx
examples/resources/OfficeDocs/document.docx
is excluded by!**/*.docx
examples/resources/OfficeDocs/document2.docx
is excluded by!**/*.docx
examples/resources/office/document.docx
is excluded by!**/*.docx
📒 Files selected for processing (27)
.gitignore
(1 hunks)GotenbergSharpApiClient.sln
(2 hunks)README.md
(1 hunks)examples/DIExample/DIExample.csproj
(1 hunks)examples/DIExample/Program.cs
(1 hunks)examples/Directory.Build.props
(1 hunks)examples/HtmlConvert/HtmlConvert.csproj
(1 hunks)examples/HtmlConvert/Program.cs
(1 hunks)examples/HtmlWithMarkdown/HtmlWithMarkdown.csproj
(1 hunks)examples/HtmlWithMarkdown/Program.cs
(1 hunks)examples/OfficeMerge/OfficeMerge.csproj
(1 hunks)examples/OfficeMerge/Program.cs
(1 hunks)examples/PdfConvert/PdfConvert.csproj
(1 hunks)examples/PdfConvert/Program.cs
(1 hunks)examples/PdfMerge/PdfMerge.csproj
(1 hunks)examples/PdfMerge/Program.cs
(1 hunks)examples/README.md
(1 hunks)examples/UrlConvert/Program.cs
(1 hunks)examples/UrlConvert/UrlConvert.csproj
(1 hunks)examples/UrlsToMergedPdf/Program.cs
(1 hunks)examples/UrlsToMergedPdf/UrlsToMergedPdf.csproj
(1 hunks)examples/Webhook/Program.cs
(1 hunks)examples/Webhook/Webhook.csproj
(1 hunks)examples/appsettings.json
(1 hunks)src/Gotenberg.Sharp.Api.Client/Extensions/TypedClientServiceCollectionExtensions.cs
(2 hunks)src/Gotenberg.Sharp.Api.Client/Infrastructure/Pipeline/BasicAuthHandler.cs
(1 hunks)src/Gotenberg.Sharp.Api.Client/Infrastructure/Pipeline/PassThroughHandler.cs
(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (13)
examples/UrlConvert/UrlConvert.csproj (1)
lib/Infrastructure/Constants.cs (1)
UrlConvert
(287-292)
src/Gotenberg.Sharp.Api.Client/Extensions/TypedClientServiceCollectionExtensions.cs (4)
src/Gotenberg.Sharp.Api.Client/Infrastructure/Pipeline/BasicAuthHandler.cs (2)
BasicAuthHandler
(24-49)BasicAuthHandler
(34-38)src/Gotenberg.Sharp.Api.Client/Infrastructure/Pipeline/PassThroughHandler.cs (1)
PassThroughHandler
(21-29)lib/Domain/Settings/GotenbergSharpClientOptions.cs (1)
GotenbergSharpClientOptions
(18-45)lib/Extensions/TypedClientServiceCollectionExtensions.cs (1)
sp
(36-51)
examples/DIExample/Program.cs (3)
src/Gotenberg.Sharp.Api.Client/Domain/Requests/UrlRequest.cs (1)
UrlRequest
(20-91)lib/GotenbergSharpClient.cs (6)
GotenbergSharpClient
(37-319)Task
(186-193)Task
(195-205)GotenbergSharpClient
(56-70)Task
(95-104)Task
(80-87)lib/Domain/Settings/GotenbergSharpClientOptions.cs (1)
GotenbergSharpClientOptions
(18-45)
examples/PdfMerge/Program.cs (4)
src/Gotenberg.Sharp.Api.Client/Infrastructure/Constants.cs (1)
Gotenberg
(51-295)src/Gotenberg.Sharp.Api.Client/Infrastructure/Pipeline/BasicAuthHandler.cs (3)
Task
(40-48)BasicAuthHandler
(24-49)BasicAuthHandler
(34-38)lib/GotenbergSharpClient.cs (6)
GotenbergSharpClient
(37-319)Task
(147-154)Task
(161-168)Task
(175-184)Task
(186-193)Task
(195-205)lib/Domain/Requests/MergeRequest.cs (2)
Gotenberg
(16-51)MergeRequest
(18-50)
examples/HtmlConvert/Program.cs (3)
src/Gotenberg.Sharp.Api.Client/Infrastructure/Pipeline/BasicAuthHandler.cs (3)
Task
(40-48)BasicAuthHandler
(24-49)BasicAuthHandler
(34-38)lib/GotenbergSharpClient.cs (3)
GotenbergSharpClient
(37-319)Task
(129-138)Task
(113-120)lib/Domain/Requests/HtmlRequest.cs (1)
HtmlRequest
(27-61)
examples/UrlsToMergedPdf/Program.cs (5)
src/Gotenberg.Sharp.Api.Client/Infrastructure/Pipeline/BasicAuthHandler.cs (3)
Task
(40-48)BasicAuthHandler
(24-49)BasicAuthHandler
(34-38)src/Gotenberg.Sharp.Api.Client/Domain/Requests/UrlRequest.cs (1)
UrlRequest
(20-91)lib/GotenbergSharpClient.cs (8)
GotenbergSharpClient
(37-319)Task
(80-87)Task
(186-193)Task
(195-205)Task
(95-104)Task
(161-168)Task
(113-120)GotenbergSharpClient
(56-70)lib/Domain/Requests/MergeRequest.cs (1)
Gotenberg
(16-51)lib/Domain/Settings/GotenbergSharpClientOptions.cs (1)
GotenbergSharpClientOptions
(18-45)
src/Gotenberg.Sharp.Api.Client/Infrastructure/Pipeline/BasicAuthHandler.cs (1)
src/Gotenberg.Sharp.Api.Client/Infrastructure/Pipeline/PassThroughHandler.cs (1)
Task
(23-28)
examples/Webhook/Program.cs (3)
src/Gotenberg.Sharp.Api.Client/Infrastructure/Constants.cs (1)
Gotenberg
(51-295)src/Gotenberg.Sharp.Api.Client/Extensions/TypedClientServiceCollectionExtensions.cs (1)
GotenbergSharpClientOptions
(81-84)src/Gotenberg.Sharp.Api.Client/Infrastructure/Pipeline/BasicAuthHandler.cs (3)
Task
(40-48)BasicAuthHandler
(24-49)BasicAuthHandler
(34-38)
examples/PdfConvert/Program.cs (2)
src/Gotenberg.Sharp.Api.Client/Extensions/TypedClientServiceCollectionExtensions.cs (1)
GotenbergSharpClientOptions
(81-84)src/Gotenberg.Sharp.Api.Client/Infrastructure/Pipeline/BasicAuthHandler.cs (3)
Task
(40-48)BasicAuthHandler
(24-49)BasicAuthHandler
(34-38)
src/Gotenberg.Sharp.Api.Client/Infrastructure/Pipeline/PassThroughHandler.cs (1)
src/Gotenberg.Sharp.Api.Client/Infrastructure/Pipeline/BasicAuthHandler.cs (1)
Task
(40-48)
examples/UrlConvert/Program.cs (1)
src/Gotenberg.Sharp.Api.Client/Infrastructure/Pipeline/BasicAuthHandler.cs (3)
Task
(40-48)BasicAuthHandler
(24-49)BasicAuthHandler
(34-38)
examples/OfficeMerge/Program.cs (1)
src/Gotenberg.Sharp.Api.Client/Infrastructure/Pipeline/BasicAuthHandler.cs (3)
Task
(40-48)BasicAuthHandler
(24-49)BasicAuthHandler
(34-38)
examples/HtmlWithMarkdown/Program.cs (2)
src/Gotenberg.Sharp.Api.Client/Extensions/TypedClientServiceCollectionExtensions.cs (1)
GotenbergSharpClientOptions
(81-84)src/Gotenberg.Sharp.Api.Client/Infrastructure/Pipeline/BasicAuthHandler.cs (3)
Task
(40-48)BasicAuthHandler
(24-49)BasicAuthHandler
(34-38)
🪛 GitHub Actions: Build and Push to Nuget
examples/DIExample/DIExample.csproj
[error] 1-1: dotnet build failed: Assets file '/home/runner/work/GotenbergSharpApiClient/GotenbergSharpApiClient/examples/DIExample/obj/project.assets.json' not found. Run 'dotnet restore' (NuGet package restore) to generate this file.
🔇 Additional comments (11)
README.md (1)
89-89
: LGTM! Documentation updated correctly.The path reference has been correctly updated from the linqPad folder to the examples folder, aligning with the PR's objective to convert LinqPad scripts to .NET 8 console applications.
src/Gotenberg.Sharp.Api.Client/Infrastructure/Pipeline/BasicAuthHandler.cs (1)
24-49
: LGTM! Basic Auth implementation is correct.The implementation properly:
- Validates credentials in the constructor (lines 34-38)
- Encodes credentials using ASCII and Base64 (line 44)
- Sets the Authorization header with the "Basic" scheme (line 45)
The approach is standard for HTTP Basic Authentication handlers.
src/Gotenberg.Sharp.Api.Client/Extensions/TypedClientServiceCollectionExtensions.cs (2)
35-41
: LGTM! Simplified first overload.The first overload now delegates to the second overload with a clean configuration action, removing the inline Basic Auth logic in favor of the new handler-based approach.
51-79
: LGTM! Handler-based authentication is correctly implemented.The changes properly:
- Introduce a local
builder
variable for clarity (line 51)- Add conditional handler selection via
AddHttpMessageHandler
(lines 61-74)- Select
BasicAuthHandler
when credentials are configured (lines 66-70)- Fall back to
PassThroughHandler
when no auth is needed (line 73)- Return the configured builder (line 78)
This refactoring centralizes authentication handling into the HTTP pipeline, making it more testable and maintainable.
examples/OfficeMerge/OfficeMerge.csproj (1)
1-2
: No action needed – required properties are inherited. Directory.Build.props sets<OutputType>Exe</OutputType>
and<TargetFramework>net8.0</TargetFramework>
.examples/appsettings.json (1)
1-14
: Configuration is appropriate for example projects.The configuration structure is well-organized with sensible defaults for local development:
- Localhost URLs for local Gotenberg instance
- Test credentials matching docker-compose-basic-auth.yml
- Reasonable retry policy settings (4 retries, exponential backoff)
The hardcoded test credentials are acceptable for example projects, as they're clearly documented as matching the provided Docker Compose configuration.
examples/README.md (1)
1-119
: Excellent documentation for the example suite.The README provides comprehensive coverage of all example projects with:
- Clear prerequisites and setup instructions
- Detailed usage examples with command-line arguments
- Appropriate warnings about special requirements (e.g., increased timeouts for UrlsToMergedPdf, webhook receiver for Webhook example)
- Good explanation of shared configuration and project structure
The documentation effectively guides users through running the examples and understanding the codebase organization.
examples/UrlsToMergedPdf/UrlsToMergedPdf.csproj (1)
1-2
: Directory.Build.props provides required project propertiesVerified that Directory.Build.props contains
<OutputType>Exe</OutputType>
and<TargetFramework>net8.0</TargetFramework>
, so the minimal .csproj requires no changes.examples/DIExample/DIExample.csproj (1)
1-2
: Ensure DIExample project restores correctly
- DIExample.csproj is referenced in the solution (✓).
- Confirm the CI pipeline’s
dotnet restore
targets the solution root (includes examples/DIExample).- Verify Directory.Build.props covers this project and doesn’t exclude or override its settings.
- Run locally:
dotnet restore GotenbergSharpApiClient.sln dotnet build examples/DIExample/DIExample.csprojexamples/DIExample/Program.cs (1)
45-54
: LGTM: Clean DI setup.The dependency injection configuration properly wires up the client, options, and logging. This is the recommended approach for production scenarios.
examples/UrlsToMergedPdf/Program.cs (1)
83-83
: LGTM: Correct use of CancellationToken.None.This line correctly passes
CancellationToken.None
to the async operation, aligning with the PR objectives.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
GotenbergSharpApiClient.sln
(2 hunks)README.md
(1 hunks)src/Gotenberg.Sharp.Api.Client/Extensions/TypedClientServiceCollectionExtensions.cs
(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- README.md
🧰 Additional context used
🧬 Code graph analysis (2)
GotenbergSharpApiClient.sln (2)
test/GotenbergSharpClient.Tests/HtmlConversionBehaviorBuilderTests.cs (4)
TestFixture
(9-208)Test
(65-86)Test
(88-128)Test
(164-207)test/GotenbergSharpClient.Tests/CookieTests.cs (1)
TestFixture
(7-224)
src/Gotenberg.Sharp.Api.Client/Extensions/TypedClientServiceCollectionExtensions.cs (4)
src/Gotenberg.Sharp.Api.Client/Infrastructure/Pipeline/BasicAuthHandler.cs (2)
BasicAuthHandler
(24-49)BasicAuthHandler
(34-38)src/Gotenberg.Sharp.Api.Client/Infrastructure/Pipeline/PassThroughHandler.cs (1)
PassThroughHandler
(21-29)test/GotenbergSharpClient.Tests/BasicAuthTests.cs (6)
options
(25-30)TestFixture
(11-130)options
(55-59)Test
(18-46)options
(83-88)Test
(116-129)lib/Domain/Settings/GotenbergSharpClientOptions.cs (1)
GotenbergSharpClientOptions
(18-45)
🪛 GitHub Check: build (8.x)
src/Gotenberg.Sharp.Api.Client/Extensions/TypedClientServiceCollectionExtensions.cs
[warning] 94-94:
Possible null reference argument for parameter 'password' in 'BasicAuthHandler.BasicAuthHandler(string username, string password)'.
[warning] 94-94:
Possible null reference argument for parameter 'username' in 'BasicAuthHandler.BasicAuthHandler(string username, string password)'.
🔇 Additional comments (1)
GotenbergSharpApiClient.sln (1)
10-16
: Well-organized solution structure.The solution folder organization is well-structured. The Examples folder with solution items (appsettings.json, Directory.Build.props, README.md) and the NestedProjects section properly groups all example projects together, making the solution easy to navigate.
Also applies to: 172-182
src/Gotenberg.Sharp.Api.Client/Extensions/TypedClientServiceCollectionExtensions.cs
Show resolved
Hide resolved
…nc calls Resource disposal fixes: - Wrapped HttpClientHandler, BasicAuthHandler, and HttpClient in using declarations - Ensures proper disposal of all HTTP-related resources when method exits - Prevents resource leaks in non-DI examples CancellationToken consistency: - Added explicit CancellationToken.None parameter to all CopyToAsync calls - Ensures consistent cancellation token usage across all examples - Removed .ConfigureAwait(false) in favor of explicit cancellation token Files updated: - DIExample: Added using System.Threading, CancellationToken.None - HtmlConvert: Added using declarations, CancellationToken.None - HtmlWithMarkdown: Added using declarations, CancellationToken.None - OfficeMerge: Added using declarations, CancellationToken.None (removed ConfigureAwait) - PdfMerge: Added using declarations (already had CancellationToken.None) - PdfConvert: Added using declarations (already had CancellationToken.None) - UrlConvert: Added using declarations, CancellationToken.None - Webhook: Added using declarations - UrlsToMergedPdf: Added using declarations, CancellationToken.None All 9 examples build successfully with 0 errors and 0 warnings. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…stration - Added null-forgiving operators (!) to BasicAuthUsername and BasicAuthPassword - Added validation to ensure both username and password are provided together - Throws InvalidOperationException if only one credential is configured - Prevents silently falling back to PassThroughHandler on misconfiguration - Uses XOR operator (^) to detect partial configuration This ensures users get clear error messages when BasicAuth is misconfigured instead of silently failing to authenticate. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (1)
examples/UrlsToMergedPdf/Program.cs (1)
93-93
: Consider adding CancellationToken.None for consistency.Line 83 passes
CancellationToken.None
toUrlToPdfAsync
, but this call toMergePdfsAsync
doesn't include a cancellation token parameter. For consistency with the PR objective of explicitly providing cancellation tokens, consider adding it if the method signature supports it.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
examples/DIExample/Program.cs
(1 hunks)examples/HtmlConvert/Program.cs
(1 hunks)examples/HtmlWithMarkdown/Program.cs
(1 hunks)examples/OfficeMerge/Program.cs
(1 hunks)examples/PdfConvert/Program.cs
(1 hunks)examples/PdfMerge/Program.cs
(1 hunks)examples/UrlConvert/Program.cs
(1 hunks)examples/UrlsToMergedPdf/Program.cs
(1 hunks)examples/Webhook/Program.cs
(1 hunks)src/Gotenberg.Sharp.Api.Client/Extensions/TypedClientServiceCollectionExtensions.cs
(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
- examples/HtmlConvert/Program.cs
- examples/OfficeMerge/Program.cs
- examples/HtmlWithMarkdown/Program.cs
- examples/PdfConvert/Program.cs
- examples/Webhook/Program.cs
- examples/PdfMerge/Program.cs
🧰 Additional context used
🧬 Code graph analysis (4)
examples/DIExample/Program.cs (4)
src/Gotenberg.Sharp.Api.Client/Infrastructure/Constants.cs (1)
Gotenberg
(51-295)src/Gotenberg.Sharp.Api.Client/Domain/Requests/UrlRequest.cs (1)
UrlRequest
(20-91)lib/GotenbergSharpClient.cs (6)
GotenbergSharpClient
(37-319)Task
(186-193)Task
(195-205)GotenbergSharpClient
(56-70)Task
(80-87)Task
(95-104)lib/Domain/Settings/GotenbergSharpClientOptions.cs (1)
GotenbergSharpClientOptions
(18-45)
examples/UrlConvert/Program.cs (3)
src/Gotenberg.Sharp.Api.Client/Infrastructure/Pipeline/BasicAuthHandler.cs (3)
Task
(40-48)BasicAuthHandler
(24-49)BasicAuthHandler
(34-38)lib/GotenbergSharpClient.cs (6)
GotenbergSharpClient
(37-319)Task
(80-87)GotenbergSharpClient
(56-70)Task
(95-104)Task
(186-193)Task
(195-205)lib/Domain/Settings/GotenbergSharpClientOptions.cs (1)
GotenbergSharpClientOptions
(18-45)
examples/UrlsToMergedPdf/Program.cs (3)
src/Gotenberg.Sharp.Api.Client/Infrastructure/Constants.cs (1)
Gotenberg
(51-295)src/Gotenberg.Sharp.Api.Client/Infrastructure/Pipeline/BasicAuthHandler.cs (3)
Task
(40-48)BasicAuthHandler
(24-49)BasicAuthHandler
(34-38)src/Gotenberg.Sharp.Api.Client/Domain/Requests/UrlRequest.cs (1)
UrlRequest
(20-91)
src/Gotenberg.Sharp.Api.Client/Extensions/TypedClientServiceCollectionExtensions.cs (3)
src/Gotenberg.Sharp.Api.Client/Infrastructure/Pipeline/BasicAuthHandler.cs (2)
BasicAuthHandler
(24-49)BasicAuthHandler
(34-38)src/Gotenberg.Sharp.Api.Client/Infrastructure/Pipeline/PassThroughHandler.cs (1)
PassThroughHandler
(21-29)src/Gotenberg.Sharp.Api.Client/Infrastructure/Pipeline/PolicyFactory.cs (1)
PolicyFactory
(33-75)
🔇 Additional comments (8)
examples/UrlsToMergedPdf/Program.cs (7)
1-18
: LGTM! Configuration loading is correct.The configuration loading pattern using
IConfiguration
and binding toGotenbergSharpClientOptions
follows best practices.
20-24
: LGTM! Entry point is clean.The main entry point correctly handles directory creation and orchestrates the PDF generation flow.
26-46
: LGTM! URL collection and request building is well-structured.The function clearly demonstrates the pattern of building multiple URL requests for batch conversion.
48-65
: LGTM! Request builder configuration demonstrates the API well.The builder pattern usage is correct, and the updated
WithPageProperties
method is used appropriately (as per API v2.8.1 changes).
69-78
: LGTM! Handler disposal addresses previous feedback.The
using
statements on the handlers andHttpClient
prevent resource leaks as requested in the previous review. While the handler ownership semantics could be refined (handlers owned byHttpClient
don't strictly need explicitusing
statements), the current approach is safe becauseDispose
is idempotent.
98-107
: LGTM! File writing is correct.The file writing implementation properly uses
CancellationToken.None
withCopyToAsync
(as addressed in the previous review) and correctly disposes the destination stream.
80-80
: GotenbergSharpClient does not implement IDisposable
GotenbergSharpClient doesn’t implement IDisposable, so no using block is needed.src/Gotenberg.Sharp.Api.Client/Extensions/TypedClientServiceCollectionExtensions.cs (1)
86-107
: Credential guard and null-safety fix LGTMNice job explicitly catching partial basic-auth configuration and using null-forgiving to satisfy nullable flow; the handler selection now reads clearly.
Added Debug|Any CPU.Build.0 and Release|Any CPU.Build.0 entries for all 9 example projects in the solution file. This ensures examples are built during CI/CD pipeline execution. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
♻️ Duplicate comments (1)
examples/Webhook/Program.cs (1)
19-20
: Swapped header/footer paths (previously reported).The path assignments remain incorrect:
footerPath
points to UrlHeader.html whileheaderPath
points to UrlFooter.html. This issue was already flagged in a previous review.
🧹 Nitpick comments (2)
examples/OfficeMerge/Program.cs (1)
56-66
: Consider explaining or removing the arbitrary limit.Line 65 takes the first 10 documents without explanation. If this limit serves a specific purpose (e.g., avoiding memory issues or demonstrating pagination), add a comment. If it's not necessary for the example, consider removing it.
Additionally, the function doesn't validate that the source directory exists or contains files. While acceptable for example code, you might want to add basic error handling:
static async Task<IEnumerable<KeyValuePair<string, byte[]>>> GetDocsAsync(string sourceDirectory) { if (!Directory.Exists(sourceDirectory)) throw new DirectoryNotFoundException($"Source directory not found: {sourceDirectory}"); var paths = Directory.GetFiles(sourceDirectory, "*.*", SearchOption.TopDirectoryOnly); if (paths.Length == 0) throw new InvalidOperationException($"No files found in: {sourceDirectory}"); var names = paths.Select(p => new FileInfo(p).Name); var tasks = paths.Select(f => File.ReadAllBytesAsync(f)); var docs = await Task.WhenAll(tasks); // Limit to 10 documents to keep the example manageable return names.Select((name, index) => KeyValuePair.Create(name, docs[index])) .Take(10); }examples/HtmlConvert/Program.cs (1)
37-58
: Consider adding basic error handling.The conversion and file operations work correctly. For a production-ready example, consider wrapping the API call and file operations in try-catch blocks to demonstrate proper error handling (e.g., Gotenberg API errors, file I/O failures).
Example error handling:
try { var response = await sharpClient.HtmlToPdfAsync(request); await using var destinationStream = File.Create(resultPath); await response.CopyToAsync(destinationStream, CancellationToken.None); return resultPath; } catch (HttpRequestException ex) { Console.WriteLine($"Gotenberg API error: {ex.Message}"); throw; } catch (IOException ex) { Console.WriteLine($"File I/O error: {ex.Message}"); throw; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
examples/DIExample/Program.cs
(1 hunks)examples/HtmlConvert/Program.cs
(1 hunks)examples/HtmlWithMarkdown/Program.cs
(1 hunks)examples/OfficeMerge/Program.cs
(1 hunks)examples/PdfConvert/Program.cs
(1 hunks)examples/PdfMerge/Program.cs
(1 hunks)examples/UrlConvert/Program.cs
(1 hunks)examples/UrlsToMergedPdf/Program.cs
(1 hunks)examples/Webhook/Program.cs
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
- examples/DIExample/Program.cs
- examples/PdfConvert/Program.cs
- examples/UrlConvert/Program.cs
- examples/UrlsToMergedPdf/Program.cs
- examples/PdfMerge/Program.cs
- examples/HtmlWithMarkdown/Program.cs
🧰 Additional context used
🧬 Code graph analysis (3)
examples/HtmlConvert/Program.cs (3)
src/Gotenberg.Sharp.Api.Client/Infrastructure/Constants.cs (1)
Gotenberg
(51-295)src/Gotenberg.Sharp.Api.Client/Infrastructure/Pipeline/BasicAuthHandler.cs (3)
Task
(40-48)BasicAuthHandler
(24-49)BasicAuthHandler
(34-38)lib/GotenbergSharpClient.cs (3)
GotenbergSharpClient
(37-319)Task
(129-138)Task
(113-120)
examples/OfficeMerge/Program.cs (1)
src/Gotenberg.Sharp.Api.Client/Infrastructure/Pipeline/BasicAuthHandler.cs (3)
Task
(40-48)BasicAuthHandler
(24-49)BasicAuthHandler
(34-38)
examples/Webhook/Program.cs (1)
src/Gotenberg.Sharp.Api.Client/Infrastructure/Pipeline/BasicAuthHandler.cs (3)
Task
(40-48)BasicAuthHandler
(24-49)BasicAuthHandler
(34-38)
🔇 Additional comments (4)
examples/HtmlConvert/Program.cs (3)
1-14
: LGTM!The imports are appropriate, and the configuration loading pattern is standard and correct.
16-22
: LGTM!The destination directory setup with command-line override is well-structured, and the resource path configuration is appropriate for a console example.
63-71
: LGTM!The helper methods correctly read resource files asynchronously. If desired, you could add error handling to provide clearer messages when resource files are missing, but this is acceptable for an example.
examples/Webhook/Program.cs (1)
31-42
: LGTM!The HttpClient setup with conditional BasicAuth is implemented correctly. The handler chaining pattern matches the BasicAuthHandler design, and resource cleanup via
using
statements is proper.
using var handler = new HttpClientHandler(); | ||
using var authHandler = !string.IsNullOrWhiteSpace(options.BasicAuthUsername) && !string.IsNullOrWhiteSpace(options.BasicAuthPassword) | ||
? new BasicAuthHandler(options.BasicAuthUsername, options.BasicAuthPassword) { InnerHandler = handler } | ||
: null; | ||
|
||
using var httpClient = new HttpClient(authHandler ?? (HttpMessageHandler)handler) | ||
{ | ||
BaseAddress = options.ServiceUrl, | ||
Timeout = options.TimeOut | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix double-disposal of message handlers.
The using var
declarations on both handler
and authHandler
cause double-disposal. When HttpClient
is disposed, it disposes its message handler (either authHandler
or handler
). Since authHandler
wraps handler
as its InnerHandler
, disposing authHandler
also disposes handler
. The explicit using
statements then attempt to dispose these handlers again at the end of the scope.
Apply this diff to fix the disposal pattern:
- using var handler = new HttpClientHandler();
- using var authHandler = !string.IsNullOrWhiteSpace(options.BasicAuthUsername) && !string.IsNullOrWhiteSpace(options.BasicAuthPassword)
+ var handler = new HttpClientHandler();
+ var authHandler = !string.IsNullOrWhiteSpace(options.BasicAuthUsername) && !string.IsNullOrWhiteSpace(options.BasicAuthPassword)
? new BasicAuthHandler(options.BasicAuthUsername, options.BasicAuthPassword) { InnerHandler = handler }
: null;
using var httpClient = new HttpClient(authHandler ?? (HttpMessageHandler)handler)
{
BaseAddress = options.ServiceUrl,
Timeout = options.TimeOut
};
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
using var handler = new HttpClientHandler(); | |
using var authHandler = !string.IsNullOrWhiteSpace(options.BasicAuthUsername) && !string.IsNullOrWhiteSpace(options.BasicAuthPassword) | |
? new BasicAuthHandler(options.BasicAuthUsername, options.BasicAuthPassword) { InnerHandler = handler } | |
: null; | |
using var httpClient = new HttpClient(authHandler ?? (HttpMessageHandler)handler) | |
{ | |
BaseAddress = options.ServiceUrl, | |
Timeout = options.TimeOut | |
}; | |
var handler = new HttpClientHandler(); | |
var authHandler = !string.IsNullOrWhiteSpace(options.BasicAuthUsername) && !string.IsNullOrWhiteSpace(options.BasicAuthPassword) | |
? new BasicAuthHandler(options.BasicAuthUsername, options.BasicAuthPassword) { InnerHandler = handler } | |
: null; | |
using var httpClient = new HttpClient(authHandler ?? (HttpMessageHandler)handler) | |
{ | |
BaseAddress = options.ServiceUrl, | |
Timeout = options.TimeOut | |
}; |
🤖 Prompt for AI Agents
In examples/HtmlConvert/Program.cs around lines 26 to 35, the HttpMessageHandler
instances are declared with "using var" causing double-disposal when HttpClient
(which disposes its handler chain) is disposed; create the handlers without
"using" (remove the using on handler and authHandler), build authHandler with
handler as InnerHandler if needed, and keep HttpClient in a using so only
HttpClient is disposed (it will cascade-dispose the handler(s)); alternatively,
if you prefer explicit disposal, dispose only the outermost handler (authHandler
if non-null, otherwise handler) after disposing HttpClient — but do not dispose
both.
using var handler = new HttpClientHandler(); | ||
using var authHandler = !string.IsNullOrWhiteSpace(options.BasicAuthUsername) && !string.IsNullOrWhiteSpace(options.BasicAuthPassword) | ||
? new BasicAuthHandler(options.BasicAuthUsername, options.BasicAuthPassword) { InnerHandler = handler } | ||
: null; | ||
|
||
using var httpClient = new HttpClient(authHandler ?? (HttpMessageHandler)handler) | ||
{ | ||
BaseAddress = options.ServiceUrl, | ||
Timeout = options.TimeOut | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix the handler disposal pattern to prevent double-dispose.
When handlers are passed to HttpClient
, the client takes ownership and disposes them automatically. The current pattern with using
statements on lines 26-29 creates a double (or triple) disposal scenario:
- If
authHandler
is not null:httpClient
disposesauthHandler
, which disposes itsInnerHandler
(handler
), then bothusing
statements dispose them again. - If
authHandler
is null:httpClient
disposeshandler
, then theusing
statement disposes it again.
While HttpClientHandler
typically handles multiple disposals gracefully, this pattern is incorrect and could cause issues with other handler implementations.
Apply this diff to fix the disposal pattern:
- using var handler = new HttpClientHandler();
- using var authHandler = !string.IsNullOrWhiteSpace(options.BasicAuthUsername) && !string.IsNullOrWhiteSpace(options.BasicAuthPassword)
- ? new BasicAuthHandler(options.BasicAuthUsername, options.BasicAuthPassword) { InnerHandler = handler }
- : null;
+ var handler = new HttpClientHandler();
+ var authHandler = !string.IsNullOrWhiteSpace(options.BasicAuthUsername) && !string.IsNullOrWhiteSpace(options.BasicAuthPassword)
+ ? new BasicAuthHandler(options.BasicAuthUsername, options.BasicAuthPassword) { InnerHandler = handler }
+ : null;
using var httpClient = new HttpClient(authHandler ?? (HttpMessageHandler)handler)
{
BaseAddress = options.ServiceUrl,
Timeout = options.TimeOut
};
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
using var handler = new HttpClientHandler(); | |
using var authHandler = !string.IsNullOrWhiteSpace(options.BasicAuthUsername) && !string.IsNullOrWhiteSpace(options.BasicAuthPassword) | |
? new BasicAuthHandler(options.BasicAuthUsername, options.BasicAuthPassword) { InnerHandler = handler } | |
: null; | |
using var httpClient = new HttpClient(authHandler ?? (HttpMessageHandler)handler) | |
{ | |
BaseAddress = options.ServiceUrl, | |
Timeout = options.TimeOut | |
}; | |
var handler = new HttpClientHandler(); | |
var authHandler = !string.IsNullOrWhiteSpace(options.BasicAuthUsername) && !string.IsNullOrWhiteSpace(options.BasicAuthPassword) | |
? new BasicAuthHandler(options.BasicAuthUsername, options.BasicAuthPassword) { InnerHandler = handler } | |
: null; | |
using var httpClient = new HttpClient(authHandler ?? (HttpMessageHandler)handler) | |
{ | |
BaseAddress = options.ServiceUrl, | |
Timeout = options.TimeOut | |
}; |
🤖 Prompt for AI Agents
In examples/OfficeMerge/Program.cs around lines 26 to 35, the handler disposal
pattern is incorrect because handlers are wrapped in using and also passed to
HttpClient (which will dispose them), causing double-dispose; fix by removing
the using statements for the handlers and only dispose the HttpClient (create
HttpClientHandler and optional BasicAuthHandler without using, assign
InnerHandler when creating auth handler, then create HttpClient with the handler
and keep a single using or explicit disposal for the HttpClient itself so
ownership/disposal is handled once).
b.SetHeader(await File.ReadAllTextAsync(headerPath)) | ||
.SetFooter(await File.ReadAllBytesAsync(footerPath)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix inconsistent file reading methods.
SetHeader
uses ReadAllTextAsync
while SetFooter
uses ReadAllBytesAsync
. Since both are HTML files, they should be read consistently as text to avoid encoding issues.
Apply this diff to use consistent text reading:
.AddAsyncHeaderFooter(async b =>
b.SetHeader(await File.ReadAllTextAsync(headerPath))
- .SetFooter(await File.ReadAllBytesAsync(footerPath))
+ .SetFooter(await File.ReadAllTextAsync(footerPath))
)
🤖 Prompt for AI Agents
In examples/Webhook/Program.cs around lines 56 to 57, the header is read with
File.ReadAllTextAsync while the footer is read with File.ReadAllBytesAsync
causing inconsistency for HTML files; change the footer read to use await
File.ReadAllTextAsync(footerPath) (optionally specifying Encoding.UTF8 if
needed) so both header and footer are read as text and passed to SetFooter as a
string.
Includes all changes since v2.8.1: - Configuration improvements - Comprehensive documentation (PR #65) - Examples conversion and bug fixes (PR #66) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Summary
Converted all LinqPad scripts to standalone .NET 8 console applications with proper configuration management and basic authentication support via reusable HTTP middleware.
Changes
Examples Directory (/examples)
Directory.Build.props
for shared configurationappsettings.json
for common settingsexamples/resources/
for cross-platform compatibilityexamples/README.md
with usage instructionsBasic Authentication Support
Library Changes:
BasicAuthHandler
as a reusableDelegatingHandler
for HttpClient pipelinePassThroughHandler
for when no auth is configuredAddGotenbergSharpClient
to useAddHttpMessageHandler
GotenbergSharpClientOptions
BasicAuthUsername
andBasicAuthPassword
properties toGotenbergSharpClientOptions
Example Updates:
appsettings.json
usingIConfiguration
GotenbergSharpClientOptions
instead of magic stringsBasicAuthHandler
from optionsBasicAuthHandler
via middlewareAPI Updates
All examples updated to use latest API (v2.8.1):
WithDimensions()
→WithPageProperties()
PdfFormats
→LibrePdfFormats
SetUserAgent()
andSetEmulatedMediaType()
callsEnableNativePdfFormat()
LibrePdfFormats.A1a
→A2b
(A1a deprecated in Gotenberg 7.6+)CopyToAsync()
calls to useCancellationToken.None
instead ofdefault
Examples Created
Benefits
BasicAuthHandler
can be used by any library consumerGotenbergSharpClientOptions
instead of magic stringsappsettings.json
Testing
--api-enable-basic-auth
docker-compose-basic-auth.yml
setupFiles Changed
🤖 Generated with Claude Code
Summary by CodeRabbit