Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ This repository contains the C# source code for the .NET clients to the PDFinch

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).
# Basic usage
Our API currently supports one thing: generating PDFs from HTML. You can do so by calling `IPdfClient.GeneratePdfFromHtmlAsync()`:
Our API currently supports one thing: generating PDFs from HTML, with some variations. The most simple way is available by calling `IPdfClient.GeneratePdfFromHtmlAsync()`:

```C#
IPdfClient pdfClient = ... // see chapter "Obtaining an IPdfClient" below
Expand All @@ -31,8 +31,29 @@ else
throw new InvalidOperationException($"Error generating PDF: {pdfResult.StatusMessage}");
}
```

You can also merge multiple blocks of HTML:

```C#
PdfResult<Stream> pdfResult = await pdfClient.GenerateMergedPdfFromHtmlAsync(new []
{
new PdfRequest("<h1>Your-Html-String</h1>")
{
MarginBottom = 10,
MarginTop = 10,
MarginLeft = 10,
MarginRight = 10,
Landscape = false,
GrayScale = false,
}, new PdfRequest("<h1>Your-Second-Html</h1>")
{
Landscape = true,
},
});
```

# Handling responses
`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.
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.

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public IndexModel(IPdfClientFactory pdfClientFactory/*, IPdfClient pdfClient*/)

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

Expand Down
12 changes: 12 additions & 0 deletions TestClients/PDFinch.TestClient.ASPNET60/Pages/Merge.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@page
@model PDFinch.TestClient.ASPNET60.Pages.MergeModel
@{
}

<h1>Error</h1>

<p>If you see this, an error occurred. The error:</p>

<pre>@Model.StatusMessage</pre>

<p>Hit reload to try again.</p>
58 changes: 58 additions & 0 deletions TestClients/PDFinch.TestClient.ASPNET60/Pages/Merge.cshtml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using PDFinch.Client.Common;

namespace PDFinch.TestClient.ASPNET60.Pages
{
public class MergeModel : PageModel
{
private readonly IPdfClientFactory _pdfClientFactory;

public MergeModel(IPdfClientFactory pdfClientFactory)
{
_pdfClientFactory = pdfClientFactory;
}

public string? StatusMessage { get; private set; }

public async Task<IActionResult> OnGet()
{
// Use the factory to request a named client:
var client = _pdfClientFactory.GetPdfClient("Develop");

var requests = new List<PdfRequest>
{
new ($"<h1>Portrait</h1><p>Generated on {DateTime.Now:F}.</p>")
{
Landscape = false,
MarginTop = 42,
MarginBottom = 41,
MarginLeft = 40,
MarginRight = 39,
},
new ($"<h1>Landscape</h1><p>Generated on {DateTime.Now:F}.</p>")
{
Landscape = true,
GrayScale = true,

},
new ($"<h1>Portrait</h1><h2>(again)</h2><p>Generated on {DateTime.Now:F}.</p>")
{
Landscape = false
},
};

var pdfResult = await client.GenerateMergedPdfFromHtmlAsync(requests);

// Returning that PDF when it succeeded.
if (pdfResult.Success)
{
return File(pdfResult.Data, "application/pdf");
}

StatusMessage = pdfResult.StatusMessage;

return Page();
}
}
}
69 changes: 52 additions & 17 deletions src/PDFinch.Client.Common/HttpPdfClient.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
Expand Down Expand Up @@ -32,36 +33,57 @@ protected HttpPdfClient(PdfClientOptions pdfinchOptions)
/// </summary>
protected abstract Task<HttpClient> AuthenticateClientAsync(HttpRequestMessage httpRequestMessage);

/// <inheritdoc/>
public Task<PdfResult<Stream>> GeneratePdfFromHtmlAsync(PdfRequest pdfRequest) => GeneratePdfFromHtmlAsync(pdfRequest.Html, pdfRequest);

/// <inheritdoc/>
public async Task<PdfResult<Stream>> GeneratePdfFromHtmlAsync(string html, PdfOptions? options = null)
{
HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, Resources.CreatePdfEndpoint + options?.ToQueryString());

httpRequestMessage.Content = new StringContent(html, Encoding.UTF8, "text/html");
HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, Resources.CreatePdfEndpoint + options?.ToQueryString())
{
Content = new StringContent(html, Encoding.UTF8, "text/html")
};

HttpClient? autenticatedHttpClient;
return await ExecuteRequestAsync(httpRequestMessage);
}

try
{
autenticatedHttpClient = await AuthenticateClientAsync(httpRequestMessage);
}
catch (Exception ex)
{
return new PdfResult<Stream>(otherError: true, statusMessage: GetExceptionJson(ex));
}
/// <inheritdoc/>
public async Task<PdfResult<Stream>> GenerateMergedPdfFromHtmlAsync(IEnumerable<PdfRequest> pdfRequests)
{
var formContent = new MultipartFormDataContent();

// ReSharper disable once ConstantConditionalAccessQualifier - implementor can return null.
if (autenticatedHttpClient?.BaseAddress == null)
var i = 0;

foreach (var request in pdfRequests)
{
const string errorMessage = $"{nameof(AuthenticateClientAsync)}() must return an {nameof(HttpClient)} with its {nameof(HttpClient.BaseAddress)} set";
formContent.Add(new StringContent(request.Html, Encoding.UTF8, "text/html"), $"d[{i}].body");

return new PdfResult<Stream>(otherError: true, statusMessage: PdfResult<Stream>.JsonStatus(errorMessage));
formContent.Add(new StringContent(request.Landscape.ToString()), $"d[{i}].landscape");
formContent.Add(new StringContent(request.GrayScale.ToString()), $"d[{i}].grayscale");
formContent.Add(new StringContent(request.MarginLeft.ToString()), $"d[{i}].marginleft");
formContent.Add(new StringContent(request.MarginRight.ToString()), $"d[{i}].marginright");
formContent.Add(new StringContent(request.MarginTop.ToString()), $"d[{i}].margintop");
formContent.Add(new StringContent(request.MarginBottom.ToString()), $"d[{i}].marginbottom");

i++;
}

HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, Resources.MergePdfEndpoint)
{
Content = formContent
};

return await ExecuteRequestAsync(httpRequestMessage);
}

private async Task<PdfResult<Stream>> ExecuteRequestAsync(HttpRequestMessage httpRequestMessage)
{
try
{
var autenticatedHttpClient = await AuthenticateClientImplAsync(httpRequestMessage);

var response = await autenticatedHttpClient.SendAsync(httpRequestMessage);

if (response.IsSuccessStatusCode)
{
var stream = await response.Content.ReadAsStreamAsync();
Expand All @@ -85,6 +107,19 @@ public async Task<PdfResult<Stream>> GeneratePdfFromHtmlAsync(string html, PdfOp
}
}

private async Task<HttpClient> AuthenticateClientImplAsync(HttpRequestMessage httpRequestMessage)
{
var autenticatedHttpClient = await AuthenticateClientAsync(httpRequestMessage);

// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract - implementor can return null.
if (autenticatedHttpClient?.BaseAddress == null)
{
throw new InvalidOperationException($"{nameof(AuthenticateClientAsync)}() must return an {nameof(HttpClient)} with its {nameof(HttpClient.BaseAddress)} set");
}

return autenticatedHttpClient;
}

private static string GetExceptionJson(Exception ex)
{
return JsonSerializer.Serialize(new
Expand Down
13 changes: 12 additions & 1 deletion src/PDFinch.Client.Common/IPdfClient.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.IO;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;

namespace PDFinch.Client.Common
Expand All @@ -22,5 +23,15 @@ public interface IPdfClient
/// Generate a PDF using the given HTML and options.
/// </summary>
Task<PdfResult<Stream>> GeneratePdfFromHtmlAsync(string html, PdfOptions? options = null);

/// <summary>
/// Generate a PDF using the given HTML and options.
/// </summary>
Task<PdfResult<Stream>> GeneratePdfFromHtmlAsync(PdfRequest pdfRequest);

/// <summary>
/// Generate a PDF using the given HTML and options.
/// </summary>
Task<PdfResult<Stream>> GenerateMergedPdfFromHtmlAsync(IEnumerable<PdfRequest> pdfRequests);
}
}
21 changes: 21 additions & 0 deletions src/PDFinch.Client.Common/PdfRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace PDFinch.Client.Common
{
/// <summary>
/// Represents a request to generate a PDF from the given HTML and <see cref="PdfOptions"/>.
/// </summary>
public class PdfRequest : PdfOptions
{
/// <summary>
/// The HTML to generate a PDF from.
/// </summary>
public string Html { get; }

/// <summary>
/// Instantiate a request with the given <paramref name="html"/>.
/// </summary>
public PdfRequest(string html)
{
Html = html;
}
}
}
5 changes: 5 additions & 0 deletions src/PDFinch.Client.Common/Resources.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ public static class Resources
/// </summary>
public const string CreatePdfEndpoint = "pdf/create";

/// <summary>
/// Endpoint for merging multiple HTML requests to a single PDF.
/// </summary>
public const string MergePdfEndpoint = "pdf/merge";

/// <summary>
/// HttpClient name for authentication clients.
/// </summary>
Expand Down
16 changes: 0 additions & 16 deletions test/PDFinch.Client.Common.Tests/BadPdfClient.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace PDFinch.Client.Common.Tests
{
public class HttpClientExtensionTests
public class HttpClientTokenTests
{
[Test]
public async Task GetTokenAsync_Parses_Json()
Expand Down
Loading