Creating a Purchase Order PDF - Code
Index.razor
@page "/"
@using BlazorPurchaseOrders.Data
@using Microsoft.Extensions.Configuration
@using System.IO;
@inject IPOHeaderService POHeaderService
@inject IPOLineService POLineService
@inject NavigationManager NavigationManager
@inject IJSRuntime IJS
@inject IEmailService EmailService
@inject IEmailSender EmailSender
@inject IConfiguration _config
@inject IControlTableService ControlTableService
@inject CreatePurchaseOrderPdf exportService
@attribute [Microsoft.AspNetCore.Authorization.AllowAnonymous]
@inject AuthenticationStateProvider AuthenticationStateProvider
<head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css">
</head>
<div class="col-sm-12">
<AuthorizeView>
<NotAuthorized>
<h5>Please Log in</h5>
<h6>or</h6>
<h5>Register to use Blazor Purchase Orders</h5>
</NotAuthorized>
<Authorized>
<h3>Purchase Orders</h3>
<br />
<SfGrid DataSource="@poheader"
Toolbar="Toolbaritems">
<GridEvents RowSelected="RowSelectHandler" OnToolbarClick="ToolbarClickHandler" TValue="POHeader"></GridEvents>
<GridColumns>
<GridColumn Field="@nameof(POHeader.POHeaderOrderNumber)"
HeaderText="No"
TextAlign="@TextAlign.Left"
Width="10">
</GridColumn>
<GridColumn Field="@nameof(POHeader.POHeaderOrderDate)"
HeaderText="Date"
Format="d"
Type="ColumnType.Date"
TextAlign="@TextAlign.Center"
Width="15">
</GridColumn>
<GridColumn Field="@nameof(POHeader.SupplierName)"
HeaderText="Supplier"
TextAlign="@TextAlign.Left"
Width="40">
</GridColumn>
<GridColumn Field="@nameof(POHeader.TotalOrderValue)"
HeaderText="Value"
TextAlign="@TextAlign.Right"
Format="C2"
Width="20">
</GridColumn>
<GridColumn Field="@nameof(POHeader.POHeaderRequestedBy)"
HeaderText="Requested by"
TextAlign="@TextAlign.Left"
Width="40">
</GridColumn>
</GridColumns>
</SfGrid>
<WarningPage @ref="Warning" WarningHeaderMessage="@WarningHeaderMessage" WarningContentMessage="@WarningContentMessage" />
<ConfirmPage @ref="ConfirmOrderDelete" ConfirmHeaderMessage="@ConfirmHeaderMessage" ConfirmContentMessage="@ConfirmContentMessage" ConfirmationChanged="ConfirmOrderArchive" />
</Authorized>
</AuthorizeView>
</div>
<div class="col-lg-12 control-section toast-default-section">
<SfToast @ref="ToastObj" Timeout="4000">
<ToastPosition X="Right" Y="Top"></ToastPosition>
</SfToast>
</div>
<style>
@@font-face {
font-family: 'Toast_icons';
src: url(data:application/x-font-ttf;charset=utf-8;base64,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) format('truetype');
font-weight: normal;
font-style: normal;
}
#toast_types button {
margin: 5px;
min-width: 160px;
max-width: 160px;
}
.toast-icons {
font-family: 'Toast_icons' !important;
speak: none;
font-size: 55px;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
#toast_type .e-toast-icon.e-icons {
height: auto;
font-size: 30px;
}
.toast-icons.e-success::before {
content: "\e701";
}
.toast-icons.e-error::before {
content: "\e700";
}
.toast-icons.e-info::before {
content: "\e704";
}
.toast-icons.e-warning::before {
content: "\e703";
}
#toast_types {
text-align: center;
}
</style>
@code {
// Create an empty list, named poheader, of empty POHeader objects.
IEnumerable<POHeader> poheader;
POHeader orderHeader = new POHeader();
IEnumerable<POLine> orderLinesByPOHeader;
public List<POLine> orderLines = new List<POLine>();
private List<ItemModel> Toolbaritems = new List<ItemModel>();
//int POHeaderID = 0;
private int selectedPOHeaderID { get; set; } = 0;
private Guid selectedPOHeaderGuid { get; set; }
private string selectedPOHeaderSupplierEmail { get; set; } = "";
private int selectedPOHeaderOrderNumber { get; set; } = 0;
WarningPage Warning;
string WarningHeaderMessage = "";
string WarningContentMessage = "";
ConfirmPage ConfirmOrderDelete;
string ConfirmHeaderMessage = "";
string ConfirmContentMessage = "";
public bool ConfirmationChanged { get; set; } = false;
private string emailbody = "";
[CascadingParameter]
private Task<AuthenticationState> authenticationStateTask { get; set; }
SfToast ToastObj;
string ServerLocation = "";
ControlTable controltable = new(); //NEW
private string SmtpHost { get; set; } = ""; //NEW
private int SmtpPort { get; set; } = 0; //NEW
private string SmtpUserFriendlyName { get; set; } = ""; //NEW
private string SmtpUserEmailAddress { get; set; } = ""; //NEW
private string SmtpPass { get; set; } = ""; //NEW
private string SENDGRID_APIKEY { get; set; } = ""; //NEW
private string SenderEmail { get; set; } = ""; //NEW
private string SenderEmailFriendlyName { get; set; } = ""; //NEW
private string EmailSendMethod { get; set; } = ""; //NEW
private List<ToastModel> Toast = new List<ToastModel>
{
new ToastModel{ Title = "Warning!", Content="There was a problem with your network connection.", CssClass="e-toast-warning", Icon="e-warning toast-icons" },
new ToastModel{ Title = "Success!", Content="Your message has been sent successfully.", CssClass="e-toast-success", Icon="e-success toast-icons" },
new ToastModel{ Title = "Error!", Content="A problem has been occurred while submitting your email", CssClass="e-toast-danger", Icon="e-error toast-icons" },
new ToastModel{ Title = "Information!", Content="Please read the comments carefully.", CssClass="e-toast-info", Icon="e-info toast-icons" }
};
protected override async Task OnInitializedAsync()
{
//Populate the list of orders objects from the Purchase Order Header table.
await GetOrderList();
Toolbaritems.Add(new ItemModel() { Text = "Add", TooltipText = "Add a new order", PrefixIcon = "e-add" });
Toolbaritems.Add(new ItemModel() { Text = "Edit", TooltipText = "Edit selected order", PrefixIcon = "e-edit" });
Toolbaritems.Add(new ItemModel() { Text = "Delete", TooltipText = "Delete selected order", PrefixIcon = "e-delete" });
Toolbaritems.Add(new ItemModel() { Text = "Preview", TooltipText = "Preview selected order", PrefixIcon = "e-print" });
Toolbaritems.Add(new ItemModel() { Text = "Email", TooltipText = "Email selected order", PrefixIcon = "bi-envelope" });
//Toolbaritems.Add(new ItemModel() { Text = "PDF", TooltipText = "PDF Test", PrefixIcon = "e-print" });
ServerLocation = _config.GetValue<string>("ApplicationSettings:PreviewOrderLinkRoute");
controltable = await ControlTableService.ControlTable_GetOne(); //NEW
SmtpHost = controltable.SmtpHost; //NEW
SmtpPort = controltable.SmtpPort; //NEW
SmtpUserFriendlyName = controltable.SmtpUserFriendlyName; //NEW
SmtpUserEmailAddress = controltable.SmtpUserEmailAddress; //NEW
SmtpPass = controltable.SmtpPass; //NEW
SENDGRID_APIKEY = controltable.SENDGRID_APIKEY; //NEW
SenderEmail = controltable.SenderEmail; //NEW
SenderEmailFriendlyName = controltable.SenderEmailFriendlyName; //NEW
EmailSendMethod = controltable.EmailSendMethod; //NEW
}
public async Task ToolbarClickHandler(Syncfusion.Blazor.Navigations.ClickEventArgs args)
{
if (args.Item.Text == "Add")
{
//Code for adding goes here
//POHeaderID = 0;
NavigationManager.NavigateTo($"/purchaseorder/{Guid.Empty}");
}
if (args.Item.Text == "Edit")
{
//Code for editing - Check that an Order has been selected from the grid
if (selectedPOHeaderID == 0)
{
WarningHeaderMessage = "Warning!";
WarningContentMessage = "Please select an Order from the grid.";
Warning.OpenDialog();
}
else
{
NavigationManager.NavigateTo($"/purchaseorder/{selectedPOHeaderGuid}");
}
}
if (args.Item.Text == "Delete")
{
//Code for deleting
if (selectedPOHeaderID == 0) //Check that an order has been selected
{
WarningHeaderMessage = "Warning!";
WarningContentMessage = "Please select an Order from the grid.";
Warning.OpenDialog();
}
else
{
//Populate orderHeader using selectedPOHeaderID
orderHeader = await POHeaderService.POHeader_GetOne(selectedPOHeaderID);
ConfirmHeaderMessage = "Confirm Deletion";
ConfirmContentMessage = "Please confirm that this order should be deleted.";
ConfirmOrderDelete.OpenDialog();
}
}
if (args.Item.Text == "Preview")
{
//Code for editing - Check that an Order has been selected from the grid
if (selectedPOHeaderID == 0)
{
WarningHeaderMessage = "Warning!";
WarningContentMessage = "Please select an Order from the grid.";
Warning.OpenDialog();
}
else
{
await IJS.InvokeAsync<object>("open", new object[] { "/previeworder/" + selectedPOHeaderGuid + "", "_blank" });
}
}
if (args.Item.Text == "Email")
{
//Code for editing - Check that an Order has been selected from the grid
if (selectedPOHeaderID == 0)
{
WarningHeaderMessage = "Warning!";
WarningContentMessage = "Please select an Order from the grid.";
Warning.OpenDialog();
return;
}
if (selectedPOHeaderSupplierEmail == null || selectedPOHeaderSupplierEmail == "")
{
WarningHeaderMessage = "Warning!";
WarningContentMessage = "Supplier Email Address is missing.";
Warning.OpenDialog();
return;
}
//Populate orderHeader using selectedPOHeaderID
orderHeader = await POHeaderService.POHeader_GetOne(selectedPOHeaderID);
orderLinesByPOHeader = await POLineService.POLine_GetByPOHeader(selectedPOHeaderID);
orderLines = orderLinesByPOHeader.ToList(); //Convert from IEnumerable to List
string filename = "";
using (MemoryStream pdfStream = exportService.CreatePdf(orderHeader, orderLines))
{
filename = @$"{Environment.CurrentDirectory}/wwwroot/Files/PO-" + selectedPOHeaderOrderNumber + ".pdf";
FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None);
byte[] pdfData = pdfStream.ToArray();
fs.Write(pdfData, 0, pdfData.Length);
fs.Close();
}
//Create body of email using html
emailbody = "<p>Dear Supplier</p>";
emailbody = emailbody + "<p>Please find attached our order number " + selectedPOHeaderOrderNumber;
emailbody = emailbody + ". Alternatively click one of the following links (or copy and paste the link into your browser)";
emailbody = emailbody + "</p>";
//without the anchor tag
emailbody = emailbody + "<p>" + ServerLocation + "/previeworder/"
+ selectedPOHeaderGuid + "</p>";
//with the anchor tag
emailbody = emailbody + "<p><a href= "" + ServerLocation + "/previeworder/"
+ selectedPOHeaderGuid + "">" + ServerLocation + "/previeworder/"
+ selectedPOHeaderGuid + "<a></p>";
emailbody = emailbody + "<p>Kind regards</p>";
emailbody = emailbody + "<p>BlazorCode</p>";
bool emailSent = false;
string attachmentName = "PurchaseOrder-" + selectedPOHeaderOrderNumber + ".pdf";
switch (EmailSendMethod)
{
case "SMTP":
emailSent = EmailService.Send(SmtpHost,
SmtpPort,
SmtpUserFriendlyName,
SmtpUserEmailAddress,
SmtpPass,
selectedPOHeaderSupplierEmail,
"Purchase Order:" + selectedPOHeaderOrderNumber + ".",
emailbody,
filename,
attachmentName);
break;
case "SendGrid":
emailSent = await EmailSender.SendEmail(SENDGRID_APIKEY,
SenderEmailFriendlyName,
SenderEmail,
selectedPOHeaderSupplierEmail,
"Purchase Order:" + selectedPOHeaderOrderNumber + ".",
emailbody,
filename,
attachmentName);
break;
default:
WarningHeaderMessage = "Warning!";
WarningContentMessage = "No Email Method has been setup in Control Table.";
Warning.OpenDialog();
break;
}
//Always delete purchase order pdf
File.Delete(filename);
if (emailSent)
{
await this.ToastObj.Show(Toast[1]);
}
else
{
await this.ToastObj.Show(Toast[2]);
}
}
if (args.Item.Text == "PDF")
{
//Code for editing - Check that an Order has been selected from the grid
if (selectedPOHeaderID == 0)
{
WarningHeaderMessage = "Warning!";
WarningContentMessage = "Please select an Order from the grid.";
Warning.OpenDialog();
return;
}
//Populate orderHeader using selectedPOHeaderID
orderHeader = await POHeaderService.POHeader_GetOne(selectedPOHeaderID);
//Populate orderLines using selectedPOHeaderID
orderLinesByPOHeader = await POLineService.POLine_GetByPOHeader(selectedPOHeaderID);
orderLines = orderLinesByPOHeader.ToList(); //Convert from IEnumerable to List
//Pass orderHeader and orderLines to exportservice.CreatePdf and get back a MemoryStream of the pdf document
using (MemoryStream pdfStream = exportService.CreatePdf(orderHeader, orderLines))
{
//Get current directory and concatenate with 'Files' folder and selected Order Number to create pdf file name
string filename = @$"{Environment.CurrentDirectory}/wwwroot/Files/PO-" + selectedPOHeaderOrderNumber + ".pdf";
//Define a FileStream object
FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None);
//Convert MemoryStream to a byte array
byte[] pdfData = pdfStream.ToArray();
//Write the byte array to the FileStream (Disc) and close the Filestream
fs.Write(pdfData, 0, pdfData.Length);
fs.Close();
}
}
}
public void RowSelectHandler(RowSelectEventArgs<POHeader> args)
{
//{args.Data} returns the current selected records.
selectedPOHeaderID = args.Data.POHeaderID;
selectedPOHeaderGuid = args.Data.POHeaderGuid;
selectedPOHeaderSupplierEmail = args.Data.POHeaderSupplierEmail;
selectedPOHeaderOrderNumber = args.Data.POHeaderOrderNumber;
}
protected async Task ConfirmOrderArchive(bool archiveConfirmed)
{
if (archiveConfirmed)
{
orderHeader.POHeaderIsArchived = true;
bool Success = await POHeaderService.POHeaderUpdate(orderHeader);
await GetOrderList();
StateHasChanged();
}
}
protected async Task GetOrderList()
{
var user = (await authenticationStateTask).User;
if (user.IsInRole("Admin") || user.IsInRole("Manager"))
{
poheader = await POHeaderService.POHeaderList(null); //leave user name blank
}
else
{
poheader = await POHeaderService.POHeaderList(user.Identity.Name); //pass user name
}
}
}
CreatePurchaseOrderPdf.cs
using System;
using System.Collections.Generic;
using Syncfusion.Pdf;
using Syncfusion.Pdf.Graphics;
using Syncfusion.Pdf.Grid;
using Syncfusion.Drawing;
using System.IO;
namespace BlazorPurchaseOrders.Data
{ public class CreatePurchaseOrderPdf
{
public MemoryStream CreatePdf(POHeader orderHeader, List<POLine> orderLines)
{
//Create a new PDF document
using (PdfDocument pdfDocument = new())
{
int paragraphAfterSpacing = 8;
int cellMargin = 4;
//Add page to the PDF document
PdfPage page = pdfDocument.Pages.Add();
//Set the page size
pdfDocument.PageSettings.Size = PdfPageSize.A4;
pdfDocument.PageSettings.Margins.Left = 28;
pdfDocument.PageSettings.Margins.Right = 28;
pdfDocument.PageSettings.Margins.Top = 8;
pdfDocument.PageSettings.Margins.Bottom = 14;
#region Header
//Define 'bounds' for header
RectangleF boundsHeader = new (0, 0, pdfDocument.Pages[0].GetClientSize().Width, 200);
PdfPageTemplateElement header = new(boundsHeader);
pdfDocument.Template.Top = header;
//Load the image from the disc
string logoFilename = @$"{Environment.CurrentDirectory}/wwwroot/Media/blazorcode_logo_small.png";
FileStream logoStream = new(logoFilename, FileMode.Open, FileAccess.Read);
PdfBitmap logo = new(logoStream);
//Draw the image -- SizeF scales the image to specified size
header.Graphics.DrawImage(logo, new PointF(0, 0), new SizeF(183, 35));
//Create a new font
PdfStandardFont companyAddressFont = new(PdfFontFamily.Helvetica, 10, PdfFontStyle.Bold);
//Set the format for string
PdfStringFormat stringFormat = new();
//Set the text alignment
stringFormat.Alignment = PdfTextAlignment.Right;
string adddressText = "blazorcode.uk";
header.Graphics.DrawString(adddressText, companyAddressFont, PdfBrushes.Black, new RectangleF(0, 0, page.GetClientSize().Width, page.GetClientSize().Height), stringFormat);
adddressText = "5 High Street";
header.Graphics.DrawString(adddressText, companyAddressFont, PdfBrushes.Black, new RectangleF(0, 14, page.GetClientSize().Width, page.GetClientSize().Height), stringFormat);
adddressText = "Trumpington";
header.Graphics.DrawString(adddressText, companyAddressFont, PdfBrushes.Black, new RectangleF(0, 28, page.GetClientSize().Width, page.GetClientSize().Height), stringFormat);
adddressText = "TR4A IRS";
header.Graphics.DrawString(adddressText, companyAddressFont, PdfBrushes.Black, new RectangleF(0, 42, page.GetClientSize().Width, page.GetClientSize().Height), stringFormat);
//Concatenate Supplier Name & Address, omitting blank address lines
string supplierAddress = orderHeader.SupplierName + Environment.NewLine;
if (!String.IsNullOrEmpty(orderHeader.POHeaderSupplierAddress1))
{
supplierAddress += orderHeader.POHeaderSupplierAddress1 + Environment.NewLine;
}
if (!String.IsNullOrEmpty(orderHeader.POHeaderSupplierAddress2))
{
supplierAddress += orderHeader.POHeaderSupplierAddress2 + Environment.NewLine;
}
if (!String.IsNullOrEmpty(orderHeader.POHeaderSupplierAddress3))
{
supplierAddress += orderHeader.POHeaderSupplierAddress3 + Environment.NewLine;
}
supplierAddress += orderHeader.POHeaderSupplierPostCode;
//Set the text alignment and style
stringFormat.Alignment = PdfTextAlignment.Left;
PdfStandardFont headerFont = new(PdfFontFamily.Helvetica, 9, PdfFontStyle.Regular);
//Print Supplier Name & Address
header.Graphics.DrawString(supplierAddress, headerFont, PdfBrushes.Black, new RectangleF(0, 90, page.GetClientSize().Width, page.GetClientSize().Height), stringFormat);
//Order No etc
header.Graphics.DrawString("Order No", headerFont, PdfBrushes.Black, new RectangleF((page.GetClientSize().Width) / 2, 90, page.GetClientSize().Width / 2, 18), stringFormat);
header.Graphics.DrawString("Order Date", headerFont, PdfBrushes.Black, new RectangleF((page.GetClientSize().Width) / 2, 108, page.GetClientSize().Width, page.GetClientSize().Height), stringFormat);
header.Graphics.DrawString("Contact", headerFont, PdfBrushes.Black, new RectangleF((page.GetClientSize().Width) / 2, 126, page.GetClientSize().Width, page.GetClientSize().Height), stringFormat);
header.Graphics.DrawString(": " + orderHeader.POHeaderOrderNumber.ToString(), headerFont, PdfBrushes.Black, new RectangleF(((page.GetClientSize().Width) / 2) + 50, 90, page.GetClientSize().Width, page.GetClientSize().Height), stringFormat);
header.Graphics.DrawString(": " + orderHeader.POHeaderOrderDate.ToShortDateString(), headerFont, PdfBrushes.Black, new RectangleF(((page.GetClientSize().Width) / 2) + 50, 108, page.GetClientSize().Width, page.GetClientSize().Height), stringFormat);
header.Graphics.DrawString(": " + orderHeader.POHeaderRequestedBy, headerFont, PdfBrushes.Black, new RectangleF(((page.GetClientSize().Width) / 2) + 50, 126, page.GetClientSize().Width, page.GetClientSize().Height), stringFormat);
//Purchase Order Title
stringFormat.Alignment = PdfTextAlignment.Center;
PdfStandardFont docTypeFont = new(PdfFontFamily.Helvetica, 14, PdfFontStyle.Bold);
header.Graphics.DrawString("PURCHASE ORDER", docTypeFont, PdfBrushes.Black, new RectangleF(0, 170, page.GetClientSize().Width, page.GetClientSize().Height), stringFormat);
#endregion
#region Body
PdfStandardFont contentFont = new(PdfFontFamily.Helvetica, 8);
decimal runningNetPrice = 0;
decimal runningTaxAmount = 0;
decimal runningGrossPrice = 0;
float bottomOfGrid = 0;
int linesPerPage = 40;
//Calculate the number of pages
int pageCount = ((orderLines.Count + (linesPerPage - 1)) / linesPerPage);
//Initialise pageNo and the line number
int pageNo = 1;
int i = 0; //Effectively line number
// Deal with full pages first - (if only one page is needed, see next section)
while (pageNo < pageCount)
{
//Create a PdfGrid
PdfGrid pdfGrid = new();
pdfGrid.Style.CellPadding.Left = cellMargin;
pdfGrid.Style.CellPadding.Right = cellMargin;
//Sets up columns, column alignment and header text
CreatePdfGridLayout(pdfGrid);
while (i < (pageNo * linesPerPage))
{
string truncatedProductCode = orderLines[i].POLineProductCode;
string truncatedProductDescription = orderLines[i].POLineProductDescription;
SizeF sizeProductCode = contentFont.MeasureString(orderLines[i].POLineProductCode);
SizeF sizeProductDescription = contentFont.MeasureString(orderLines[i].POLineProductDescription);
//Product Code Column Width (minus cellMargin) = 70 - (2*4) = 62
if (sizeProductCode.Width > 62)
{
truncatedProductCode = GetFittedStringToPrint(truncatedProductCode, 62, contentFont);
}
//Product Description Column Width (minus cellMargin) = 130 - (2*4) = 122
if (sizeProductDescription.Width > 122)
{
truncatedProductDescription = GetFittedStringToPrint(truncatedProductDescription, 122, contentFont);
}
//Add rows
PdfGridRow pdfGridRow = pdfGrid.Rows.Add();
pdfGridRow.Cells[0].Value = truncatedProductCode;
pdfGridRow.Cells[1].Value = truncatedProductDescription;
pdfGridRow.Cells[2].Value = String.Format("{0:N0}", orderLines[i].POLineProductQuantity);
pdfGridRow.Cells[3].Value = String.Format("{0:C2}", orderLines[i].POLineProductUnitPrice);
pdfGridRow.Cells[4].Value = String.Format("{0:C2}", orderLines[i].POLineNetPrice);
pdfGridRow.Cells[5].Value = String.Format("{0:P2}", orderLines[i].POLineTaxRate);
pdfGridRow.Cells[6].Value = String.Format("{0:C2}", orderLines[i].POLineTaxAmount);
pdfGridRow.Cells[7].Value = String.Format("{0:C2}", orderLines[i].POLineGrossPrice);
runningNetPrice = (decimal)(runningNetPrice + orderLines[i].POLineNetPrice);
runningTaxAmount += orderLines[i].POLineTaxAmount;
runningGrossPrice += orderLines[i].POLineGrossPrice;
i++;
}
//Draw PDF grid into the PDF page
PdfLayoutResult result = pdfGrid.Draw(page, new PointF(0, paragraphAfterSpacing));
if (pageNo < pageCount)
{
page = pdfDocument.Pages.Add();
}
//Increment page no
pageNo++;
}
//Now deal with last records, those that will not fill a whole page
PdfGrid pdfGridLastPage = new();
pdfGridLastPage.Style.CellPadding.Left = cellMargin;
pdfGridLastPage.Style.CellPadding.Right = cellMargin;
//Sets up columns, column alignment and header text
CreatePdfGridLayout(pdfGridLastPage);
PdfGraphics graphicsLastPage = page.Graphics;
while (i < orderLines.Count)
{
string truncatedProductCode = orderLines[i].POLineProductCode;
string truncatedProductDescription = orderLines[i].POLineProductDescription;
SizeF sizeProductCode = contentFont.MeasureString(orderLines[i].POLineProductCode);
SizeF sizeProductDescription = contentFont.MeasureString(orderLines[i].POLineProductDescription);
//Product Code Column Width (minus cellMargin) = 70 - (2*4) = 62
if (sizeProductCode.Width > 62)
{
truncatedProductCode = GetFittedStringToPrint(truncatedProductCode, 62, contentFont);
}
//Product Description Column Width (minus cellMargin) = 130 - (2*4) = 122
if (sizeProductDescription.Width > 122)
{
truncatedProductDescription = GetFittedStringToPrint(truncatedProductDescription, 122, contentFont);
}
//Add rows
PdfGridRow pdfGridRow = pdfGridLastPage.Rows.Add();
pdfGridRow.Cells[0].Value = truncatedProductCode;
pdfGridRow.Cells[1].Value = truncatedProductDescription;
pdfGridRow.Cells[2].Value = String.Format("{0:N0}", orderLines[i].POLineProductQuantity);
pdfGridRow.Cells[3].Value = String.Format("{0:C2}", orderLines[i].POLineProductUnitPrice);
pdfGridRow.Cells[4].Value = String.Format("{0:C2}", orderLines[i].POLineNetPrice);
pdfGridRow.Cells[5].Value = String.Format("{0:P2}", orderLines[i].POLineTaxRate);
pdfGridRow.Cells[6].Value = String.Format("{0:C2}", orderLines[i].POLineTaxAmount);
pdfGridRow.Cells[7].Value = String.Format("{0:C2}", orderLines[i].POLineGrossPrice);
runningNetPrice = (decimal)(runningNetPrice + orderLines[i].POLineNetPrice);
runningTaxAmount += orderLines[i].POLineTaxAmount;
runningGrossPrice += orderLines[i].POLineGrossPrice;
//Increment order line no
i++;
}
//Draw PDF grid into the PDF page
PdfLayoutResult resultLastPage = pdfGridLastPage.Draw(page, new PointF(0, paragraphAfterSpacing));
bottomOfGrid = resultLastPage.Bounds.Bottom;
//Section for showing totals
string totalNetPrice = String.Format("{0:C2}", runningNetPrice);
string totalTaxAmount = String.Format("{0:C2}", runningTaxAmount);
string totalGrossPrice = String.Format("{0:C2}", runningGrossPrice);
stringFormat.Alignment = PdfTextAlignment.Right;
PdfStandardFont totalsFont = new(PdfFontFamily.Helvetica, 8, PdfFontStyle.Bold);
graphicsLastPage.DrawString(totalNetPrice, totalsFont, PdfBrushes.Black, new RectangleF(295 - cellMargin, bottomOfGrid + 10, 60, page.GetClientSize().Height), stringFormat);
graphicsLastPage.DrawString(totalTaxAmount, totalsFont, PdfBrushes.Black, new RectangleF(395 - cellMargin, bottomOfGrid + 10, 60, page.GetClientSize().Height), stringFormat);
graphicsLastPage.DrawString(totalGrossPrice, totalsFont, PdfBrushes.Black, new RectangleF(455 - cellMargin, bottomOfGrid + 10, 60, page.GetClientSize().Height), stringFormat);
stringFormat.Alignment = PdfTextAlignment.Left;
graphicsLastPage.DrawString("Order Total", totalsFont, PdfBrushes.Black, new RectangleF(0 + cellMargin, bottomOfGrid + 10, 60, page.GetClientSize().Height), stringFormat);
//Create new PDF pen
PdfPen pen = new(Color.Black, 1);
//Draw line on the page below the Order Totals line
graphicsLastPage.DrawLine(pen, 0, bottomOfGrid + 20, 515, bottomOfGrid + 20);
#endregion
#region Footer
//Define 'bounds' for footer
RectangleF boundsFooter = new(0, pdfDocument.Pages[0].GetClientSize().Height,
pdfDocument.Pages[0].GetClientSize().Width, 10);
PdfPageTemplateElement footer = new (boundsFooter);
pdfDocument.Template.Bottom = footer;
PdfStandardFont footerFont = new (PdfFontFamily.Helvetica, 7);
PdfBrush brush = new PdfSolidBrush(Color.Black);
//Page x of y
//string footerPageNumber = $"Page {pageNo} of {pageCount}";
//stringFormat.Alignment = PdfTextAlignment.Center;
//footer.Graphics.DrawString(footerPageNumber, footerFont, brush, new RectangleF(0, 0,
// page.GetClientSize().Width, page.GetClientSize().Height), stringFormat);
//Create date printed field
string datePrinted = "Printed on: " + DateTime.Now.ToString("dd/MM/yyyy");
stringFormat.Alignment = PdfTextAlignment.Right;
footer.Graphics.DrawString(datePrinted, footerFont, brush, new RectangleF(0, 0,
page.GetClientSize().Width, page.GetClientSize().Height), stringFormat);
#endregion
//Alternative version for page numbers using PdfCompositeField
///Create page number and count fields
PdfPageNumberField pageNumber = new();
PdfPageCountField count = new();
//Create composite field.
//Add the fields in composite fields
PdfCompositeField compositeField = new(footerFont, brush, "Page {0} of {1}", pageNumber, count);
//Draw the composite fields in footer. Note they are both on the same 'line'.
compositeField.Draw(footer.Graphics, new PointF(240, 0));
using (MemoryStream stream = new ())
{
//Saving the PDF document into the stream
pdfDocument.Save(stream);
//Closing the PDF document
pdfDocument.Close(true);
return stream;
}
static string GetFittedStringToPrint(string input, float maxWidth, PdfStandardFont contentFont)
{
var output = input;
var stringMeasurement = contentFont.MeasureString(input);
if (stringMeasurement.Width > maxWidth)
{
var inputMinusOneChar = input;
do
{
inputMinusOneChar = inputMinusOneChar.Substring(0, inputMinusOneChar.Length - 1).Trim();
stringMeasurement = contentFont.MeasureString(inputMinusOneChar + "...");
} while (stringMeasurement.Width > maxWidth);
output = inputMinusOneChar + "...";
}
return output;
}
static void CreatePdfGridLayout(PdfGrid pdfGrid)
{
//Create PDF grid column. NOTE: A4 Page width between standard margins = 515 points
PdfGridColumn column1 = new(pdfGrid);
column1.Width = 70;
PdfGridColumn column2 = new(pdfGrid);
column2.Width = 130;
PdfGridColumn column3 = new(pdfGrid);
column3.Width = 45;
PdfGridColumn column4 = new(pdfGrid);
column4.Width = 50;
PdfGridColumn column5 = new(pdfGrid);
column5.Width = 60;
PdfGridColumn column6 = new(pdfGrid);
column6.Width = 50;
PdfGridColumn column7 = new(pdfGrid);
column7.Width = 50;
PdfGridColumn column8 = new(pdfGrid);
column8.Width = 60;
//Add eight columns.
pdfGrid.Columns.Add(column1);
pdfGrid.Columns.Add(column2);
pdfGrid.Columns.Add(column3);
pdfGrid.Columns.Add(column4);
pdfGrid.Columns.Add(column5);
pdfGrid.Columns.Add(column6);
pdfGrid.Columns.Add(column7);
pdfGrid.Columns.Add(column8);
//Set alignment of columns 3 to 8
column3.Format.Alignment = PdfTextAlignment.Right;
column4.Format.Alignment = PdfTextAlignment.Right;
column5.Format.Alignment = PdfTextAlignment.Right;
column6.Format.Alignment = PdfTextAlignment.Right;
column7.Format.Alignment = PdfTextAlignment.Right;
column8.Format.Alignment = PdfTextAlignment.Right;
//Add header.
pdfGrid.Headers.Add(1);
PdfGridRow pdfGridHeader = pdfGrid.Headers[0];
pdfGridHeader.Cells[0].Value = "Code";
pdfGridHeader.Cells[1].Value = "Description";
pdfGridHeader.Cells[2].Value = "Quantity";
pdfGridHeader.Cells[3].Value = "Unit Price";
pdfGridHeader.Cells[4].Value = "Net Price";
pdfGridHeader.Cells[5].Value = "Tax Rate";
pdfGridHeader.Cells[6].Value = "Tax";
pdfGridHeader.Cells[7].Value = "Total";
//Applying built-in style to the PDF grid
pdfGrid.ApplyBuiltinStyle(PdfGridBuiltinStyle.GridTable4Accent1);
//We want the grid header to be in bold. To do this:
//Create an instance of PdfGridRowStyle
PdfGridRowStyle pdfGridRowStyle = new ();
pdfGridRowStyle.Font = new PdfStandardFont(PdfFontFamily.Helvetica, 8, PdfFontStyle.Bold);
//Set style for the header
pdfGrid.Headers.ApplyStyle(pdfGridRowStyle);
}
}
}
}
}
EmailSenderMailKit.cs
using MailKit.Net.Smtp;
using MailKit.Security;
using MimeKit;
using MimeKit.Text;
using System;
using System.Collections.Generic;
using System.IO;
namespace BlazorPurchaseOrders.Data
{
public interface IEmailService
{
bool Send(string SmtpHost,
int SmtpPort,
string SmtpUserFriendlyName,
string SmtpUserEmailAddress,
string SmtpPass,
string to,
string subject,
string body,
string filename,
string attachmentName);
}
public class EmailService : IEmailService
{
public bool Send(string SmtpHost,
int SmtpPort,
string SmtpUserFriendlyName,
string SmtpUserEmailAddress,
string SmtpPass,
string to,
string subject,
string body,
string filename,
string attachmentName)
{
// create message
var email = new MimeMessage();
email.From.Add(new MailboxAddress(SmtpUserFriendlyName, SmtpUserEmailAddress));
email.To.Add(new MailboxAddress(to, to));
email.Subject = subject;
var msgbody = new BodyBuilder
{
HtmlBody = string.Format(body),
TextBody = body
};
msgbody.Attachments.Add(filename);
foreach (var file in msgbody.Attachments)
{
file.ContentDisposition.FileName = attachmentName;
}
email.Body = msgbody.ToMessageBody();
try
{
// send email
using var smtp = new SmtpClient();
smtp.Connect(SmtpHost, SmtpPort, SecureSocketOptions.StartTls);
smtp.Authenticate(SmtpUserEmailAddress, SmtpPass);
smtp.Send(email);
smtp.Disconnect(true);
return true;
}
catch
{
return false;
}
}
}
}EmailSenderSendGrid.cs
using System;
using System.IO;
using System.Threading.Tasks;
using SendGrid;
using SendGrid.Helpers.Mail;
namespace BlazorPurchaseOrders.Data
{
public interface IEmailSender
{
Task<bool> SendEmail(string apiKey,
string FromFriendlyName,
string FromEmailAddress,
string toEmail,
string subject,
string body,
string filename,
string attachmentName);
}
public class EmailSender : IEmailSender
{
public async Task<bool> SendEmail(string apiKey,
string FromFriendlyName,
string FromEmailAddress,
string toEmail,
string subject,
string body,
string filename,
string attachmentName)
{
var client = new SendGridClient(apiKey);
var from = new EmailAddress(FromEmailAddress, FromFriendlyName);
var to = new EmailAddress(toEmail, toEmail);
var plainTextContent = body;
var htmlContent = body;
var msg = MailHelper.CreateSingleEmail(from, to, subject, plainTextContent, htmlContent);
var bytes = File.ReadAllBytes(filename);
var file = Convert.ToBase64String(bytes);
msg.AddAttachment(attachmentName, file);
var response = await client.SendEmailAsync(msg).ConfigureAwait(false);
return response.IsSuccessStatusCode;
}
}
}
Startup.cs
Add the following line below the other 'services' lines:
services.AddSingleton<CreatePurchaseOrderPdf>();