Skip to content

Instantly share code, notes, and snippets.

@tmakin
Last active December 20, 2023 08:38
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tmakin/d391f446588b040665765b0f7e06c240 to your computer and use it in GitHub Desktop.
Save tmakin/d391f446588b040665765b0f7e06c240 to your computer and use it in GitHub Desktop.
c# Html to Pdf on AWS Lambda using AspNetCore
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AssemblyName>HtmlToPdfAws</AssemblyName>
<Rootnamespace>HtmlToPdfAws</Rootnamespace>
</PropertyGroup>
<ItemGroup>
<!-- native assets excluded as we are using lambda layers instead -->
<PackageReference Include="Haukcode.WkHtmlToPdfDotNet" Version="1.3.0" ExcludeAssets="native" />
<PackageReference Include="Amazon.Lambda.AspNetCoreServer" Version="5.3.0" />
<PackageReference Include="AWSXRayRecorder.Handlers.AspNetCore" Version="2.7.1" />
</ItemGroup>
<ItemGroup>
<None Update="Assets\test.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
using Amazon.Lambda.Core;
using Microsoft.AspNetCore.Hosting;
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
namespace HtmlToPdfAws
{
public class LambdaEntryPoint : Amazon.Lambda.AspNetCoreServer.APIGatewayHttpApiV2ProxyFunction
{
protected override void Init(IWebHostBuilder builder)
{
builder.UseStartup<Startup>();
}
}
}
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace HtmlToPdfAws
{
[ApiController]
[Route("Pdf")]
public class PdfController : ControllerBase
{
private readonly ILogger<ExportPdf> _logger;
private readonly IPdfGenerator _generator;
public ExportPdf(ILogger<ExportPdf> logger, IPdfGenerator generator)
{
_logger = logger;
_generator = generator;
}
[HttpPost]
public async Task<FileContentResult> Post()
{
using var reader = new StreamReader(Request.Body, Encoding.UTF8);
var html = await reader.ReadToEndAsync();
return BuildPdfResult(html);
}
[HttpGet("test")]
public async Task<FileContentResult> Get()
{
var html = await System.IO.File.ReadAllTextAsync("./Assets/test.html");
return BuildPdfResult(html);
}
private FileContentResult BuildPdfResult(string html)
{
_logger.LogInformation("Processing PDF Request");
var pdf = _generator.BuildPdf(html);
_logger.LogInformation($"PDF Generated. Length={pdf.Length}");
return File(pdf, "application/pdf");
}
}
}
using WkHtmlToPdfDotNet;
using IPdfConverter = WkHtmlToPdfDotNet.Contracts.IConverter;
namespace HtmlToPdfAws
{
public interface IPdfGenerator
{
byte[] BuildPdf(string html);
}
internal class PdfGenerator : IPdfGenerator
{
private readonly IPdfConverter _pdfConverter = new SynchronizedConverter(new PdfTools());
public byte[] BuildPdf(string html)
{
const double margin = 25;
return _pdfConverter.Convert(new HtmlToPdfDocument()
{
GlobalSettings = {
Orientation = Orientation.Portrait,
PaperSize = PaperKind.A4,
Margins = new MarginSettings(margin, margin, margin, margin),
},
Objects =
{
new ObjectSettings
{
WebSettings = new WebSettings
{
PrintMediaType = true,
EnableIntelligentShrinking = true
},
HtmlContent = html
}
}
});
}
}
}
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace HtmlToPdfAws
{
/// <summary>
/// Note that we can't use IStartup interface as it is not supported by the Lambda runtime
/// We have to use convention based builders instead
/// </summary>
public class ExportStartup
{
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// https://andrewlock.net/new-in-asp-net-core-3-service-provider-validation/
services.AddControllers().AddControllersAsServices();
services.AddSingleton<IPdfGenerator, PdfGenerator>();
// ... whatever else you might need
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app)
{
app.UseXRay("YourXRayAppName");
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
# SAM deployment template
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
Demo app for generating pdf from HTML on AWS lamnda using ASP net core
Parameters:
StageName:
Type: String
Default: prod
Resources:
HtmlToPdfAwsApiGateway: # rename this to whatever you like
Type: AWS::Serverless::HttpApi
Properties:
StageName: !Ref StageName
FailOnWarnings: True
HtmlToPdfAwsFunction:
Type: AWS::Serverless::Function # https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
FunctionName: HtmlToPdfAws
Handler: HtmlToPdfAws.LambdaEntryPoint::FunctionHandlerAsync
Runtime: dotnetcore3.1
CodeUri: '' # This is injected by 'dotnet lambda deploy-serverless''
MemorySize: 1024
Timeout: 30
Role: !GetAtt HtmlToPdfAwsRole.Arn
Policies:
- AWSLambdaFullAccess
Layers:
- arn:aws:lambda:eu-west-2:347599033421:layer:wkhtmltopdf-0_12_6:1 # https://github.com/brandonlim-hs/wkhtmltopdf-aws-lambda-layer
- arn:aws:lambda:eu-west-2:347599033421:layer:amazon_linux_fonts:1 # https://github.com/brandonlim-hs/fonts-aws-lambda-layer
Environment:
Variables:
FONTCONFIG_PATH: /opt/etc/fonts
Tracing: Active
Events:
ProxyResource:
Type: HttpApi
Properties:
Path: "/{proxy+}"
Method: ANY
ApiId: !Ref HtmlToPdfAwsApiGateway
HtmlToPdfAwsRole: #https://aws.amazon.com/blogs/compute/sharing-secrets-with-aws-lambda-using-aws-systems-manager-parameter-store/
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
-
Effect: Allow
Principal:
Service:
- 'lambda.amazonaws.com'
Action:
- 'sts:AssumeRole'
ManagedPolicyArns:
- 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
- 'arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess'
- 'arn:aws:iam::aws:policy/AWSLambdaFullAccess'
Outputs:
ApiURL:
Description: API endpoint URL for Prod environment
Value:
Fn::Sub: https://${HtmlToPdfAwsApiGateway}.execute-api.${AWS::Region}.amazonaws.com/${StageName}/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment