{"id":15011642,"url":"https://github.com/changemakerstudios/gotenbergsharpapiclient","last_synced_at":"2025-05-15T13:07:00.267Z","repository":{"id":36112813,"uuid":"183506022","full_name":"ChangemakerStudios/GotenbergSharpApiClient","owner":"ChangemakerStudios","description":".NET C# Client for the Gotenberg API","archived":false,"fork":false,"pushed_at":"2025-03-25T21:29:22.000Z","size":2178,"stargazers_count":146,"open_issues_count":5,"forks_count":21,"subscribers_count":9,"default_branch":"develop","last_synced_at":"2025-05-15T13:06:56.829Z","etag":null,"topics":["api-client","async","csharp","fluent","gotenberg","html-to-pdf","httpclientfactory","netstandard","nuget","nuget-package","pdf","pdf-converter","pdf-generation","pdf-merge","polly-resilience","typed-clients","url-to-pdf","word-to-pdf"],"latest_commit_sha":null,"homepage":"","language":"Rich Text Format","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ChangemakerStudios.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2019-04-25T20:34:55.000Z","updated_at":"2025-04-27T23:24:46.000Z","dependencies_parsed_at":"2024-04-17T04:22:22.204Z","dependency_job_id":"7fca91f8-6844-4463-bf4b-0785403a7604","html_url":"https://github.com/ChangemakerStudios/GotenbergSharpApiClient","commit_stats":{"total_commits":324,"total_committers":11,"mean_commits":"29.454545454545453","dds":0.6080246913580247,"last_synced_commit":"a2a9a8cf849869a5996d2cf2a0abb211372e6a55"},"previous_names":[],"tags_count":29,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ChangemakerStudios%2FGotenbergSharpApiClient","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ChangemakerStudios%2FGotenbergSharpApiClient/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ChangemakerStudios%2FGotenbergSharpApiClient/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ChangemakerStudios%2FGotenbergSharpApiClient/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ChangemakerStudios","download_url":"https://codeload.github.com/ChangemakerStudios/GotenbergSharpApiClient/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254346624,"owners_count":22055808,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api-client","async","csharp","fluent","gotenberg","html-to-pdf","httpclientfactory","netstandard","nuget","nuget-package","pdf","pdf-converter","pdf-generation","pdf-merge","polly-resilience","typed-clients","url-to-pdf","word-to-pdf"],"created_at":"2024-09-24T19:41:22.978Z","updated_at":"2025-05-15T13:07:00.169Z","avatar_url":"https://github.com/ChangemakerStudios.png","language":"Rich Text Format","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ![gotenberg icon](https://raw.githubusercontent.com/ChangemakerStudios/GotenbergSharpApiClient/develop/lib/Resources/gotenbergSharpClient.PNG) Gotenberg.Sharp.Api.Client\n\n[![NuGet version](https://badge.fury.io/nu/Gotenberg.Sharp.Api.Client.svg)](https://badge.fury.io/nu/Gotenberg.Sharp.Api.Client)\n[![Downloads](https://img.shields.io/nuget/dt/Gotenberg.Sharp.API.Client.svg?logo=nuget\u0026color=purple)](https://www.nuget.org/packages/Gotenberg.Sharp.API.Client) \n![Build status](https://github.com/ChangemakerStudios/GotenbergSharpApiClient/actions/workflows/deploy.yml/badge.svg)\n\n⭐ For Gotenberg v7 \u0026 v8 ⭐\n\n.NET C# Client for interacting with the [Gotenberg](https://gotenberg.dev/) v7 \u0026 v8 micro-service's API. [Gotenberg](https://github.com/gotenberg/gotenberg) is a [Docker-powered stateless API](https://hub.docker.com/r/gotenberg/gotenberg/) for converting \u0026 merging HTML, Markdown and Office documents to PDF. The client supports a configurable [Polly](http://www.thepollyproject.org/) **retry policy** with exponential backoff for handling transient exceptions.\n\n# Getting Started\n*Pull the image from dockerhub.com*\n```powershell\n\u003e docker pull gotenberg/gotenberg:latest\n```\n*Create \u0026 start a container*\n```powershell\ndocker run --name gotenbee8x --rm -p 3000:3000 gotenberg/gotenberg:latest gotenberg --api-timeout=1800s --log-level=debug\n```\n\n# .NET Core Project Setup\n*Install nuget package into your project*\n```powershell\nPM\u003e Install-Package Gotenberg.Sharp.Api.Client\n```\n\n*Note: Use v1.x nugets for Gotenberg v6.*\n\n## AppSettings\n```json\n  \"GotenbergSharpClient\": {\n    \"ServiceUrl\": \"http://localhost:3000\",\n    \"HealthCheckUrl\": \"http://localhost:3000/health\",\n    \"RetryPolicy\": {\n      \"Enabled\": true,\n      \"RetryCount\": 4,\n      \"BackoffPower\": 1.5,\n      \"LoggingEnabled\": true\n    }\n  }\n```\n\n## Configure Services In Startup.cs\n```csharp\npublic void ConfigureServices(IServiceCollection services)\n{\n\t.....\n    services.AddOptions\u003cGotenbergSharpClientOptions\u003e()\n\t        .Bind(Configuration.GetSection(nameof(GotenbergSharpClient)));\n    services.AddGotenbergSharpClient();\n\t.....    \n}\n\n```\n# Using GotenbergSharpClient\n*See the [linqPad folder](linqpad/)* for complete examples. \n\n### Html To Pdf \n*With embedded assets:*\n\n```csharp\n [HttpGet]\n public async Task\u003cActionResult\u003e HtmlToPdf([FromServices] GotenbergSharpClient sharpClient)\n {\n     var builder = new HtmlRequestBuilder()\n         .AddDocument(doc =\u003e \n             doc.SetBody(GetBody()).SetFooter(GetFooter())\n         ).WithPageProperties(pp =\u003e\n         {\n             pp.SetPaperSize(PaperSizes.A3)\n                 .SetMargins(Margins.None)\n                 .SetScale(.99);\n         }).WithAsyncAssets(async assets =\u003e assets.AddItem(\"some-image.jpg\", await GetImageBytes()));\n\n     var req = await builder.BuildAsync();\n\n     var result = await sharpClient.HtmlToPdfAsync(req);\n\n     return this.File(result, \"application/pdf\", \"gotenbergFromHtml.pdf\");\n }\n```\n\n### Url To Pdf\n*Url to Pdf with custom page range, header \u0026 footer:*\n\n```csharp\npublic async Task\u003cStream\u003e CreateFromUrl(string headerPath, string footerPath)\n{\n\tvar builder = new UrlRequestBuilder()\n\t\t.SetUrl(\"https://www.cnn.com\")\n\t\t.ConfigureRequest(config =\u003e\n\t\t{\n\t\t\tconfig.SetPageRanges(\"1-2\");\n\t\t})\n\t\t.AddAsyncHeaderFooter(async\n\t\t\tdoc =\u003e doc.SetHeader(await File.ReadAllTextAsync(headerPath))\n\t\t\t\t  .SetFooter(await File.ReadAllBytesAsync(footerPath)\n\t\t)).WithPageProperties(pp =\u003e\n\t\t{\n\t\t\tpp.SetPaperSize(PaperSizes.A4)\n\t\t\t .SetMargins(Margins.None)\n\t\t\t .SetScale(.90)\n\t\t\t .SetLandscape();\n\t\t});\n\n\tvar request = await builder.BuildAsync();\n\treturn await _sharpClient.UrlToPdfAsync(request);\n}\n```\n## Merge Office Docs\n*Merges office documents and configures the request time-out:*\n\n```csharp\npublic async Task\u003cStream\u003e DoOfficeMerge(string sourceDirectory)\n{\n\tvar builder = new MergeOfficeBuilder()\n\t\t.WithAsyncAssets(async a =\u003e a.AddItems(await GetDocsAsync(sourceDirectory)));\n\n\tvar request = await builder.BuildAsync();\n\treturn await _sharpClient.MergeOfficeDocsAsync(request);\n}\n```\n### Markdown to Pdf\n*Markdown to Pdf conversion with embedded assets:*\n\n```csharp\npublic async Task\u003cStream\u003e CreateFromMarkdown()\n{\n\tvar builder = new HtmlRequestBuilder()\n\t\t.AddAsyncDocument(async\n\t\t\tdoc =\u003e doc.SetHeader(await this.GetHeaderAsync())\n\t\t\t\t  .SetBody(await GetBodyAsync())\n\t\t\t\t  .SetContainsMarkdown()\n\t\t\t\t  .SetFooter(await GetFooterAsync())\n\t\t).WithPageProperties(pp =\u003e\n\t\t{\n\t\t\tpp.UseChromeDefaults().SetLandscape().SetScale(.90);\n\t\t}).WithAsyncAssets(async\n\t\t\ta =\u003e a.AddItems(await GetMarkdownAssets())\n\t\t);\n\n\tvar request = await builder.BuildAsync();\n\treturn await _sharpClient.HtmlToPdfAsync(request);\n}\n```\n### Webhook\n*All request types support webhooks*\n\n```csharp\n public async Task SendUrlToWebhookEndpoint(string headerPath, string footerPath)\n {\n     var builder = new UrlRequestBuilder()\n         .SetUrl(\"https://www.cnn.com\")\n         .ConfigureRequest(reqBuilder =\u003e\n         {\n             reqBuilder.AddWebhook(hook =\u003e\n                 {\n                     hook.SetUrl(\"http://host.docker.internal:5000/api/your/webhookReceiver\")\n                         .SetErrorUrl(\"http://host.docker.internal:5000/api/your/webhookReceiver/error\")\n                         .AddExtraHeader(\"custom-header\", \"value\");\n                 })\n                 .SetPageRanges(\"1-2\");\n         })\n         .AddAsyncHeaderFooter(async\n             b =\u003e b.SetHeader(await System.IO.File.ReadAllTextAsync(headerPath))\n                   .SetFooter(await System.IO.File.ReadAllBytesAsync(footerPath))\n         ).WithPageProperties(pp =\u003e\n         {\n             pp.SetPaperSize(PaperSizes.A4)\n                 .SetMargins(Margins.None)\n                 .SetScale(.90)\n                 .SetLandscape();\n         });\n\n     var request = await builder.BuildAsync();\n\n     await _sharpClient.FireWebhookAndForgetAsync(request);\n }\n\n```\n### Merge 15 Urls to one pdf\n*Builds a 30 page pdf by merging the front two pages of 15 news sites. Takes about a minute to complete*\n\n```csharp\npublic async Task\u003cStream\u003e CreateWorldNewsSummary()\n{\n    var sites = new[]\n        {\n            \"https://www.nytimes.com\", \"https://www.axios.com/\", \"https://www.csmonitor.com\",\n            \"https://www.wsj.com\", \"https://www.usatoday.com\", \"https://www.irishtimes.com\",\n            \"https://www.lemonde.fr\", \"https://calgaryherald.com\", \"https://www.bbc.com/news/uk\",\n            \"https://www.thehindu.com\", \"https://www.theaustralian.com.au\",\n            \"https://www.welt.de\", \"https://www.cankaoxiaoxi.com\",\n            \"https://www.novinky.cz\", \"https://www.elobservador.com.uy\"\n        }\n        .Select(u =\u003e new Uri(u));\n\n    var builders = CreateBuilders(sites);\n    var requests = builders.Select(b =\u003e b.Build());\n\n    return await ExecuteRequestsAndMerge(requests);\n}\n\nIEnumerable\u003cUrlRequestBuilder\u003e CreateBuilders(IEnumerable\u003cUri\u003e uris)\n{\n    foreach (var uri in uris)\n    {\n        yield return new UrlRequestBuilder()\n            .SetUrl(uri)\n            .ConfigureRequest(req =\u003e { req.SetPageRanges(\"1-2\"); })\n            .AddHeaderFooter(docBuilder =\u003e\n            {\n                docBuilder.SetHeader(GetHeadFoot(uri.Host.Replace(\"www.\", string.Empty).ToUpper()))\n                   .SetFooter(GetHeadFoot(uri.ToString()));\n            })\n            .WithPageProperties(pp =\u003e\n            {\n                pp.UseChromeDefaults()\n                    .SetScale(.90)\n                    .SetLandscape()\n                    .SetMarginLeft(.5)\n                    .SetMarginRight(.5);\n            });\n    }\n\n    static string GetHeadFoot(string heading)\n        =\u003e \"\u003chtml\u003e\u003chead\u003e \u003cstyle\u003e body { font-size: 8rem; } h1 { margin-left: auto; margin-right: auto; } \u003c/style\u003e\u003c/head\u003e\u003cbody\u003e\u003ch1\u003e\" +\n           heading + \"\u003c/h1\u003e\u003c/body\u003e\u003c/html\u003e\";\n}\n\nasync Task\u003cStream\u003e ExecuteRequestsAndMerge(IEnumerable\u003cUrlRequest\u003e requests)\n{\n    var tasks = requests.Select(r =\u003e _sharpClient.UrlToPdfAsync(r));\n    var results = await Task.WhenAll(tasks);\n\n    var mergeBuilder = new MergeBuilder()\n        .WithAssets(b =\u003e { \n            b.AddItems(results.Select((r, i) =\u003e KeyValuePair.Create($\"{i}.pdf\", r))); \n        });\n\n    var request = mergeBuilder.Build();\n    return await _sharpClient.MergePdfsAsync(request);\n}\n``` \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchangemakerstudios%2Fgotenbergsharpapiclient","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchangemakerstudios%2Fgotenbergsharpapiclient","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchangemakerstudios%2Fgotenbergsharpapiclient/lists"}