From 24be76616058f259104a67ebc01fcabe1ff7255b Mon Sep 17 00:00:00 2001 From: Vikram Reddy Date: Mon, 22 Jun 2026 20:46:27 +0530 Subject: [PATCH] Enhance PdfViewer download functionality Refactored the `DownloadPdf` implementation in `PdfViewer.razor.cs` and `PdfViewerJsInterop.cs` to support downloading PDFs from both base64 data and validated URLs. Added error handling for empty URLs and introduced unique filename generation for downloads. Updated JavaScript functions in `blazor.bootstrap.pdf.js` to align with the new interop methods and improve maintainability. --- .../Components/PdfViewer/PdfViewer.razor.cs | 9 +-- .../PdfViewer/PdfViewerJsInterop.cs | 16 ++--- .../wwwroot/blazor.bootstrap.pdf.js | 62 ++++++++++++++++--- 3 files changed, 68 insertions(+), 19 deletions(-) diff --git a/blazorbootstrap/Components/PdfViewer/PdfViewer.razor.cs b/blazorbootstrap/Components/PdfViewer/PdfViewer.razor.cs index 8d59fe9e4..c1d1b82cb 100644 --- a/blazorbootstrap/Components/PdfViewer/PdfViewer.razor.cs +++ b/blazorbootstrap/Components/PdfViewer/PdfViewer.razor.cs @@ -113,6 +113,11 @@ public void SetPdfViewerMetaData(PdfViewerModel pdfViewerModel) OnPageChanged.InvokeAsync(new PdfViewerEventArgs(pageNumber, pagesCount)); } + private async Task DownloadPdf() + { + await PdfViewerJsInterop!.DownloadPdfAsync(objRef!, Id!, Url!); + } + private async Task FirstPageAsync() => await PdfViewerJsInterop!.FirstPageAsync(objRef!, Id!); private int GetZoomPercentage(int zoomLevel) => @@ -225,10 +230,6 @@ private async Task ZoomOutAsync() await PdfViewerJsInterop!.ZoomInOutAsync(objRef!, Id!, scale); } - private async Task DownloadPdf() - { - await PdfViewerJsInterop!.DownloadPdfAsync(Url); - } #endregion #region Properties, Indexers diff --git a/blazorbootstrap/Components/PdfViewer/PdfViewerJsInterop.cs b/blazorbootstrap/Components/PdfViewer/PdfViewerJsInterop.cs index 15ba1b6d9..6add82a0f 100644 --- a/blazorbootstrap/Components/PdfViewer/PdfViewerJsInterop.cs +++ b/blazorbootstrap/Components/PdfViewer/PdfViewerJsInterop.cs @@ -1,5 +1,4 @@ - -namespace BlazorBootstrap; +namespace BlazorBootstrap; public class PdfViewerJsInterop : JsInteropBase { @@ -14,6 +13,14 @@ public PdfViewerJsInterop(IJSRuntime jsRuntime) #region Methods + public async Task DownloadPdfAsync(object objRef, string elementId, string url) + { + if (string.IsNullOrWhiteSpace(url)) + throw new ArgumentException("URL cannot be null or empty.", nameof(url)); + + await SafeInvokeVoidAsync("downloadPdf", objRef, elementId, url); + } + public async Task FirstPageAsync(object objRef, string elementId) { await SafeInvokeVoidAsync("firstPage", objRef, elementId); @@ -58,10 +65,5 @@ public async Task ZoomInOutAsync(object objRef, string elementId, double scale) { await SafeInvokeVoidAsync("zoomInOut", objRef, elementId, scale); } - - public async Task DownloadPdfAsync(string fileUrl) - { - await SafeInvokeVoidAsync("downloadPdf", fileUrl); - } #endregion } diff --git a/blazorbootstrap/wwwroot/blazor.bootstrap.pdf.js b/blazorbootstrap/wwwroot/blazor.bootstrap.pdf.js index 2f6cd2efd..536eb3bb3 100644 --- a/blazorbootstrap/wwwroot/blazor.bootstrap.pdf.js +++ b/blazorbootstrap/wwwroot/blazor.bootstrap.pdf.js @@ -51,6 +51,24 @@ class Pdf { } } +export function downloadPdf(dotNetHelper, elementId, url) { + if (url) { + if (url.indexOf('data:') === 0) { + const split = url.split(','); + const base64Data = split.length > 1 ? split[1] : ''; + try { + saveAsFileFromBase64Data(base64Data); + } catch (e) { + console.error('Failed to trigger PDF download:', e); + } + } else { + saveAsFileFromUrl(url); + } + } else { + console.error('Pdf Url empty'); + } +} + export function firstPage(dotNetHelper, elementId) { const pdf = getPdf(elementId); @@ -65,6 +83,11 @@ export function firstPage(dotNetHelper, elementId) { setPdfViewerMetaData(dotNetHelper, pdf); } +function getUUIDPDFName(prefix = 'document') { + const uuid = self.crypto.randomUUID(); // Built-in browser API + return `${prefix}-${uuid}.pdf`; +} + export function gotoPage(dotNetHelper, elementId, gotoPageNum) { const pdf = getPdf(elementId); @@ -170,6 +193,37 @@ export function rotate(dotNetHelper, elementId, rotation) { queueRenderPage(pdf, pdf.pageNum); } +function saveAsFileFromBase64Data(base64Data) { + const link = document.createElement('a'); + link.download = getUUIDPDFName(); + link.href = "data:application/pdf;base64," + base64Data; + link.style.display = 'none'; // Avoid potential layout/scroll quirks + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); +} + +function saveAsFileFromUrl(url) { + // Validate the URL to ensure it uses an allowed scheme + const allowedSchemes = ['http:', 'https:', 'blob:']; + try { + const parsedUrl = new URL(url, window.location.origin); + if (!allowedSchemes.includes(parsedUrl.protocol)) { + console.error('Invalid URL scheme:', parsedUrl.protocol); + return; + } + } catch (e) { + console.error('Invalid URL:', e); + return; + } + + var link = document.createElement('a'); + link.href = url; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); +} + export function zoomInOut(dotNetHelper, elementId, scale) { const pdf = getPdf(elementId); @@ -182,14 +236,6 @@ export function zoomInOut(dotNetHelper, elementId, scale) { queueRenderPage(pdf, pdf.pageNum); } -export function downloadPdf(fileUrl) { - const link = document.createElement('a'); - link.href = fileUrl; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); -} - // resize // print // download