Skip to content

Commit f9182ef

Browse files
authored
Merge pull request #1 from PDFinch/feature/M-m-m-multipost
Add `IPdfClient.GenerateMergedPdfFromHtmlAsync(IEnumerable<PdfRequest> pdfRequests)`
2 parents 22d6382 + 1b03507 commit f9182ef

File tree

14 files changed

+410
-154
lines changed

14 files changed

+410
-154
lines changed

README.md

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ This repository contains the C# source code for the .NET clients to the PDFinch
55

66
You need to have an account with enough credits and an active set of API keys. You can [register an account or log in here](https://www.pdfinch.com/account/login).
77
# Basic usage
8-
Our API currently supports one thing: generating PDFs from HTML. You can do so by calling `IPdfClient.GeneratePdfFromHtmlAsync()`:
8+
Our API currently supports one thing: generating PDFs from HTML, with some variations. The most simple way is available by calling `IPdfClient.GeneratePdfFromHtmlAsync()`:
99

1010
```C#
1111
IPdfClient pdfClient = ... // see chapter "Obtaining an IPdfClient" below
@@ -31,8 +31,29 @@ else
3131
throw new InvalidOperationException($"Error generating PDF: {pdfResult.StatusMessage}");
3232
}
3333
```
34+
35+
You can also merge multiple blocks of HTML:
36+
37+
```C#
38+
PdfResult<Stream> pdfResult = await pdfClient.GenerateMergedPdfFromHtmlAsync(new []
39+
{
40+
new PdfRequest("<h1>Your-Html-String</h1>")
41+
{
42+
MarginBottom = 10,
43+
MarginTop = 10,
44+
MarginLeft = 10,
45+
MarginRight = 10,
46+
Landscape = false,
47+
GrayScale = false,
48+
}, new PdfRequest("<h1>Your-Second-Html</h1>")
49+
{
50+
Landscape = true,
51+
},
52+
});
53+
```
54+
3455
# Handling responses
35-
`PDFinch.Client.Common.IPdfClient.GeneratePdfFromHtmlAsync()` returns a `Task<PdfResult<Stream>>`. This means the call should be `await`ed, and the return value must be checked for success.
56+
The `PDFinch.Client.Common.IPdfClient.Generate...Async()` methods return a `Task<PdfResult<Stream>>`. This means the call should be `await`ed, and the return value must be checked for success.
3657

3758
If `PdfResult<Stream>.Success` is `false`, `.Data` will be `null` and `.StatusMessage` will contain a machine-readable (JSON) error message returned by the API.
3859

TestClients/PDFinch.TestClient.ASPNET60/Pages/Index.cshtml.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public IndexModel(IPdfClientFactory pdfClientFactory/*, IPdfClient pdfClient*/)
1818

1919
public async Task<IActionResult> OnGet()
2020
{
21-
// TODO: separate page for DI
21+
// TODO: separate page for single-client DI
2222
//// Typed client (IPdfClient in constructor)
2323
//var pdfResult = await _pdfClient.GeneratePdfFromHtmlAsync($"<h1>Typed IPdfClient</h1><p>Generated on {DateTime.Now:F}.");
2424

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
@page
2+
@model PDFinch.TestClient.ASPNET60.Pages.MergeModel
3+
@{
4+
}
5+
6+
<h1>Error</h1>
7+
8+
<p>If you see this, an error occurred. The error:</p>
9+
10+
<pre>@Model.StatusMessage</pre>
11+
12+
<p>Hit reload to try again.</p>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
using Microsoft.AspNetCore.Mvc.RazorPages;
3+
using PDFinch.Client.Common;
4+
5+
namespace PDFinch.TestClient.ASPNET60.Pages
6+
{
7+
public class MergeModel : PageModel
8+
{
9+
private readonly IPdfClientFactory _pdfClientFactory;
10+
11+
public MergeModel(IPdfClientFactory pdfClientFactory)
12+
{
13+
_pdfClientFactory = pdfClientFactory;
14+
}
15+
16+
public string? StatusMessage { get; private set; }
17+
18+
public async Task<IActionResult> OnGet()
19+
{
20+
// Use the factory to request a named client:
21+
var client = _pdfClientFactory.GetPdfClient("Develop");
22+
23+
var requests = new List<PdfRequest>
24+
{
25+
new ($"<h1>Portrait</h1><p>Generated on {DateTime.Now:F}.</p>")
26+
{
27+
Landscape = false,
28+
MarginTop = 42,
29+
MarginBottom = 41,
30+
MarginLeft = 40,
31+
MarginRight = 39,
32+
},
33+
new ($"<h1>Landscape</h1><p>Generated on {DateTime.Now:F}.</p>")
34+
{
35+
Landscape = true,
36+
GrayScale = true,
37+
38+
},
39+
new ($"<h1>Portrait</h1><h2>(again)</h2><p>Generated on {DateTime.Now:F}.</p>")
40+
{
41+
Landscape = false
42+
},
43+
};
44+
45+
var pdfResult = await client.GenerateMergedPdfFromHtmlAsync(requests);
46+
47+
// Returning that PDF when it succeeded.
48+
if (pdfResult.Success)
49+
{
50+
return File(pdfResult.Data, "application/pdf");
51+
}
52+
53+
StatusMessage = pdfResult.StatusMessage;
54+
55+
return Page();
56+
}
57+
}
58+
}

src/PDFinch.Client.Common/HttpPdfClient.cs

Lines changed: 52 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.IO;
34
using System.Net;
45
using System.Net.Http;
@@ -32,36 +33,57 @@ protected HttpPdfClient(PdfClientOptions pdfinchOptions)
3233
/// </summary>
3334
protected abstract Task<HttpClient> AuthenticateClientAsync(HttpRequestMessage httpRequestMessage);
3435

36+
/// <inheritdoc/>
37+
public Task<PdfResult<Stream>> GeneratePdfFromHtmlAsync(PdfRequest pdfRequest) => GeneratePdfFromHtmlAsync(pdfRequest.Html, pdfRequest);
38+
3539
/// <inheritdoc/>
3640
public async Task<PdfResult<Stream>> GeneratePdfFromHtmlAsync(string html, PdfOptions? options = null)
3741
{
38-
HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, Resources.CreatePdfEndpoint + options?.ToQueryString());
39-
40-
httpRequestMessage.Content = new StringContent(html, Encoding.UTF8, "text/html");
42+
HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, Resources.CreatePdfEndpoint + options?.ToQueryString())
43+
{
44+
Content = new StringContent(html, Encoding.UTF8, "text/html")
45+
};
4146

42-
HttpClient? autenticatedHttpClient;
47+
return await ExecuteRequestAsync(httpRequestMessage);
48+
}
4349

44-
try
45-
{
46-
autenticatedHttpClient = await AuthenticateClientAsync(httpRequestMessage);
47-
}
48-
catch (Exception ex)
49-
{
50-
return new PdfResult<Stream>(otherError: true, statusMessage: GetExceptionJson(ex));
51-
}
50+
/// <inheritdoc/>
51+
public async Task<PdfResult<Stream>> GenerateMergedPdfFromHtmlAsync(IEnumerable<PdfRequest> pdfRequests)
52+
{
53+
var formContent = new MultipartFormDataContent();
5254

53-
// ReSharper disable once ConstantConditionalAccessQualifier - implementor can return null.
54-
if (autenticatedHttpClient?.BaseAddress == null)
55+
var i = 0;
56+
57+
foreach (var request in pdfRequests)
5558
{
56-
const string errorMessage = $"{nameof(AuthenticateClientAsync)}() must return an {nameof(HttpClient)} with its {nameof(HttpClient.BaseAddress)} set";
59+
formContent.Add(new StringContent(request.Html, Encoding.UTF8, "text/html"), $"d[{i}].body");
5760

58-
return new PdfResult<Stream>(otherError: true, statusMessage: PdfResult<Stream>.JsonStatus(errorMessage));
61+
formContent.Add(new StringContent(request.Landscape.ToString()), $"d[{i}].landscape");
62+
formContent.Add(new StringContent(request.GrayScale.ToString()), $"d[{i}].grayscale");
63+
formContent.Add(new StringContent(request.MarginLeft.ToString()), $"d[{i}].marginleft");
64+
formContent.Add(new StringContent(request.MarginRight.ToString()), $"d[{i}].marginright");
65+
formContent.Add(new StringContent(request.MarginTop.ToString()), $"d[{i}].margintop");
66+
formContent.Add(new StringContent(request.MarginBottom.ToString()), $"d[{i}].marginbottom");
67+
68+
i++;
5969
}
6070

71+
HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, Resources.MergePdfEndpoint)
72+
{
73+
Content = formContent
74+
};
75+
76+
return await ExecuteRequestAsync(httpRequestMessage);
77+
}
78+
79+
private async Task<PdfResult<Stream>> ExecuteRequestAsync(HttpRequestMessage httpRequestMessage)
80+
{
6181
try
6282
{
83+
var autenticatedHttpClient = await AuthenticateClientImplAsync(httpRequestMessage);
84+
6385
var response = await autenticatedHttpClient.SendAsync(httpRequestMessage);
64-
86+
6587
if (response.IsSuccessStatusCode)
6688
{
6789
var stream = await response.Content.ReadAsStreamAsync();
@@ -85,6 +107,19 @@ public async Task<PdfResult<Stream>> GeneratePdfFromHtmlAsync(string html, PdfOp
85107
}
86108
}
87109

110+
private async Task<HttpClient> AuthenticateClientImplAsync(HttpRequestMessage httpRequestMessage)
111+
{
112+
var autenticatedHttpClient = await AuthenticateClientAsync(httpRequestMessage);
113+
114+
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract - implementor can return null.
115+
if (autenticatedHttpClient?.BaseAddress == null)
116+
{
117+
throw new InvalidOperationException($"{nameof(AuthenticateClientAsync)}() must return an {nameof(HttpClient)} with its {nameof(HttpClient.BaseAddress)} set");
118+
}
119+
120+
return autenticatedHttpClient;
121+
}
122+
88123
private static string GetExceptionJson(Exception ex)
89124
{
90125
return JsonSerializer.Serialize(new

src/PDFinch.Client.Common/IPdfClient.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.IO;
1+
using System.Collections.Generic;
2+
using System.IO;
23
using System.Threading.Tasks;
34

45
namespace PDFinch.Client.Common
@@ -22,5 +23,15 @@ public interface IPdfClient
2223
/// Generate a PDF using the given HTML and options.
2324
/// </summary>
2425
Task<PdfResult<Stream>> GeneratePdfFromHtmlAsync(string html, PdfOptions? options = null);
26+
27+
/// <summary>
28+
/// Generate a PDF using the given HTML and options.
29+
/// </summary>
30+
Task<PdfResult<Stream>> GeneratePdfFromHtmlAsync(PdfRequest pdfRequest);
31+
32+
/// <summary>
33+
/// Generate a PDF using the given HTML and options.
34+
/// </summary>
35+
Task<PdfResult<Stream>> GenerateMergedPdfFromHtmlAsync(IEnumerable<PdfRequest> pdfRequests);
2536
}
2637
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
namespace PDFinch.Client.Common
2+
{
3+
/// <summary>
4+
/// Represents a request to generate a PDF from the given HTML and <see cref="PdfOptions"/>.
5+
/// </summary>
6+
public class PdfRequest : PdfOptions
7+
{
8+
/// <summary>
9+
/// The HTML to generate a PDF from.
10+
/// </summary>
11+
public string Html { get; }
12+
13+
/// <summary>
14+
/// Instantiate a request with the given <paramref name="html"/>.
15+
/// </summary>
16+
public PdfRequest(string html)
17+
{
18+
Html = html;
19+
}
20+
}
21+
}

src/PDFinch.Client.Common/Resources.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ public static class Resources
2727
/// </summary>
2828
public const string CreatePdfEndpoint = "pdf/create";
2929

30+
/// <summary>
31+
/// Endpoint for merging multiple HTML requests to a single PDF.
32+
/// </summary>
33+
public const string MergePdfEndpoint = "pdf/merge";
34+
3035
/// <summary>
3136
/// HttpClient name for authentication clients.
3237
/// </summary>

test/PDFinch.Client.Common.Tests/BadPdfClient.cs

Lines changed: 0 additions & 16 deletions
This file was deleted.

test/PDFinch.Client.Common.Tests/HttpClientExtensionTests.cs renamed to test/PDFinch.Client.Common.Tests/HttpClientTokenTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
namespace PDFinch.Client.Common.Tests
99
{
10-
public class HttpClientExtensionTests
10+
public class HttpClientTokenTests
1111
{
1212
[Test]
1313
public async Task GetTokenAsync_Parses_Json()

0 commit comments

Comments
 (0)