Skip to content

Commit 96eda82

Browse files
Jabenclaude
andcommitted
Add basic authentication support via reusable HTTP middleware
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>
1 parent b898101 commit 96eda82

13 files changed

Lines changed: 286 additions & 41 deletions

File tree

examples/HtmlConvert/Program.cs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,37 @@
11
using Gotenberg.Sharp.API.Client;
22
using Gotenberg.Sharp.API.Client.Domain.Builders;
33
using Gotenberg.Sharp.API.Client.Domain.Builders.Faceted;
4+
using Gotenberg.Sharp.API.Client.Domain.Settings;
5+
using Gotenberg.Sharp.API.Client.Infrastructure.Pipeline;
6+
using Microsoft.Extensions.Configuration;
7+
8+
var config = new ConfigurationBuilder()
9+
.SetBasePath(AppContext.BaseDirectory)
10+
.AddJsonFile("appsettings.json")
11+
.Build();
12+
13+
var options = new GotenbergSharpClientOptions();
14+
config.GetSection(nameof(GotenbergSharpClient)).Bind(options);
415

516
var destinationDirectory = args.Length > 0 ? args[0] : Path.Combine(Directory.GetCurrentDirectory(), "output");
617
Directory.CreateDirectory(destinationDirectory);
718

819
var resourcePath = Path.Combine(AppContext.BaseDirectory, "resources", "Html", "ConvertExample");
9-
var path = await CreateFromHtml(destinationDirectory, resourcePath);
20+
var path = await CreateFromHtml(destinationDirectory, resourcePath, options);
1021

1122
Console.WriteLine($"PDF created: {path}");
1223

13-
static async Task<string> CreateFromHtml(string destinationDirectory, string resourcePath)
24+
static async Task<string> CreateFromHtml(string destinationDirectory, string resourcePath, GotenbergSharpClientOptions options)
1425
{
15-
var sharpClient = new GotenbergSharpClient("http://localhost:3000");
26+
var handler = new HttpClientHandler();
27+
var httpClient = new HttpClient(
28+
!string.IsNullOrWhiteSpace(options.BasicAuthUsername) && !string.IsNullOrWhiteSpace(options.BasicAuthPassword)
29+
? new BasicAuthHandler(options.BasicAuthUsername, options.BasicAuthPassword) { InnerHandler = handler }
30+
: handler
31+
)
32+
{ BaseAddress = options.ServiceUrl };
33+
34+
var sharpClient = new GotenbergSharpClient(httpClient);
1635

1736
var builder = new HtmlRequestBuilder()
1837
.AddAsyncDocument(async doc =>

examples/HtmlWithMarkdown/Program.cs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,36 @@
11
using Gotenberg.Sharp.API.Client;
22
using Gotenberg.Sharp.API.Client.Domain.Builders;
3+
using Gotenberg.Sharp.API.Client.Domain.Settings;
4+
using Gotenberg.Sharp.API.Client.Infrastructure.Pipeline;
5+
using Microsoft.Extensions.Configuration;
6+
7+
var config = new ConfigurationBuilder()
8+
.SetBasePath(AppContext.BaseDirectory)
9+
.AddJsonFile("appsettings.json")
10+
.Build();
11+
12+
var options = new GotenbergSharpClientOptions();
13+
config.GetSection(nameof(GotenbergSharpClient)).Bind(options);
314

415
var destinationDirectory = args.Length > 0 ? args[0] : Path.Combine(Directory.GetCurrentDirectory(), "output");
516
Directory.CreateDirectory(destinationDirectory);
617

718
var resourcePath = Path.Combine(AppContext.BaseDirectory, "resources", "Markdown");
8-
var path = await CreateFromMarkdown(destinationDirectory, resourcePath);
19+
var path = await CreateFromMarkdown(destinationDirectory, resourcePath, options);
920

1021
Console.WriteLine($"PDF created from Markdown: {path}");
1122

12-
static async Task<string> CreateFromMarkdown(string destinationDirectory, string resourcePath)
23+
static async Task<string> CreateFromMarkdown(string destinationDirectory, string resourcePath, GotenbergSharpClientOptions options)
1324
{
14-
var sharpClient = new GotenbergSharpClient("http://localhost:3000");
25+
var handler = new HttpClientHandler();
26+
var httpClient = new HttpClient(
27+
!string.IsNullOrWhiteSpace(options.BasicAuthUsername) && !string.IsNullOrWhiteSpace(options.BasicAuthPassword)
28+
? new BasicAuthHandler(options.BasicAuthUsername, options.BasicAuthPassword) { InnerHandler = handler }
29+
: handler
30+
)
31+
{ BaseAddress = options.ServiceUrl };
32+
33+
var sharpClient = new GotenbergSharpClient(httpClient);
1534

1635
var builder = new HtmlRequestBuilder()
1736
.AddAsyncDocument(async b =>

examples/OfficeMerge/Program.cs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,36 @@
11
using Gotenberg.Sharp.API.Client;
22
using Gotenberg.Sharp.API.Client.Domain.Builders;
33
using Gotenberg.Sharp.API.Client.Domain.Builders.Faceted;
4+
using Gotenberg.Sharp.API.Client.Domain.Settings;
5+
using Gotenberg.Sharp.API.Client.Infrastructure.Pipeline;
6+
using Microsoft.Extensions.Configuration;
7+
8+
var config = new ConfigurationBuilder()
9+
.SetBasePath(AppContext.BaseDirectory)
10+
.AddJsonFile("appsettings.json")
11+
.Build();
12+
13+
var options = new GotenbergSharpClientOptions();
14+
config.GetSection(nameof(GotenbergSharpClient)).Bind(options);
415

516
var sourceDirectory = args.Length > 0 ? args[0] : Path.Combine(AppContext.BaseDirectory, "resources", "OfficeDocs");
617
var destinationDirectory = args.Length > 1 ? args[1] : Path.Combine(Directory.GetCurrentDirectory(), "output");
718
Directory.CreateDirectory(destinationDirectory);
819

9-
var path = await DoOfficeMerge(sourceDirectory, destinationDirectory);
20+
var path = await DoOfficeMerge(sourceDirectory, destinationDirectory, options);
1021
Console.WriteLine($"Merged Office documents PDF created: {path}");
1122

12-
static async Task<string> DoOfficeMerge(string sourceDirectory, string destinationDirectory)
23+
static async Task<string> DoOfficeMerge(string sourceDirectory, string destinationDirectory, GotenbergSharpClientOptions options)
1324
{
14-
var client = new GotenbergSharpClient("http://localhost:3000");
25+
var handler = new HttpClientHandler();
26+
var httpClient = new HttpClient(
27+
!string.IsNullOrWhiteSpace(options.BasicAuthUsername) && !string.IsNullOrWhiteSpace(options.BasicAuthPassword)
28+
? new BasicAuthHandler(options.BasicAuthUsername, options.BasicAuthPassword) { InnerHandler = handler }
29+
: handler
30+
)
31+
{ BaseAddress = options.ServiceUrl };
32+
33+
var client = new GotenbergSharpClient(httpClient);
1534

1635
var builder = new MergeOfficeBuilder()
1736
.ConfigureRequest(c => c.SetTrace("ConsoleExample"))

examples/PdfConvert/Program.cs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,39 @@
11
using Gotenberg.Sharp.API.Client;
22
using Gotenberg.Sharp.API.Client.Domain.Builders;
33
using Gotenberg.Sharp.API.Client.Domain.Builders.Faceted;
4+
using Gotenberg.Sharp.API.Client.Domain.Settings;
5+
using Gotenberg.Sharp.API.Client.Infrastructure.Pipeline;
6+
using Microsoft.Extensions.Configuration;
47

58
// If you get 1 file, the result is a PDF; get more and the API returns a zip containing the results
69
// Currently, Gotenberg supports these formats: A2b & A3b
710

11+
var config = new ConfigurationBuilder()
12+
.SetBasePath(AppContext.BaseDirectory)
13+
.AddJsonFile("appsettings.json")
14+
.Build();
15+
16+
var options = new GotenbergSharpClientOptions();
17+
config.GetSection(nameof(GotenbergSharpClient)).Bind(options);
18+
819
var sourcePath = args.Length > 0 ? args[0] : Path.Combine(Directory.GetCurrentDirectory(), "pdfs");
920
var destinationPath = args.Length > 1 ? args[1] : Path.Combine(Directory.GetCurrentDirectory(), "output");
1021
Directory.CreateDirectory(destinationPath);
1122

12-
var result = await DoConversion(sourcePath, destinationPath);
23+
var result = await DoConversion(sourcePath, destinationPath, options);
1324
Console.WriteLine($"Converted PDF created: {result}");
1425

15-
static async Task<string> DoConversion(string sourcePath, string destinationPath)
26+
static async Task<string> DoConversion(string sourcePath, string destinationPath, GotenbergSharpClientOptions options)
1627
{
17-
var sharpClient = new GotenbergSharpClient("http://localhost:3000");
28+
var handler = new HttpClientHandler();
29+
var httpClient = new HttpClient(
30+
!string.IsNullOrWhiteSpace(options.BasicAuthUsername) && !string.IsNullOrWhiteSpace(options.BasicAuthPassword)
31+
? new BasicAuthHandler(options.BasicAuthUsername, options.BasicAuthPassword) { InnerHandler = handler }
32+
: handler
33+
)
34+
{ BaseAddress = options.ServiceUrl };
35+
36+
var sharpClient = new GotenbergSharpClient(httpClient);
1837

1938
var items = Directory.GetFiles(sourcePath, "*.pdf", SearchOption.TopDirectoryOnly)
2039
.Select(p => new { Info = new FileInfo(p), Path = p })

examples/PdfMerge/Program.cs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,36 @@
11
using Gotenberg.Sharp.API.Client;
22
using Gotenberg.Sharp.API.Client.Domain.Builders;
33
using Gotenberg.Sharp.API.Client.Domain.Builders.Faceted;
4+
using Gotenberg.Sharp.API.Client.Domain.Settings;
5+
using Gotenberg.Sharp.API.Client.Infrastructure.Pipeline;
6+
using Microsoft.Extensions.Configuration;
7+
8+
var config = new ConfigurationBuilder()
9+
.SetBasePath(AppContext.BaseDirectory)
10+
.AddJsonFile("appsettings.json")
11+
.Build();
12+
13+
var options = new GotenbergSharpClientOptions();
14+
config.GetSection(nameof(GotenbergSharpClient)).Bind(options);
415

516
var sourcePath = args.Length > 0 ? args[0] : Path.Combine(Directory.GetCurrentDirectory(), "pdfs");
617
var destinationPath = args.Length > 1 ? args[1] : Path.Combine(Directory.GetCurrentDirectory(), "output");
718
Directory.CreateDirectory(destinationPath);
819

9-
var result = await DoMerge(sourcePath, destinationPath);
20+
var result = await DoMerge(sourcePath, destinationPath, options);
1021
Console.WriteLine($"Merged PDF created: {result}");
1122

12-
static async Task<string> DoMerge(string sourcePath, string destinationPath)
23+
static async Task<string> DoMerge(string sourcePath, string destinationPath, GotenbergSharpClientOptions options)
1324
{
14-
var sharpClient = new GotenbergSharpClient("http://localhost:3000");
25+
var handler = new HttpClientHandler();
26+
var httpClient = new HttpClient(
27+
!string.IsNullOrWhiteSpace(options.BasicAuthUsername) && !string.IsNullOrWhiteSpace(options.BasicAuthPassword)
28+
? new BasicAuthHandler(options.BasicAuthUsername, options.BasicAuthPassword) { InnerHandler = handler }
29+
: handler
30+
)
31+
{ BaseAddress = options.ServiceUrl };
32+
33+
var sharpClient = new GotenbergSharpClient(httpClient);
1534

1635
var items = Directory.GetFiles(sourcePath, "*.pdf", SearchOption.TopDirectoryOnly)
1736
.Select(p => new { Info = new FileInfo(p), Path = p })

examples/README.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,25 @@ This directory contains console application examples demonstrating various featu
44

55
## Prerequisites
66

7-
1. **Gotenberg Server**: You need a running Gotenberg instance. See the [main README](../README.md) for setup instructions.
7+
1. **Gotenberg Server**: You need a running Gotenberg instance with basic authentication. See the [main README](../README.md) for setup instructions.
8+
9+
The examples are pre-configured to use the docker-compose setup with basic auth:
10+
```bash
11+
docker-compose -f docker/docker-compose-basic-auth.yml up -d
12+
```
813

914
2. **.NET 8.0 SDK**: All examples target .NET 8.0.
1015

1116
## Configuration
1217

1318
All examples share a common configuration file: `appsettings.json`
1419

15-
You can modify the Gotenberg service URL and retry policy settings in this file.
20+
The default configuration includes:
21+
- **Service URL**: `http://localhost:3000`
22+
- **Basic Auth**: Username `testuser`, Password `testpass` (matching docker-compose-basic-auth.yml)
23+
- **Retry Policy**: Enabled with 4 retries and exponential backoff
24+
25+
You can modify these settings in `appsettings.json` or update the credentials directly in the example code.
1626

1727
## Examples
1828

examples/UrlConvert/Program.cs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
using Gotenberg.Sharp.API.Client;
22
using Gotenberg.Sharp.API.Client.Domain.Builders;
33
using Gotenberg.Sharp.API.Client.Domain.Builders.Faceted;
4+
using Gotenberg.Sharp.API.Client.Domain.Settings;
5+
using Gotenberg.Sharp.API.Client.Infrastructure.Pipeline;
6+
using Microsoft.Extensions.Configuration;
7+
8+
var config = new ConfigurationBuilder()
9+
.SetBasePath(AppContext.BaseDirectory)
10+
.AddJsonFile("appsettings.json")
11+
.Build();
12+
13+
var options = new GotenbergSharpClientOptions();
14+
config.GetSection(nameof(GotenbergSharpClient)).Bind(options);
415

516
var destinationPath = args.Length > 0 ? args[0] : Path.Combine(Directory.GetCurrentDirectory(), "output");
617
Directory.CreateDirectory(destinationPath);
@@ -9,12 +20,20 @@
920
var headerPath = Path.Combine(resourcePath, "UrlHeader.html");
1021
var footerPath = Path.Combine(resourcePath, "UrlFooter.html");
1122

12-
var path = await CreateFromUrl(destinationPath, headerPath, footerPath);
23+
var path = await CreateFromUrl(destinationPath, headerPath, footerPath, options);
1324
Console.WriteLine($"PDF created from URL: {path}");
1425

15-
static async Task<string> CreateFromUrl(string destinationPath, string headerPath, string footerPath)
26+
static async Task<string> CreateFromUrl(string destinationPath, string headerPath, string footerPath, GotenbergSharpClientOptions options)
1627
{
17-
var sharpClient = new GotenbergSharpClient("http://localhost:3000");
28+
var handler = new HttpClientHandler();
29+
var httpClient = new HttpClient(
30+
!string.IsNullOrWhiteSpace(options.BasicAuthUsername) && !string.IsNullOrWhiteSpace(options.BasicAuthPassword)
31+
? new BasicAuthHandler(options.BasicAuthUsername, options.BasicAuthPassword) { InnerHandler = handler }
32+
: handler
33+
)
34+
{ BaseAddress = options.ServiceUrl };
35+
36+
var sharpClient = new GotenbergSharpClient(httpClient);
1837

1938
var builder = new UrlRequestBuilder()
2039
.SetUrl("https://www.cnn.com")

examples/UrlsToMergedPdf/Program.cs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,28 @@
22
using Gotenberg.Sharp.API.Client.Domain.Builders;
33
using Gotenberg.Sharp.API.Client.Domain.Builders.Faceted;
44
using Gotenberg.Sharp.API.Client.Domain.Requests;
5+
using Gotenberg.Sharp.API.Client.Domain.Settings;
6+
using Gotenberg.Sharp.API.Client.Infrastructure.Pipeline;
7+
using Microsoft.Extensions.Configuration;
58

69
// NOTE: You need to increase gotenberg api's timeout for this to work
710
// by passing --api-timeout=1800s when running the container.
811

12+
var config = new ConfigurationBuilder()
13+
.SetBasePath(AppContext.BaseDirectory)
14+
.AddJsonFile("appsettings.json")
15+
.Build();
16+
17+
var options = new GotenbergSharpClientOptions();
18+
config.GetSection(nameof(GotenbergSharpClient)).Bind(options);
19+
920
var destinationDirectory = args.Length > 0 ? args[0] : Path.Combine(Directory.GetCurrentDirectory(), "output");
1021
Directory.CreateDirectory(destinationDirectory);
1122

12-
var path = await CreateWorldNewsSummary(destinationDirectory);
23+
var path = await CreateWorldNewsSummary(destinationDirectory, options);
1324
Console.WriteLine($"News summary PDF created: {path}");
1425

15-
static async Task<string> CreateWorldNewsSummary(string destinationDirectory)
26+
static async Task<string> CreateWorldNewsSummary(string destinationDirectory, GotenbergSharpClientOptions options)
1627
{
1728
var sites = new[]
1829
{
@@ -31,7 +42,7 @@ static async Task<string> CreateWorldNewsSummary(string destinationDirectory)
3142
var builders = CreateRequestBuilders(sites);
3243
var requests = builders.Select(b => b.Build());
3344

34-
return await ExecuteRequestsAndMerge(requests, destinationDirectory);
45+
return await ExecuteRequestsAndMerge(requests, destinationDirectory, options);
3546
}
3647

3748
static IEnumerable<UrlRequestBuilder> CreateRequestBuilders(IEnumerable<Uri> uris)
@@ -53,11 +64,16 @@ static IEnumerable<UrlRequestBuilder> CreateRequestBuilders(IEnumerable<Uri> uri
5364
}
5465
}
5566

56-
static async Task<string> ExecuteRequestsAndMerge(IEnumerable<UrlRequest> requests, string destinationDirectory)
67+
static async Task<string> ExecuteRequestsAndMerge(IEnumerable<UrlRequest> requests, string destinationDirectory, GotenbergSharpClientOptions options)
5768
{
58-
var innerClient = new HttpClient
69+
var handler = new HttpClientHandler();
70+
var innerClient = new HttpClient(
71+
!string.IsNullOrWhiteSpace(options.BasicAuthUsername) && !string.IsNullOrWhiteSpace(options.BasicAuthPassword)
72+
? new BasicAuthHandler(options.BasicAuthUsername, options.BasicAuthPassword) { InnerHandler = handler }
73+
: handler
74+
)
5975
{
60-
BaseAddress = new Uri("http://localhost:3000"),
76+
BaseAddress = options.ServiceUrl,
6177
Timeout = TimeSpan.FromMinutes(7)
6278
};
6379

examples/Webhook/Program.cs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,42 @@
11
using Gotenberg.Sharp.API.Client;
22
using Gotenberg.Sharp.API.Client.Domain.Builders;
33
using Gotenberg.Sharp.API.Client.Domain.Builders.Faceted;
4+
using Gotenberg.Sharp.API.Client.Domain.Settings;
5+
using Gotenberg.Sharp.API.Client.Infrastructure.Pipeline;
6+
using Microsoft.Extensions.Configuration;
47

58
// For this to work you need an API running on localhost:5000 with an endpoint to receive the webhook
69

10+
var config = new ConfigurationBuilder()
11+
.SetBasePath(AppContext.BaseDirectory)
12+
.AddJsonFile("appsettings.json")
13+
.Build();
14+
15+
var options = new GotenbergSharpClientOptions();
16+
config.GetSection(nameof(GotenbergSharpClient)).Bind(options);
17+
718
var resourcePath = Path.Combine(AppContext.BaseDirectory, "resources", "Html");
819
var footerPath = Path.Combine(resourcePath, "UrlHeader.html");
920
var headerPath = Path.Combine(resourcePath, "UrlFooter.html");
1021

1122
Console.WriteLine($"Header: {headerPath}");
1223
Console.WriteLine($"Footer: {footerPath}");
1324

14-
await CreateFromUrl(headerPath, footerPath);
25+
await CreateFromUrl(headerPath, footerPath, options);
1526

1627
Console.WriteLine("Webhook request sent...");
1728

18-
static async Task CreateFromUrl(string headerPath, string footerPath)
29+
static async Task CreateFromUrl(string headerPath, string footerPath, GotenbergSharpClientOptions options)
1930
{
20-
var sharpClient = new GotenbergSharpClient("http://localhost:3000");
31+
var handler = new HttpClientHandler();
32+
var httpClient = new HttpClient(
33+
!string.IsNullOrWhiteSpace(options.BasicAuthUsername) && !string.IsNullOrWhiteSpace(options.BasicAuthPassword)
34+
? new BasicAuthHandler(options.BasicAuthUsername, options.BasicAuthPassword) { InnerHandler = handler }
35+
: handler
36+
)
37+
{ BaseAddress = options.ServiceUrl };
38+
39+
var sharpClient = new GotenbergSharpClient(httpClient);
2140

2241
var builder = new UrlRequestBuilder()
2342
.SetUrl("https://www.newyorker.com")

examples/appsettings.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
"GotenbergSharpClient": {
33
"ServiceUrl": "http://localhost:3000",
44
"HealthCheckUrl": "http://localhost:3000/health",
5+
"BasicAuthUsername": "testuser",
6+
"BasicAuthPassword": "testpass",
57
"RetryPolicy": {
68
"Enabled": true,
79
"RetryCount": 4,

0 commit comments

Comments
 (0)