Quote of the Day

more Quotes

Categories

Get notified of new posts

Buy me coffee

Displaying PDFs in Blazor.

Published November 24, 2023 in Web Development - 0 Comments

In this post, I go over a few options for displaying a PDF in a Blazor app, from the straightforward iframe src attribute embedding to ultilizing IJSRuntime for integration with JavaScript.


If you can access the PDF directly via an open GET endpoint, the easiest way is to embed the PDF in an iframe by setting the `src` attribute to the URL of the PDF, as shown in the snippet below generated by ChatGPT.

@page "/pdfviewer"

<h3>PDF Viewer</h3>

<iframe src="@PdfUrl" width="800" height="600" frameborder="0"></iframe>

@code {
    // Replace this URL with the actual URL of the PDF from your open REST API
    private string PdfUrl = "https://example.com/api/get-pdf";
}

However, if you need to include a bearer or security token in the header when calling the URL, the above method does not work since you don’t have a way to customize the header via src or other attributes of the iframe. One approach in this case is to programmatically construct the request to retrieve the PDF content as a byte array and set the data in the src attribute of the iframe, as shown in the below snippet generated by ChatGPT.

@page "/pdfviewer"

<h3>PDF Viewer</h3>

<iframe src="@PdfBase64" width="800" height="600" frameborder="0"></iframe>

@code {
    private string PdfBase64;

    protected override async Task OnInitializedAsync()
    {
        // Replace these values with your actual API endpoint and authentication logic
        var apiUrl = "https://example.com/api/get-pdf";
        var authToken = "YourAuthToken";

        // Call the helper method to retrieve the Base64-encoded PDF content
        PdfBase64 = await GetBase64Pdf(apiUrl, authToken);
    }

    private async Task<string> GetBase64Pdf(string apiUrl, string authToken)
    {
        using (var httpClient = new HttpClient())
        {
            // Set up headers, including authentication token if needed
            if (!string.IsNullOrEmpty(authToken))
            {
                httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
            }

            // Make the API request to get the PDF content
            var response = await httpClient.GetAsync(apiUrl);

            if (response.IsSuccessStatusCode)
            {
                // Convert the response content to Base64
                var pdfBytes = await response.Content.ReadAsByteArrayAsync();
                var base64String = Convert.ToBase64String(pdfBytes);
                return $"data:application/pdf;base64,{base64String}";
            }
            else
            {
                // Handle error scenarios based on your application needs
                // For simplicity, we return an empty string in case of an error
                return string.Empty;
            }
        }
    }
} 

The above solution works, but it copies the content of the PDF into memory and may cause performance issues for large PDFs. A more efficient solution is to stream the PDF content from the endpoint and use JavaScript to construct an object URL, which can be directly assigned to the src attribute of the iframe, as shown in the snippet below generated by ChatGPT.

@page "/pdfviewer"

<h3>PDF Viewer</h3>

<iframe id="pdfIframe" width="800" height="600" frameborder="0"></iframe>

@code {
    private string PdfContent;

   [Inject]
    private IJSRuntime JSRuntime { get; set; }

    protected override async Task OnInitializedAsync()
    {
        // Replace these values with your actual API endpoint and authentication logic
        var apiUrl = "https://example.com/api/get-pdf";
        var authToken = "YourAuthToken";

        // Call the helper method to retrieve the PDF content
        PdfContent = await GetPdfContent(apiUrl, authToken);
    }

    private async Task<string> GetPdfContent(string apiUrl, string authToken)
    {
        using (var httpClient = new HttpClient())
        {
            // Set up headers, including authentication token if needed
            if (!string.IsNullOrEmpty(authToken))
            {
                httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
            }

            // Make the API request to get the PDF content
            var response = await httpClient.GetAsync(apiUrl);

            if (response.IsSuccessStatusCode)
            {
                // Read the PDF content as a string
                return await response.Content.ReadAsStringAsync();
            }
            else
            {
                // Handle error scenarios based on your application needs
                // For simplicity, we return an empty string in case of an error
                return string.Empty;
            }
        }
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            // Use JavaScript to set the iframe src attribute with the PDF content
            await JSRuntime.InvokeVoidAsync("createPdfObjectUrl", PdfContent, "pdfIframe");
        }
    }
}
async function createPdfObjectUrl(stream, elementId) {
    const buffer = await stream.arrayBuffer();
    const blob = new Blob([buffer], { type: 'application/pdf' });
    const objectUrl = URL.createObjectURL(blob);
    const iframe = document.getElementById(elementId);
    iframe.src = objectUrl;

    // We don't need to keep the object url, let's release the memory
    URL.revokeObjectURL(objectUrl);
}

The above example loads the PDF content inside OnInitializedAsync() and calls the JavaScript function inside OnAfterRenderAsync(), using IJSRuntime. You can also load the PDF and call the JavaScript function on demand, for example, when the user clicks on a button.

The trick to the above solution is to realize that you need to explicitly load the script file before you can call the JavaScript function from the Blazor component. In my app, which is a Blazor server app, I load the JavaScript file in _host.cshtml, but using _layout.cshtml works fine as well. In a web assembly project, you probably need to use index.html. The code snippet below from ChatGPT shows an example of loading the script in _host.cshtml.

<!DOCTYPE html>
<html>
<head>
    <!-- Other head elements -->

    <!-- Reference your JavaScript file -->
    <script src="js/pdfviewer.js"></script>
</head>
<body>
    <!-- The rest of your HTML content -->

    <div id="app">
        <component type="typeof(App)" render-mode="ServerPrerendered" />
    </div>

    <!-- Other body elements -->
</body>
</html>

The above example assumes the js directory is under the wwwroot folder. Placing the JavaScript file under the wwwroot directory makes referencing the files simple since you can treat the wwwroot as the root directory of your web app. However, you may want to group all the related codes and files for the component together. In such a case, you can place the JavaScript file under the same path where you have the Blazor component. I prefer grouping the files together if the JavaScript codes are specific to the component.

Collocated JS files are publicly addressable using the path to the file in the project:

  • Pages, views, and components from a collocated scripts file in the app:{PATH}/{PAGE, VIEW, OR COMPONENT}.{EXTENSION}.js
    • The {PATH} placeholder is the path to the page, view, or component.
    • The {PAGE, VIEW, OR COMPONENT} placeholder is the page, view, or component.
    • The {EXTENSION} placeholder matches the extension of the page, view, or component, either razor or cshtml.
ASP.NET Core Blazor JavaScript interoperability (JS interop) | Microsoft Learn

If you run into issues with calling the JavaScript function, inspect the browser and ensure that the JavaScript file has been loaded under Sources, and check the console for errors.

Happy coding!

References

Call JavaScript functions from .NET methods in ASP.NET Core Blazor | Microsoft Learn

ASP.NET Core Blazor JavaScript interoperability (JS interop) | Microsoft Learn

My experience in using GitHub Copilot in Visual Studio and Visual Studio Code.

Published October 1, 2023 in Tools - 0 Comments

I have been using GitHub Copilot in both Visual Studio and Visual Studio Code. More often than not, I have found the tool to be a great companion when programming. However, I have also had negative experiences in which the tool provides misinformation, resulting in wasted energy and effort.

Continue reading

Cache angular components using RouteReuseStrategy

Published April 8, 2023 in Angular - 0 Comments

The project I have worked on has several pages that load data in Angular Material tables. The application’s user interface consists of a left menu with multiple tabs, and clicking on each tab loads the corresponding component and its child on the right. However, initializing the component appears to be an expensive operation, as it takes time to load the parent and child components and initialize the Material table, especially when there is a considerable amount of data to be displayed.

This delay causes the application to become unresponsive, especially when the user switches between tabs quickly, causing the components to pile up. I initially thought that the issue was related to fetching data through the network, but caching the data did not help to improve the performance.

After researching the topic of reusing components, I discovered the RouteReuseStrategy class. This class provides hooks for developers to advise Angular on when to reuse a route and how to cache a route. By utilizing this class, we can avoid the expensive process of destroying and initializing the components and displaying data in the table.

Continue reading

Using MSAL angular to authenticate a user against azure ADB2C via authorization code flow with Proof Key for Code Exchange.

Published March 2, 2023 in Angular , Azure , Azure ADB2C , OAuth2 , OpenID Connect , security - 1 Comment

Previously, I switched from using oidc-client to MSAL Angular to integrate an Angular app with Azure AD for authentication. If you’re interested, you can read more here. More recently, I used MSAL Angular again to connect another application, but this time to an Azure AD B2C tenant. Since I had prior experience and Microsoft provided good documentation and sample projects to follow, connecting to Azure AD B2C using MSAL Angular was not too difficult. In this post, I share how I adapted the sample project provided by Microsoft to integrate the application with our Azure AD B2C tenant and describe a few minor obstacles that I encountered.

Continue reading

Using Azure Application Insights for centralized logging

Published February 1, 2023 in Azure , Logging - 1 Comment

A typical enterprise system usually runs on multiple servers behind a load balancer. The simplest logging option could be logging locally to a location on the server where the app runs. However, this setup makes triaging an issue using the logs difficult. The problem is more prominent if the system consists of multiple applications running on different servers, and the sessions are not sticky. When an error occurs, the developer has to go to the different servers and applications to gather the logs. Even when the developer has gathered all the logs, it may still be challenging to piece together the log events that belong to a same session in a chronological order to troubleshoot the issue.

Continue reading

Web scraping in C# using HtmlAgilityPack

Published October 16, 2022 in .NET , .NET core , C# - 0 Comments

In this post, I show an example of scraping data in C# using HtmlAgilityPack. I come across HtmlAgilityPack because I need to get data from Zillow to analyze properties deals. I was able to scrape the data I want without much trouble using HtmlAgilityPack with a bit of XPath, LINQ and regular expression.

Continue reading

Easily test sending and receiving email locally using MailHog

Published September 23, 2022 in Java , Testing - 0 Comments

A java application which I work on has a feature to send emails via the corporate SMTP server to notify certain personnel whenever an error occurs. For security reasons, only servers within a certain networks can access the SMTP server and send emails. For instance, the computer which I use to build the application does not have access to the SMTP server. When searching for an email testing tool, I stumbled upon MailHog. Within minutes, I was able to run MailHog and test sending emails without having to deploy the app to the remote servers.

Continue reading

Building multitenant application – Part 3: Authentication

Published August 20, 2022 in Azure , Azure Active Directory - 0 Comments

In this post, I continue to share what I have learned while building a multitenant application by ultilizing Microsoft Identity Framework and SQL role level security. Specifically, I share what I have learned following Microsoft example project and documentations to make authentication works in a multitenant environment.

Continue reading

Building multitenant application – Part 2: Storing value into database session context from ASP.NET core web API

Published August 13, 2022 in .NET , .NET core , ASP.NET core , Azure SQL database - 0 Comments

In the previous post about row level security in SQL server, I gave an example of reading a value from the database session context to feed into the security function for filtering data. In this post, I show examples of calling the sp_set_session_context store procedure from an ASP.NET core web API to store a value into the session context.

Continue reading