Skip to content

Conversation

Jaben
Copy link
Member

@Jaben Jaben commented Oct 5, 2025

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)

  • ✅ Created 9 new console application examples
  • ✅ Converted all LinqPad scripts to standalone console apps targeting .NET 8
  • ✅ Added centralized Directory.Build.props for shared configuration
  • ✅ Added shared appsettings.json for common settings
  • ✅ Moved 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

Basic Authentication Support

Library Changes:

  • ✅ Created BasicAuthHandler as a reusable 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
  • ✅ Added BasicAuthUsername and BasicAuthPassword properties to GotenbergSharpClientOptions

Example Updates:

  • ✅ All examples load configuration from appsettings.json using IConfiguration
  • ✅ Use GotenbergSharpClientOptions instead of magic strings
  • ✅ Non-DI examples manually create BasicAuthHandler from options
  • ✅ DI example automatically uses BasicAuthHandler via middleware
  • ✅ No hardcoded credentials or URLs

API Updates

All examples updated to use latest API (v2.8.1):

  • WithDimensions()WithPageProperties()
  • PdfFormatsLibrePdfFormats
  • Removed deprecated SetUserAgent() and SetEmulatedMediaType() calls
  • Removed obsolete EnableNativePdfFormat()
  • Updated LibrePdfFormats.A1aA2b (A1a deprecated in Gotenberg 7.6+)
  • Fixed CopyToAsync() calls to use CancellationToken.None instead of default

Examples Created

  1. HtmlConvert - HTML to PDF with embedded assets
  2. DIExample - Dependency injection with logging and Polly retry policy
  3. PdfMerge - Merge multiple PDFs
  4. UrlsToMergedPdf - Convert multiple URLs to PDFs and merge
  5. HtmlWithMarkdown - HTML with Markdown to PDF
  6. OfficeMerge - Merge Office documents to PDF
  7. UrlConvert - URL to PDF with custom header/footer
  8. Webhook - Webhook for async PDF generation
  9. PdfConvert - Convert PDFs to PDF/A formats

Benefits

  • 🔒 Security: Configuration-driven basic auth via middleware
  • 📦 Reusable: BasicAuthHandler can be used by any library consumer
  • 🎯 Type-safe: Uses GotenbergSharpClientOptions instead of magic strings
  • 🔧 Maintainable: Centralized configuration in appsettings.json
  • Clean: No hardcoded credentials or URLs in code
  • 🧪 Testable: Middleware approach enables easy testing
  • 🌍 Cross-platform: Lowercase resource paths work on all OS

Testing

  • ✅ All 9 examples build successfully with 0 errors and 0 warnings
  • ✅ Examples work with Gotenberg instances using --api-enable-basic-auth
  • ✅ Pre-configured for docker-compose-basic-auth.yml setup

Files Changed

  • 49 files changed in initial conversion
  • 13 files changed for basic auth middleware
  • Total: 62 files changed, ~959 insertions

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added a suite of runnable example applications (HtmlConvert, HtmlWithMarkdown, PdfMerge, PdfConvert, UrlConvert, UrlsToMergedPdf, OfficeMerge, DIExample, Webhook) with shared build/config and sample appsettings.
  • Documentation
    • README updated to reference examples; added Examples README with prerequisites and usage instructions.
  • Chores
    • Solution updated to include example projects; .gitignore extended to ignore local settings and example output directories.
  • Refactor
    • Basic auth is now configurable via a pluggable HTTP handler with validation and DI-friendly registration.

Jaben and others added 5 commits October 5, 2025 15:22
- 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>
Copy link
Contributor

coderabbitai bot commented Oct 5, 2025

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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.

📥 Commits

Reviewing files that changed from the base of the PR and between 096803d and 4b93802.

📒 Files selected for processing (2)
  • examples/UrlsToMergedPdf/Program.cs (1 hunks)
  • examples/Webhook/Program.cs (1 hunks)

Walkthrough

Adds 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

Cohort / File(s) Summary
Repo meta
\.gitignore, README.md
Add ignore for settings.local.json and examples/**/output; update README references from linqpad to examples.
Solution structure
GotenbergSharpApiClient.sln
Add Examples solution folder and many example projects, update solution project mappings, configurations, and nested project entries.
Examples: shared config & docs
examples/Directory.Build.props, examples/appsettings.json, examples/README.md
Add shared build props targeting net8.0 with Microsoft.Extensions package refs and resource copy settings; add example appsettings.json and README for examples.
Examples: project files
examples/*/*.csproj, examples/.../*.csproj
Add minimal SDK project files for DIExample, HtmlConvert, HtmlWithMarkdown, OfficeMerge, PdfConvert, PdfMerge, UrlConvert, UrlsToMergedPdf, Webhook, and an Examples container.
Examples: programs
examples/DIExample/Program.cs, examples/HtmlConvert/Program.cs, examples/HtmlWithMarkdown/Program.cs, examples/OfficeMerge/Program.cs, examples/PdfConvert/Program.cs, examples/PdfMerge/Program.cs, examples/UrlConvert/Program.cs, examples/UrlsToMergedPdf/Program.cs, examples/Webhook/Program.cs
Add runnable example programs demonstrating configuration loading, client construction, request builders (Url/Html/Pdf/Office/Webhook), usage of GotenbergSharpClient, and output file creation.
Client DI registration
src/Gotenberg.Sharp.Api.Client/Extensions/TypedClientServiceCollectionExtensions.cs
Remove inline BasicAuth header construction; register an HttpMessageHandler via AddHttpMessageHandler that validates credentials and chooses BasicAuthHandler when both credentials exist or PassThroughHandler otherwise; adjust builder return.
Client pipeline handlers
src/Gotenberg.Sharp.Api.Client/Infrastructure/Pipeline/BasicAuthHandler.cs, src/Gotenberg.Sharp.Api.Client/Infrastructure/Pipeline/PassThroughHandler.cs
Add BasicAuthHandler (encodes and injects HTTP Basic Authorization header) and PassThroughHandler (no-op DelegatingHandler).

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

A rabbit in code with a tiny hat,
Examples sprout paths where the PDFs sat.
Handlers hum softly — auth or pass through,
Streams stitch and merge, stamped shiny and new.
Hops, build, convert — a tidy job well true. 🐇📄

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title accurately summarizes the key work of this PR by highlighting the conversion of LinqPad scripts into standalone .NET 8 console applications and the introduction of basic authentication middleware, matching the objectives and major changes without extraneous detail.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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>
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 the Count property instead of the Count() 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 call AddHttpMessageHandler 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:

  1. 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.
  2. 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:

  1. The response stream from ConvertPdfDocumentsAsync is not explicitly disposed, though it may be consumed by CopyToAsync. For clarity and to ensure cleanup, wrap it in a using statement.
  2. 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 an await using (and the destination FileStream 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. Use await 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 stream

The stream returned from MergePdfsAsync is left undisposed, keeping the HTTP connection open longer than necessary. Wrap both the response and destination streams in await 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

📥 Commits

Reviewing files that changed from the base of the PR and between a5b8338 and 3cc6fdb.

⛔ 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 properties

Verified 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.csproj
examples/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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between 3cc6fdb and 28bb8fd.

📒 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

Jaben and others added 2 commits October 5, 2025 16:30
…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>
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 to UrlToPdfAsync, but this call to MergePdfsAsync 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

📥 Commits

Reviewing files that changed from the base of the PR and between 28bb8fd and f4875f2.

📒 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 to GotenbergSharpClientOptions 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 and HttpClient prevent resource leaks as requested in the previous review. While the handler ownership semantics could be refined (handlers owned by HttpClient don't strictly need explicit using statements), the current approach is safe because Dispose is idempotent.


98-107: LGTM! File writing is correct.

The file writing implementation properly uses CancellationToken.None with CopyToAsync (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 LGTM

Nice job explicitly catching partial basic-auth configuration and using null-forgiving to satisfy nullable flow; the handler selection now reads clearly.

Jaben and others added 3 commits October 5, 2025 19:25
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>
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 while headerPath 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

📥 Commits

Reviewing files that changed from the base of the PR and between ce98331 and 096803d.

📒 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.

Comment on lines +26 to +35
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
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

Suggested change
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.

Comment on lines +26 to +35
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
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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 disposes authHandler, which disposes its InnerHandler (handler), then both using statements dispose them again.
  • If authHandler is null: httpClient disposes handler, then the using 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.

Suggested change
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).

Comment on lines +56 to +57
b.SetHeader(await File.ReadAllTextAsync(headerPath))
.SetFooter(await File.ReadAllBytesAsync(footerPath))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

@Jaben Jaben merged commit dd1e9b0 into develop Oct 5, 2025
3 checks passed
Jaben added a commit that referenced this pull request Oct 6, 2025
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant