Skip to content

Commit 336483e

Browse files
[+] Process html files
[+] Convert html to pdf
1 parent 149bbd9 commit 336483e

File tree

7 files changed

+104
-58
lines changed

7 files changed

+104
-58
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,4 +360,5 @@ MigrationBackup/
360360
.ionide/
361361

362362
# Fody - auto-generated XML schema
363-
FodyWeavers.xsd
363+
FodyWeavers.xsd
364+
/PuppeteerPDFAPI/Binaries

PuppeteerPDFAPI/Misc/Helpers.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ internal static string GetBrowserPath()
2727
if (!Directory.Exists(path))
2828
Directory.CreateDirectory(path);
2929

30-
if (browserName.ToLower() == "chromium")
30+
if (browserName.Equals("chromium", StringComparison.CurrentCultureIgnoreCase))
3131
browserName = "chrome";
3232

3333
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))

PuppeteerPDFAPI/Models/FileModel.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@
33
public class FileModel
44
{
55
public int Id { get; init; }
6-
public string Guid { get; set; } = default!;
7-
public string FileExtension { get; set; } = default!;
6+
public string? Guid { get; set; }
7+
public string? FileExtension { get; set; }
88
public string Filename
99
{
1010
get
1111
{
1212
return Guid + FileExtension;
1313
}
1414
}
15-
public IFormFile File { get; set; } = default!;
15+
public string? Content { get; set; }
1616
}
1717
}

PuppeteerPDFAPI/Program.cs

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
global using PuppeteerPDFAPI.Models;
22
using PuppeteerPDFAPI.Services;
33

4-
54
WebApplicationBuilder builder = WebApplication.CreateSlimBuilder(args);
65

76
builder.Services.AddSingleton<IFileService, FileService>();
@@ -14,25 +13,30 @@
1413

1514
WebApplication app = builder.Build();
1615

17-
app.MapPost("pdf", async (IFileService fileService, HttpRequest request) =>
16+
app.MapPost("pdf", async (IFileService fileService, IPuppeteerService puppeteerService, HttpRequest request) =>
1817
{
1918
if (!request.Form.Files.Any())
2019
return Results.BadRequest("No uploaded files!");
2120

2221
if (request.Form.Files.Count > 1)
23-
return Results.Problem("Too many files uploaded. Upload limit = 1");
22+
return Results.BadRequest("Too many files uploaded. Upload limit = 1");
23+
24+
IFormFile file = request.Form.Files[0];
25+
26+
(FileModel? processedFile, string? ErrorMessage) = await fileService.ProcessAsync(file);
27+
28+
if (processedFile is null)
29+
return Results.BadRequest(ErrorMessage);
2430

25-
FileModel file = new()
26-
{
27-
Guid = Guid.NewGuid().ToString(),
28-
FileExtension = request.Form.Files[0].FileName,
29-
File = request.Form.Files[0]
30-
};
31+
if (string.IsNullOrEmpty(processedFile.Content))
32+
return Results.BadRequest("Empty file!");
3133

34+
(Stream? pdfStream, ErrorMessage) = await puppeteerService.ConvertAsync(processedFile.Content);
3235

33-
await fileService.UploadAsync(file);
36+
if (pdfStream is null)
37+
return Results.Problem(ErrorMessage);
3438

35-
return Results.Ok();
39+
return Results.File(pdfStream, contentType: "application/pdf");
3640
});
3741

3842
IPuppeteerService puppeteerService = app.Services.GetRequiredService<IPuppeteerService>();

PuppeteerPDFAPI/Properties/launchSettings.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
"http": {
55
"commandName": "Project",
66
"dotnetRunMessages": true,
7-
"launchBrowser": true,
8-
"launchUrl": "todos",
7+
"launchBrowser": false,
8+
"launchUrl": "",
99
"applicationUrl": "http://localhost:5252",
1010
"environmentVariables": {
1111
"ASPNETCORE_ENVIRONMENT": "Development"
Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,57 @@
11
using HtmlAgilityPack;
2-
using PuppeteerPDFAPI.Models;
2+
using PuppeteerSharp;
3+
using System.IO;
4+
using System.Text;
35

46
namespace PuppeteerPDFAPI.Services
57
{
68
public interface IFileService
79
{
8-
Task<(bool Success, string? ErrorMessage)> UploadAsync(FileModel file);
9-
bool IsValidHtml();
10+
Task<(FileModel?, string? ErrorMessage)> ProcessAsync(IFormFile file);
1011
}
1112

1213
public class FileService(ILogger<FileService> logger) : IFileService
1314
{
14-
public bool IsValidHtml()
15+
public async Task<(FileModel?, string? ErrorMessage)> ProcessAsync(IFormFile formFile)
1516
{
16-
throw new NotImplementedException();
17-
}
17+
string? extension = Path.GetExtension(formFile.FileName);
1818

19-
public async Task<(bool Success, string? ErrorMessage)> UploadAsync(FileModel file)
20-
{
21-
return await Task.Run<(bool, string?)>(() =>
19+
if (extension == null)
20+
return (default, "No file extension found!");
21+
22+
if (!extension.Equals(".html"))
23+
return (default, "File has to be of type HTML!");
24+
25+
using StreamReader sr = new(formFile.OpenReadStream(), Encoding.UTF8);
26+
27+
string content = await sr.ReadToEndAsync();
28+
29+
if (!IsValidHtml(content))
30+
return (default, "Invalid HTML!");
31+
32+
FileModel file = new()
2233
{
23-
try
24-
{
25-
using Stream stream = new FileStream(file.Guid + file.FileExtension, FileMode.Create);
26-
file.File.CopyTo(stream);
27-
return (true, default);
28-
}
29-
catch (Exception ex)
30-
{
31-
return (false, ex.ToString());
32-
}
33-
});
34+
Guid = Guid.NewGuid().ToString(),
35+
FileExtension = extension,
36+
Content = content,
37+
};
38+
39+
return (file, default);
3440
}
3541

36-
private static bool IsValidHTML(string html)
42+
private bool IsValidHtml(string html)
3743
{
3844
try
3945
{
4046
HtmlDocument? doc = new();
4147
doc.LoadHtml(html);
4248
return !doc.ParseErrors.Any();
4349
}
44-
catch (Exception)
50+
catch (Exception ex)
4551
{
52+
logger.LogError($"{DateTime.Now} [{nameof(FileService)}] | {ex}");
4653
return false;
4754
}
48-
4955
}
50-
5156
}
5257
}
Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,94 @@
11
using PuppeteerPDFAPI.Misc;
22
using PuppeteerSharp;
33
using PuppeteerSharp.BrowserData;
4-
using System.IO;
5-
using System.Reflection;
64

75
namespace PuppeteerPDFAPI.Services
86
{
97
public interface IPuppeteerService
108
{
119
Task InitializeAsync();
10+
Task<(Stream?, string? ErrorMessage)> ConvertAsync(string html);
1211
}
1312

14-
public class PuppeteerService(ILogger<PuppeteerService> logger) : IPuppeteerService
13+
public class PuppeteerService(ILogger<PuppeteerService> logger, IHostApplicationLifetime appLifetime) : IPuppeteerService
1514
{
16-
const SupportedBrowser Browser = SupportedBrowser.Chrome;
15+
const SupportedBrowser TargetBrowser = SupportedBrowser.Chrome;
16+
IBrowser? Browser = default;
1717

1818
public async Task InitializeAsync()
1919
{
20+
appLifetime.ApplicationStopping.Register(async () =>
21+
{
22+
await Shutdown();
23+
});
24+
2025
string browserPath = Helpers.GetBrowserPath();
2126

22-
BrowserFetcherOptions browserFetcherOptions = new() { Browser = Browser, Path = browserPath };
27+
BrowserFetcherOptions browserFetcherOptions = new() { Browser = TargetBrowser, Path = browserPath };
2328
BrowserFetcher? browserFetcher = new(browserFetcherOptions);
2429

2530
InstalledBrowser? installedBrowser = browserFetcher.GetInstalledBrowsers()
26-
.Where(_ => _.Browser == Browser).MaxBy(_ => _.BuildId);
31+
.Where(_ => _.Browser == TargetBrowser).MaxBy(_ => _.BuildId);
2732

2833
if (installedBrowser == null)
2934
{
30-
logger.LogWarning($"{DateTime.Now} [{nameof(PuppeteerService)}] | {Browser} is not installed! Trying to download latest version...");
35+
logger.LogWarning($"{DateTime.Now} [{nameof(PuppeteerService)}] | {TargetBrowser} is not installed! Trying to download latest version...");
3136

3237
installedBrowser = await browserFetcher.DownloadAsync(BrowserTag.Latest);
3338

34-
logger.LogInformation($"{DateTime.Now} [{nameof(PuppeteerService)}] | {Browser} finished downloading...");
39+
logger.LogInformation($"{DateTime.Now} [{nameof(PuppeteerService)}] | {TargetBrowser} finished downloading...");
3540
}
3641

3742
try
3843
{
39-
logger.LogInformation($"{DateTime.Now} [{nameof(PuppeteerService)}] | Trying to startup {Browser}...");
40-
using IBrowser? browser = await Puppeteer.LaunchAsync(
44+
logger.LogInformation($"{DateTime.Now} [{nameof(PuppeteerService)}] | Trying to startup {TargetBrowser}...");
45+
46+
Browser = await Puppeteer.LaunchAsync(
4147
new LaunchOptions()
4248
{
43-
ExecutablePath = Helpers.GetBrowserBinPath(Browser.ToString())
49+
ExecutablePath = Helpers.GetBrowserBinPath(TargetBrowser.ToString())
4450
});
4551

46-
await browser.NewPageAsync();
47-
await browser.CloseAsync();
48-
49-
logger.LogInformation($"{DateTime.Now} [{nameof(PuppeteerService)}] | {Browser} startup finished.");
52+
logger.LogInformation($"{DateTime.Now} [{nameof(PuppeteerService)}] | {TargetBrowser} startup finished.");
5053
}
5154
catch (Exception ex)
5255
{
53-
logger.LogInformation($"{DateTime.Now} [{nameof(PuppeteerService)}] | {Browser} startup failed with error: {ex}.");
56+
logger.LogInformation($"{DateTime.Now} [{nameof(PuppeteerService)}] | {TargetBrowser} startup failed with error: {ex}.");
5457
throw;
5558
}
5659
}
60+
61+
public async Task<(Stream?, string? ErrorMessage)> ConvertAsync(string html)
62+
{
63+
if (Browser is null)
64+
return (default, "Browser not initialized!");
65+
66+
try
67+
{
68+
using IPage page = await Browser.NewPageAsync();
69+
await page.SetContentAsync(html);
70+
71+
Stream pdfStream = await page.PdfStreamAsync();
72+
73+
await page.CloseAsync();
74+
75+
return (pdfStream, default);
76+
}
77+
catch (Exception ex)
78+
{
79+
logger.LogError($"{DateTime.Now} [{nameof(PuppeteerService)}] | {ex}");
80+
return (null, ex.ToString());
81+
}
82+
}
83+
84+
public async Task Shutdown()
85+
{
86+
if (Browser is null)
87+
return;
88+
89+
await Browser.CloseAsync();
90+
await Browser.DisposeAsync();
91+
92+
}
5793
}
5894
}

0 commit comments

Comments
 (0)