<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Fritz on the Web</title>
	<atom:link href="https://jeffreyfritz.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://jeffreyfritz.com</link>
	<description>My little corner of the web, full of my point of view</description>
	<lastBuildDate>Mon, 08 Jul 2024 23:25:07 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.5.8</generator>
	<item>
		<title>Adding Antivirus to .NET Aspire Systems</title>
		<link>https://jeffreyfritz.com/2024/07/adding-antivirus-to-net-aspire-systems/</link>
					<comments>https://jeffreyfritz.com/2024/07/adding-antivirus-to-net-aspire-systems/#respond</comments>
		
		<dc:creator><![CDATA[Jeff]]></dc:creator>
		<pubDate>Mon, 08 Jul 2024 22:38:33 +0000</pubDate>
				<category><![CDATA[.NET]]></category>
		<category><![CDATA[ASP.NET]]></category>
		<category><![CDATA[.net aspire]]></category>
		<category><![CDATA[asp.net]]></category>
		<category><![CDATA[docker]]></category>
		<guid isPermaLink="false">https://jeffreyfritz.com/?p=11659</guid>

					<description><![CDATA[In this article, I introduce a proof-of-concept project that adds the open source ClamAV antivirus scanner to a .NET Aspire system and shares connection information so that other resources can request malware scans.]]></description>
										<content:encoded><![CDATA[
<p>I was working on a web application over the weekend and I needed to add a feature that would allow images to be uploaded by end-users.  As we all know, we should never trust content uploaded by anonymous &#8216;friends&#8217; on the internet.  I wanted to add malware scanning to my project, but how?  In this article, I introduce a proof-of-concept project that adds the open source ClamAV antivirus scanner to a .NET Aspire system and shares connection information so that other resources can request malware scans.</p>



<h2 class="wp-block-heading">The idea</h2>



<p>I stumbled on a <a href="https://medium.com/@jeroenverhaeghe/building-a-malware-and-antivirus-scanner-in-net-core-e4309f2d429c" target="_blank" rel="noreferrer noopener nofollow">medium article by Jeroen Verhaeghe</a> where the <a href="https://www.clamav.net/" target="_blank" rel="noreferrer noopener nofollow">ClamAV antivirus scanner</a> was introduced in combination with the <a href="https://github.com/tekmaven/nClam" data-type="link" data-id="https://github.com/tekmaven/nClam" target="_blank" rel="noreferrer noopener nofollow">nClam library</a> from <a href="https://github.com/tekmaven" target="_blank" rel="noreferrer noopener nofollow">Ryan Hoffman</a>. &nbsp;Jeroen shows us how the ClamAV scanner can be run in a <a href="https://registry.hub.docker.com/r/clamav/clamav" target="_blank" rel="noreferrer noopener nofollow">Docker container</a>, and in reading that I thought &#8211; why not let .NET Aspire manage the container and its communications for us?</p>



<h2 class="wp-block-heading">The Proof of Concept Code</h2>



<p>ClamAV <a href="https://docs.clamav.net/manual/Installing/Docker.html" target="_blank" rel="noreferrer noopener nofollow">publishes a self-maintaining docker container</a> that updates itself with latest malware definitions.  We know that containers can be added to a .NET Aspire project, so I wrote a quick resource that shared an HttpEndpoint:</p>



<script src="https://gist.github.com/csharpfritz/a06d6a20b98b5ec259c49e5ac0f7ab49.js?file=clamav-resource.cs"></script>



<p>Now we&#8217;re talking!  We can now reference a ClamAV container resource easily in our .NET Aspire AppHost project and pass it to our website like this:</p>



<script src="https://gist.github.com/csharpfritz/a06d6a20b98b5ec259c49e5ac0f7ab49.js?file=program.cs"></script>



<figure class="wp-block-image size-large"><a href="https://jeffreyfritz.com/wp-content/uploads/2024/07/dashboard.png"><img fetchpriority="high" decoding="async" width="1024" height="335" src="https://jeffreyfritz.com/wp-content/uploads/2024/07/dashboard-1024x335.png" alt=".NET Aspire Dashboard showing the ClamAV antivirus resource" class="wp-image-11663" srcset="https://jeffreyfritz.com/wp-content/uploads/2024/07/dashboard-1024x335.png 1024w, https://jeffreyfritz.com/wp-content/uploads/2024/07/dashboard-300x98.png 300w, https://jeffreyfritz.com/wp-content/uploads/2024/07/dashboard-768x252.png 768w, https://jeffreyfritz.com/wp-content/uploads/2024/07/dashboard-624x204.png 624w, https://jeffreyfritz.com/wp-content/uploads/2024/07/dashboard.png 1108w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></figure>



<p>We can now scan our uploaded files with the nClam library like this:</p>



<script src="https://gist.github.com/csharpfritz/a06d6a20b98b5ec259c49e5ac0f7ab49.js?file=service.cs"></script>



<p>That&#8217;s pretty easy to get started with&#8230; and there&#8217;s a lot more that we can do with this to help make our public file uploads secure by default.</p>



<h2 class="wp-block-heading">Get the Code!</h2>



<p>I&#8217;ve published my sample code, showing a Blazor application uploading files to a minimal API endpoint where ClamAV inspects the content and reports back whether malware was detected.  You can find it on my GitHub at <a href="https://github.com/csharpfritz/AspireAntivirus">https://github.com/csharpfritz/AspireAntivirus</a></p>



<p>What do you think?  Is this something that I should spend some more time wrapping and making easier to use?  Create an issue on the GitHub repository if there&#8217;s something I can improve or a comment below to let me know what you think.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://jeffreyfritz.com/2024/07/adding-antivirus-to-net-aspire-systems/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Seamless Navigation in .NET MAUI Hybrid Apps &#8211; My .NET MAUI Hybrid Journey (part 1)</title>
		<link>https://jeffreyfritz.com/2024/04/seamless-navigation-in-net-maui-hybrid-apps-my-net-maui-hybrid-journey-part-1/</link>
		
		<dc:creator><![CDATA[Jeff]]></dc:creator>
		<pubDate>Fri, 12 Apr 2024 14:40:26 +0000</pubDate>
				<category><![CDATA[.NET]]></category>
		<category><![CDATA[.NET MAUI]]></category>
		<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[blazor]]></category>
		<guid isPermaLink="false">https://jeffreyfritz.com/?p=11637</guid>

					<description><![CDATA[As a seasoned web developer, the thought of diving into native mobile or desktop application development has always been a daunting prospect. The world of native apps seemed like a distant land, far removed from the comfort zone of HTML, CSS, and JavaScript that I had grown so accustomed to. However, the emergence of **.NET MAUI** and its seamless integration with **Blazor** has opened a new door, one that bridges the gap between web and native app development.


The decision to migrate the TagzApp application to run as a native desktop application was not made lightly. It represented a significant shift in my development paradigm. The application's simple layout, with its header menu bar, seemed like the perfect starting point for this transition. Yet, as I delved deeper into the process, I quickly realized that menus in a native context were not as straightforward as they appeared.]]></description>
										<content:encoded><![CDATA[
<p>I&#8217;ve always wanted to build a native application with all the insight and expertise that I&#8217;ve accumulated in building web applications, it&#8217;s felt like a big jump for me to get into native mobile or desktop applications. A long time ago I used to build windows forms applications for my employer, and I haven&#8217;t worked a desktop application since.</p>



<p>With the advent of <a href="https://dotnet.microsoft.com/en-us/apps/maui" target="_blank" rel="noreferrer noopener">.NET MAUI</a> and the ability to build Blazor applications for a website and share their pages and components for use in a native application felt like a real possibility for me. I haven&#8217;t really stepped into building and working working with this application model yet… until now.</p>



<p>This is the first of what I expect will be a series of blog posts describing things that I&#8217;ve discovered, challenges that I&#8217;ve overcome, and features as I migrate the <a href="https://tagzapp.net" target="_blank" rel="noreferrer noopener">TagzApp application</a> to run as a native desktop application.</p>



<h2 class="wp-block-heading">Menus &#8211; Not as Easy as They Look</h2>



<p>The first feature that I wanted to bring over was going to be the layout of the application. In TagzApp I have a very simple layout with a header menu bar with a couple of options to allow you to navigate to other areas of the application. It&#8217;s not too complex but it felt like an easy piece to migrate over to the hybrid application.</p>



<p>In doing some research and thinking more about how I want to represent a menubar inside of a native application it felt like it made more sense to turn this into a native menubar and not an HTML menubar that lived inside of my Blazor application. I started doing some research about how to create a menubar in .NET MAUI and found a few examples that showed how to use a tab bar to create and use multiple BlazorWebView components to represent different sections of the application. This felt clumsy to me because it meant that I would be spinning up multiple browsers to run inside of my application just to access and work with other parts of the application. I knew that that would mean more resources used by the computer when this application is running, and that felt a little irresponsible for me as a developer.</p>



<p>I wanted to actually have a menu bar with items that you would click and it would navigate inside of my Blazor application. Looking at the documentation for the BlazorWebView, there is no direct access to the NavigationManager or an ability to reset the location of the browser component. I set about to make the NavigationManager inside of Blazor accessible to the .NET MAUI application.</p>



<p>In the demos on this post, I&#8217;ll start with the default Blazor Hybrid template application and turn the vertical NavBar element into a native menu. <a href="https://github.com/csharpfritz/BlazorHybridNavigation" data-type="link" data-id="https://github.com/csharpfritz/BlazorHybridNavigation" target="_blank" rel="noreferrer noopener">Completed source code</a> for this sample is available on my GitHub.  I also have recorded a video where I talk through this demo:</p>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe title="Blazor Navigation with .NET MAUI Components" width="625" height="352" src="https://www.youtube.com/embed/hmHOnRNl_j0?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>


<div class="wp-block-image">
<figure class="aligncenter size-full"><a href="https://jeffreyfritz.com/wp-content/uploads/2024/04/image.png"><img decoding="async" width="738" height="560" src="https://jeffreyfritz.com/wp-content/uploads/2024/04/image.png" alt="" class="wp-image-11638" srcset="https://jeffreyfritz.com/wp-content/uploads/2024/04/image.png 738w, https://jeffreyfritz.com/wp-content/uploads/2024/04/image-300x228.png 300w, https://jeffreyfritz.com/wp-content/uploads/2024/04/image-624x473.png 624w" sizes="(max-width: 738px) 100vw, 738px" /></a><figcaption class="wp-element-caption">The default experience inside a Blazor Hybrid application with .NET 8</figcaption></figure></div>


<h2 class="wp-block-heading">Configuring the Shell and Menu Items</h2>



<p>To start with, I configured the App.xaml file to have a <a href="https://learn.microsoft.com/dotnet/maui/fundamentals/shell/" target="_blank" rel="noreferrer noopener">Shell</a> embedded directly and contain the BlazorWebView for my application.  This would allow me to add a Menubar to the Shell.</p>



<pre class="wp-block-code"><code>&lt;Application.MainPage&gt;
  &lt;Shell&gt;
    &lt;ShellContent&gt;
      &lt;ShellContent.ContentTemplate&gt;
        &lt;DataTemplate&gt;
          &lt;ContentPage&gt;
            &lt;BlazorWebView x:Name="blazorWebView1" HostPage="wwwroot/index.html"&gt;
              &lt;BlazorWebView.RootComponents&gt;
                &lt;RootComponent Selector="#app" ComponentType="{x:Type local:Components.Routes}" /&gt;
              &lt;/BlazorWebView.RootComponents&gt;
            &lt;/BlazorWebView&gt;
          &lt;/ContentPage&gt;
        &lt;/DataTemplate&gt;
      &lt;/ShellContent.ContentTemplate&gt;
    &lt;/ShellContent&gt;
  &lt;/Shell&gt;
&lt;/Application.MainPage&gt;
</code></pre>



<p>I also removed the call inside App.xaml.cs to set <code>MainPage = new MainPage(); </code> Since we&#8217;re specifying our own MainPage inside the XAML markup, there&#8217;s no need to instantiate another page.  I could run the application now, and I&#8217;d get the same user experience as the previous image.</p>



<p>Ok.. next steps&#8230;</p>



<h2 class="wp-block-heading">Adding a MenuBar component</h2>



<p>In .NET MAUI, the MenuBar component is added when you introduce MenubarItems.  No problem, I added a MenuBarItem and 3 MenuFlyoutItems for the 3 base pages inside the default application.  This code was added just inside the <code>ContentPage</code> element in App.xaml</p>



<pre class="wp-block-code"><code>&lt;ContentPage.MenuBarItems&gt;
  &lt;MenuBarItem Text="Content"&gt;
    &lt;MenuFlyoutItem Text="Home" Clicked="MenuItem_Clicked"&gt;&lt;/MenuFlyoutItem&gt;
    &lt;MenuFlyoutItem Text="Counter" Clicked="MenuItem_Clicked"&gt;&lt;/MenuFlyoutItem&gt;
    &lt;MenuFlyoutItem Text="Weather" Clicked="MenuItem_Clicked"&gt;&lt;/MenuFlyoutItem&gt;
  &lt;/MenuBarItem&gt;
&lt;/ContentPage.MenuBarItems&gt;</code></pre>



<p>Notice that I set each of the menu items to trigger the same event, <code>MenuItem_Clicked</code>  All of these menu items do the same thing, but vary in the location they target.  We&#8217;ll write this method in a little bit, because we need to first make the NavigationManager available</p>



<h2 class="wp-block-heading">Enabling the NavigationManager in .NET MAUI</h2>



<p>The Blazor NavigationManager isn&#8217;t directly accessible in .NET MAUI.  You can&#8217;t inject it or reach into the BlazorWebView and interact with it.  Instead, we need to create a service that will allow us to capture the NavigationManager and interact with it.  The curious part of this is that both parts of the application model, .NET MAUI and Blazor use the same dependency injection services.  So&#8230;. we can exploit this to allow our service to be injected into both Blazor AND .NET MAUI.</p>



<p>No problem, I can whip up a little bit of code that allows both application models to work with the Blazor NavigationManager:</p>



<pre class="wp-block-code"><code>public class NavigatorService
{

  internal NavigationManager NavigationManager { get; set; }

}
</code></pre>



<p>I can then register this <code>NavigatorService</code> with the service locator in .NET MAUI with this line in the MauiProgram.cs file:</p>



<pre class="wp-block-code"><code>builder.Services.AddSingleton&lt;NavigatorService&gt;();</code></pre>



<p>I want this Navigator service on every page in my Blazor application, so I&#8217;ll inject it and configure the NavigationManager we&#8217;ll use inside the MainLayout.razor file:</p>



<pre class="wp-block-code"><code>@inherits LayoutComponentBase
@inject MauiApp1.NavigatorService NavigatorService
@inject NavigationManager NavigationManager

&lt;div class="page"&gt;
...
&lt;/div&gt;

@code {

  protected override void OnInitialized()
  {

    NavigatorService.NavigationManager = NavigationManager;

  }

}</code></pre>



<p>Finally, I&#8217;ll add the NavigatorService to my App.xaml.cs code so that it is injected and stored as a property for use later:</p>



<pre class="wp-block-code"><code>public partial class App : Application
{
  public App(NavigatorService navigatorService)
  {
    InitializeComponent();
    NavigatorService = navigatorService;
  }

  internal NavigatorService NavigatorService { get; }

  private void MenuItem_Clicked(object sender, EventArgs e)
  {
  }
}</code></pre>



<h2 class="wp-block-heading">Connecting and Navigating from the MenuBar</h2>



<p>Now, we can use a switch statement to configure the navigation of the BlazorWebView.  Let&#8217;s add that switch inside the <code>Menuitem_Clicked</code> method:</p>



<pre class="wp-block-code"><code>private void MenuItem_Clicked(object sender, EventArgs e)
{

  var menuItem = (MenuItem)sender;
  var url = menuItem.Text switch
  {
    "Counter" =&gt; "/counter",
    "Weather" =&gt; "/weather",
    _ =&gt; "/"
  };
  NavigatorService.NavigationManager.NavigateTo(url);

}</code></pre>



<figure class="wp-block-image size-full"><a href="https://jeffreyfritz.com/wp-content/uploads/2024/04/image-1.png"><img loading="lazy" decoding="async" width="832" height="650" src="https://jeffreyfritz.com/wp-content/uploads/2024/04/image-1.png" alt="" class="wp-image-11639" srcset="https://jeffreyfritz.com/wp-content/uploads/2024/04/image-1.png 832w, https://jeffreyfritz.com/wp-content/uploads/2024/04/image-1-300x234.png 300w, https://jeffreyfritz.com/wp-content/uploads/2024/04/image-1-768x600.png 768w, https://jeffreyfritz.com/wp-content/uploads/2024/04/image-1-624x488.png 624w" sizes="(max-width: 832px) 100vw, 832px" /></a><figcaption class="wp-element-caption">Application with the new MenuBar</figcaption></figure>



<p>Now, when we click the various items in the native MenuBar, the browser navigates appropriately.</p>



<p>For completeness, I removed the side navigation from the MainLayout.razor file so that the application felt more native and didn&#8217;t have 2 MenuBars.</p>



<figure class="wp-block-image size-full"><a href="https://jeffreyfritz.com/wp-content/uploads/2024/04/image-2.png"><img loading="lazy" decoding="async" width="832" height="650" src="https://jeffreyfritz.com/wp-content/uploads/2024/04/image-2.png" alt="" class="wp-image-11640" srcset="https://jeffreyfritz.com/wp-content/uploads/2024/04/image-2.png 832w, https://jeffreyfritz.com/wp-content/uploads/2024/04/image-2-300x234.png 300w, https://jeffreyfritz.com/wp-content/uploads/2024/04/image-2-768x600.png 768w, https://jeffreyfritz.com/wp-content/uploads/2024/04/image-2-624x488.png 624w" sizes="(max-width: 832px) 100vw, 832px" /></a></figure>



<h2 class="wp-block-heading">Summary</h2>



<p>This is just one creative way to connect our Blazor application to .NET MAUI and reuse the code we&#8217;ve already built in Blazor.  The <a href="https://github.com/csharpfritz/BlazorHybridNavigation" target="_blank" rel="noreferrer noopener">complete source code</a> for this sample is available on my GitHub. I&#8217;m working through an entire application for TagzApp, and will share more of my findings in the weeks ahead.</p>



<p>Have you tried using Blazor content in .NET MAUI, WPF, or Windows Forms?  What was your experience?  Let me know in the comments below</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>From IoT to the Cloud: A .NET Ecosystem Showcase with GitHub, Raspberry Pi, and Azure</title>
		<link>https://jeffreyfritz.com/2024/02/from-iot-to-the-cloud-a-net-ecosystem-showcase-with-github-raspberry-pi-and-azure/</link>
					<comments>https://jeffreyfritz.com/2024/02/from-iot-to-the-cloud-a-net-ecosystem-showcase-with-github-raspberry-pi-and-azure/#respond</comments>
		
		<dc:creator><![CDATA[Jeff]]></dc:creator>
		<pubDate>Mon, 26 Feb 2024 16:58:44 +0000</pubDate>
				<category><![CDATA[ASP.NET Core]]></category>
		<category><![CDATA[Azure]]></category>
		<guid isPermaLink="false">https://jeffreyfritz.com/?p=11630</guid>

					<description><![CDATA[As part of a demo to show a complete interconnected .NET ecosystem from IoT to the Cloud, I assembled a demo with the following components: &#8211; Raspberry Pi 3b (ARM32 processor, Wifi, 4GB RAM) &#8211; Touchscreen case from SmartPi-Touch &#8211; Azure Container Registry &#8211; GitHub Repository &#8211; GitHub action &#8211; Docker &#8211; Docker-Compose &#8211; .NET [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>As part of a demo to show a complete interconnected .NET ecosystem from IoT to the Cloud, I assembled a demo with the following components:</p>
<p>&#8211; <a href="https://www.raspberrypi.com/products/raspberry-pi-3-model-b/" target="_blank" rel="noopener">Raspberry Pi 3b (ARM32 processor, Wifi, 4GB RAM)</a><br />
&#8211; <a href="https://smarticase.com/products/smartipi-touch-2" target="_blank" rel="noopener">Touchscreen case from SmartPi-Touch</a><br />
&#8211; <a href="https://azure.microsoft.com/en-us/products/container-registry/" target="_blank" rel="noopener">Azure Container Registry</a><br />
&#8211; GitHub Repository<br />
&#8211; GitHub action<br />
&#8211; Docker<br />
&#8211; Docker-Compose<br />
&#8211; .NET 7 / ASP.NET Core / SignalR Core</p>
<p>The goal of this demo is to show how a connected IoT device, a Raspberry Pi, can run unattended and receive automatic updates from GitHub, Azure, refresh .NET content on a screen with no interruptions.</p>
<p>Prior to writing the software for this demo, I followed all of the instructions for the touch-screen and mounted the Raspberry Pi inside the case it provides.  This allows me to connect a keyboard and mouse as well as a power cable and work on the Raspberry Pi as a typical desktop workstation.</p>
<p><em>Note: This blog post are the notes from my construction of this demo.  I typically present and show this content on-stage and it is easily my most complex demo with moving pieces on a Raspberry Pi device that I show in my hands, Azure Container Registry, GitHub Codespaces and GitHub actions.  To make the demo a little more interesting, I&#8217;ll update some CSS (sometimes suggested by the audience) in the GitHub repository using GitHub codespaces on stage and the Raspberry Pi will update with the new look automatically.  I&#8217;d like to record a video showing this demo and update this post with a link to that demo<br />
</em></p>
<h2>Step 0: Configure an Azure Container Registry</h2>
<p>I already have one of these at `fritzregistry.azurecr.io`. It was easy enough to configure and deploy with credentials required to access the content.  Alternatively, you can also use the <a href="https://github.com/features/packages" target="_blank" rel="noopener">GitHub package repository features</a> and store containers there.</p>
<h2>Step 1: Configured Docker on the Raspberry Pi</h2>
<p>These instructions originally appeared at: <a href="https://raspberrytips.com/docker-on-raspberry-pi/" target="_blank" rel="noopener">https://raspberrytips.com/docker-on-raspberry-pi/</a></p>
<p>On the Raspberry Pi, I downloaded Docker with this command when running as root:</p>
<pre>curl -sSL https://get.docker.com | sh</pre>
<p>I then added myself to the `docker` group by running this as my standard user `jfritz`:</p>
<pre>sudo usermod -aG docker $USER</pre>
<p>To get connected with the Azure registry, I logged in with this command and specified the registry name, the user id and password displayed on the <strong>Access Keys</strong> panel:</p>
<p><div id="attachment_11631" style="width: 635px" class="wp-caption aligncenter"><a href="https://jeffreyfritz.com/wp-content/uploads/2024/02/Pasted-image-20230818165729.png"><img loading="lazy" decoding="async" aria-describedby="caption-attachment-11631" class="size-large wp-image-11631" src="https://jeffreyfritz.com/wp-content/uploads/2024/02/Pasted-image-20230818165729-1024x374.png" alt="Azure Portal and identifying the Access Keys for a container registry" width="625" height="228" srcset="https://jeffreyfritz.com/wp-content/uploads/2024/02/Pasted-image-20230818165729-1024x374.png 1024w, https://jeffreyfritz.com/wp-content/uploads/2024/02/Pasted-image-20230818165729-300x109.png 300w, https://jeffreyfritz.com/wp-content/uploads/2024/02/Pasted-image-20230818165729-768x280.png 768w, https://jeffreyfritz.com/wp-content/uploads/2024/02/Pasted-image-20230818165729-624x228.png 624w, https://jeffreyfritz.com/wp-content/uploads/2024/02/Pasted-image-20230818165729.png 1137w" sizes="(max-width: 625px) 100vw, 625px" /></a><p id="caption-attachment-11631" class="wp-caption-text">Azure Portal and identifying the Access Keys for a container registry</p></div></p>
<p>I could then login to the registry with these credentials on my Raspberry Pi by executing</p>
<pre>docker login</pre>
<p>This generated a `/home/.docker/config.json` file that will be used to login to my private Azure registry.</p>
<h2>Step 2: Configured Docker-compose</h2>
<p>For this system, I want to use Watchtower to monitor the container registry and install updates automatically.  In order to configure this with those dependencies, I needed Docker-componse. The prerequisites for Docker-Compose are installed with these commands:</p>
<pre>sudo apt-get install libffi-dev libssl-dev
sudo apt install python3-dev
sudo apt-get install -y python3 python3-pip</pre>
<p>I completed the installation of Docker-Compose with a pip3 install command:</p>
<pre>sudo pip3 install docker-compose</pre>
<h2>Step 3: Configure Watchtower</h2>
<p><a href="https://containrrr.dev/watchtower/" target="_blank" rel="noopener">Watchtower</a> is a container that will watch other containers and gracefully update them when updates are available. I grabbed the watchtower image for ARM devices using this docker command on the Raspberry Pi:</p>
<pre>docker pull containrrr/watchtower:armhf-latest</pre>
<p>I built a `docker-compose.yml` file with my desired watchtower configuration:</p>
<p><script src="https://gist.github.com/csharpfritz/9a259c1703effe7dbefa3be3af9bf226.js"></script></p>
<p>The `command` argument instructs watchtower to check for updated images every 30 seconds, and the `restart` argument instructs the container to start at startup and always restart when the watchtower stops.  More details about how to configure a Docker-compose file are available in their <a href="https://docs.docker.com/compose/compose-file/compose-file-v3/" target="_blank" rel="noopener">documentation</a>.</p>
<h2>Step 4 : Customize the Dockerfile to build for ARM32</h2>
<p>The device I have for this scenario is a Raspberry Pi 3B and has an ARM32 processor.  That can throw a little wrinkle into things because most systems now target ARM64 processors by default.  Not a problem, because there is still support for ARM32 available and we just need to specify it in our deployment scripts.</p>
<p>The application that I will be running on the device is a <a href="https://github.com/csharpfritz/demopi" target="_blank" rel="noopener">simple ASP.NET Core application</a> that counts the number of times the screen has been touched.  In a more complete scenario, there might be a sensor connected or some kiosk screen wired up that presents information and collects data.</p>
<p>I wrote a Dockerfile for ARM is called `Dockerfile-ARM32` with the following content:</p>
<p><script src="https://gist.github.com/csharpfritz/396c5581efd551b1217b11e51f6d5a53.js"></script></p>
<p>There&#8217;s an interesting bit in the middle where I copied in the `.git` folder. This allows my application to grab the latest git SHA hash as a bit of a version check for the source code. That SHA is made availalble on the assembly&#8217;s `AssemblyInformationalVersionAttribute` attribute value.</p>
<p>We can then build the container for my ASP.NET Core application to run on the Pi from my Windows workstation using this command:</p>
<pre>docker build --platform linux/arm -f .\Fritz.DemoPi\Dockerfile-ARM32 . /
-t fritz.demopi:4 /
-t fritz.demopi:latest /
-t fritzregistry.azurecr.io/fritz.demopi:4 /
-t fritzregistry.azurecr.io/fritz.demopi:latest</pre>
<p>and then push to my remote registry with:</p>
<pre>docker push fritzregistry.azurecr.io/fritz.demopi -a</pre>
<h2>Step 5: Configure Docker on the Pi to run the website</h2>
<p>By default ASP.NET Core configured port 8080 for the website inside the container. I wrote a quick `docker-compose.yml` script to run my .NET application with all of the configuration I would need:</p>
<p><script src="https://gist.github.com/csharpfritz/ace672e3d7d5bac55317ca6281e2a3ae.js"></script></p>
<p>To ensure that the SignalR bits of my demo would work, I removed a privacy extension from the Chromium browser that comes with the Raspberry Pi device.</p>
<h2>Step 6: Configure the Pi to boot into Chromium for the website</h2>
<p>In order to configure the Pi device to boot directly into a browser and my application running in the container, I added a file at `~/.config/lxsession/LXDE-pi` called `autostart` with this configuration:</p>
<pre>@lxpanel --profile LXDE-pi
@pcmanfm --desktop --profile LXDE-pi
#@xscreensaver -no-splash
point-rpi
@chromium-browser --start-fullscreen --start-maximized http://localhost/</pre>
<p>Additional options and the instructions I started with are at <a href="https://smarthomepursuits.com/open-website-on-startup-with-raspberry-pi-os/?expand_article=1" target="_blank" rel="noopener">https://smarthomepursuits.com/open-website-on-startup-with-raspberry-pi-os/?expand_article=1</a> where I learned how to write this script.</p>
<h2>Step 7: Prepare a GitHub action</h2>
<p>I configured a GitHub action to checkout my code and build in ARM32 format with the Dockerfile established previously.  This would also publish the resultant container to my Azure container registry:</p>
<p><script src="https://gist.github.com/csharpfritz/2d4ff2968b3c4df6dd9985c0600a8792.js"></script></p>
<p>Success!</p>
<h2>Summary</h2>
<p>As changes are made to the GitHub repository, the GitHub action will rebuild the image and deploy it to the Azure Container Registry. Watchtower identifies the update and automatically stops the existing application on the Pi and then deploys a new copy with the same settings. With a little SignalR work, the UI updates seamlessly.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://jeffreyfritz.com/2024/02/from-iot-to-the-cloud-a-net-ecosystem-showcase-with-github-raspberry-pi-and-azure/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Blazor and .NET 8: How I Built a Fast and Flexible Website</title>
		<link>https://jeffreyfritz.com/2024/02/blazor-and-net-8-how-i-build-a-fast-and-flexible-website/</link>
					<comments>https://jeffreyfritz.com/2024/02/blazor-and-net-8-how-i-build-a-fast-and-flexible-website/#comments</comments>
		
		<dc:creator><![CDATA[Jeff]]></dc:creator>
		<pubDate>Thu, 22 Feb 2024 15:47:54 +0000</pubDate>
				<category><![CDATA[ASP.NET Core]]></category>
		<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[asp.net core]]></category>
		<category><![CDATA[azure]]></category>
		<category><![CDATA[blazor]]></category>
		<guid isPermaLink="false">https://jeffreyfritz.com/?p=11619</guid>

					<description><![CDATA[I&#8217;ve been working on a new website for my series CSharp in the Cards. I built this website in a way that was easy to maintain, flexible and most importantly would respond quickly to requests from visitors.  I knew that Blazor with .NET 8 had a static server rendering feature and decided that I wanted [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>I&#8217;ve been working on a new website for my series <a href="https://csharpinthecards.com" target="_blank" rel="noopener">CSharp in the Cards</a>. I built this website in a way that was easy to maintain, flexible and most importantly would respond quickly to requests from visitors.  I knew that <a href="https://blazor.net" target="_blank" rel="noopener">Blazor with .NET 8</a> had a static server rendering feature and decided that I wanted to put it to the test. I recently published a new lesson to the website and included a web assembly component to allow for paging and filtering the list of lessons I was pleasantly surprised when I saw the performance dashboards on azure showing that it was handling requests and responding very very quickly.</p>
<p><div id="attachment_11620" style="width: 967px" class="wp-caption aligncenter"><a href="https://jeffreyfritz.com/wp-content/uploads/2024/02/Screenshot-2024-02-20-152034.png"><img loading="lazy" decoding="async" aria-describedby="caption-attachment-11620" class="size-full wp-image-11620" src="https://jeffreyfritz.com/wp-content/uploads/2024/02/Screenshot-2024-02-20-152034.png" alt="" width="957" height="473" srcset="https://jeffreyfritz.com/wp-content/uploads/2024/02/Screenshot-2024-02-20-152034.png 957w, https://jeffreyfritz.com/wp-content/uploads/2024/02/Screenshot-2024-02-20-152034-300x148.png 300w, https://jeffreyfritz.com/wp-content/uploads/2024/02/Screenshot-2024-02-20-152034-768x380.png 768w, https://jeffreyfritz.com/wp-content/uploads/2024/02/Screenshot-2024-02-20-152034-624x308.png 624w" sizes="(max-width: 957px) 100vw, 957px" /></a><p id="caption-attachment-11620" class="wp-caption-text">Response times of C# in the Cards after the new episode</p></div></p>
<p>In this blog post, let&#8217;s talk about how I&#8217;ve optimized the website for speed and some of the finishing touches that you can put on your Blazor website to make it screaming fast running on a very small instance of Azure App Service.</p>
<h2>Static Site Rendering &#8211; Its Blazor, but easier</h2>
<p>With .NET 8 there&#8217;s a new render mode for Blazor and it&#8217;s called static site rendering or SSR. This new render mode ditches all interactivity that we previously had with Blazor server side and Blazor Web Assembly and instead favors delivering HTML and other content from the server to browsers in a high speed manner. We can bolt on other techniques that we know from SEO and website optimization to make this even faster and deliver a great experience for our visitors.</p>
<p><script src="https://gist.github.com/csharpfritz/7f4a094f16d16614c94bffaa195307a2.js"></script></p>
<p>The <a href="https://csharpinthecards.com/about" target="_blank" rel="noopener">About page</a> is configured to output a bunch of HTML headers for the SEO folks and the social media pages to be able to present good information about the site.  Notice the headers that are added to satisfy the search engines:</p>
<ul>
<li>a canonical link element that identifies where the page should be served from</li>
<li>a keywords meta element with information about what you can find here</li>
<li>a robots element that tells the search engine crawlers what they can do with the page</li>
<li>open graph and Twitter meta tags that instruct Twitter, Facebook, LinkedIn, Discord, and other sites about the images, titles, and description of the page</li>
</ul>
<p>That&#8217;s fine&#8230; but there are two other features to notice:</p>
<ol>
<li>This is a static page with no data being presented.  I&#8217;ve tagged it on line 2 with an attribute to allow output caching for 600 seconds (10 minutes).  This way the web server doesn&#8217;t have to render a new copy when its requested within 10 minutes of a previous request.</li>
<li>The images references are in webp format.  Let&#8217;s not overlook this super-compressed format for displaying high-quality images.  It might be 2024, but every bit we deliver over the network still matters for performance and the 600&#215;600 portrait picture of myself on this page was compressed nicely:</li>
</ol>
<table>
<tbody>
<tr>
<th style="text-align: center;">Original</th>
<th style="text-align: center;">Compressed</th>
<th style="text-align: center;"># Difference</th>
</tr>
<tr>
<td style="text-align: center;">PNG</td>
<td style="text-align: center;">WEBP</td>
</tr>
<tr>
<td style="text-align: center;">450kb</td>
<td style="text-align: center;">30kb</td>
<td style="text-align: center;">-93.3%</td>
</tr>
</tbody>
</table>
<p>93% savings&#8230;  that&#8217;s crazy good and means that your browser is not downloading an extra 420kb it doesn&#8217;t need.</p>
<h2>Data is stored in static files on disk</h2>
<p>For this simple website I don&#8217;t need a big fancy database like SQL Server or Postgres or even MySQL. For this site, I&#8217;ve stored all of the data in a simple CSV file on disk.  That means that I can edit the list of articles that are available and the metadata that goes with them by just opening the file in Excel and writing new content. This means that when it comes time for me to read data about the list of content that&#8217;s available I&#8217;m only reading from a very small file on disk and I don&#8217;t need to worry about any kind of contention. I also don&#8217;t need to worry about any service that&#8217;s running to deliver that data because it&#8217;s only coming out of a small file on disk that&#8217;s read only.</p>
<p><script src="https://gist.github.com/csharpfritz/427d0e2747a0e3e065df7ee92fb80f6f.js?ts=2"></script></p>
<p>In this repository class I use the <a href="https://www.nuget.org/packages/LinqToCsv" target="_blank" rel="noopener">LinqToCSV library</a> to open and read all of the content from the file into a Post object in the first method, GetPostsFromDisk.  Later, in a public method called GetPosts, you see where I use the in memory cache feature of ASP.NET Core to fetch data from the cache if its available or get it from disk and store it in cache for 30 minutes.  I could probably extend this timeout to several hours or even days since the website doesn&#8217;t get any new content without uploading a new version of the site.</p>
<p>The key here is that the meta data about the lessons on the site is loaded and kept in memory.  As of episode 9 of the series, the posts.csv file is only 1.4kb so I have no worries about loading its entire contents into memory.</p>
<p>Don&#8217;t forget, in order to add the MemoryCache to your ASP.NET Core application, you need to add this line to your site configuration in the Program.cs file:</p>
<pre>builder.Services.AddMemoryCache();</pre>
<p>I could add other cache options like Redis to the site, but with how small the data I want to cache is, I don&#8217;t need that sophistication at this point.</p>
<h2>Pre-rendered Interactive Web Assembly Content is fast&#8230; REALLY fast</h2>
<p>I wanted to add a subset of the lessons to the front page of the website so that you could see the latest six episodes in the video series and scroll back and forth to the other episodes. This should be an interactive component but I still wanted the home page to render quickly and have a fresh speedy response time as you page through and look at the various episodes that are available. The natural way to do this with Blazor is to build a web assembly component that will run on the client and render data as users click on the buttons for that collection of articles.</p>
<p>I wrote a simple pager component that would receive a collection of lesson data and render cards for each lesson.  Since we already know that the collection of lesson data is less than 2kb in size I don&#8217;t have a problem sending the entire collection of data into the browser to be rendered.</p>
<p><script src="https://gist.github.com/csharpfritz/af863192bf3e1e4c5ec45b6b6a3bfab2.js"></script><br />
When I use the @rendermode attribute in this code, it forces the render mode to web assembly and ASP.NET will pre-render as well as cache a version of that component&#8217;s resultant HTML with the home page. After viewers download the Web Assembly content it will hand control over to web assembly and it will be a fully interactive component for them to be able to work with.</p>
<p><div id="attachment_11623" style="width: 310px" class="wp-caption aligncenter"><a href="https://jeffreyfritz.com/wp-content/uploads/2024/02/pager.png"><img loading="lazy" decoding="async" aria-describedby="caption-attachment-11623" class="size-medium wp-image-11623" src="https://jeffreyfritz.com/wp-content/uploads/2024/02/pager-300x188.png" alt="Lesson Pager on the C# in the Cards website" width="300" height="188" srcset="https://jeffreyfritz.com/wp-content/uploads/2024/02/pager-300x188.png 300w, https://jeffreyfritz.com/wp-content/uploads/2024/02/pager-768x481.png 768w, https://jeffreyfritz.com/wp-content/uploads/2024/02/pager-624x390.png 624w, https://jeffreyfritz.com/wp-content/uploads/2024/02/pager.png 951w" sizes="(max-width: 300px) 100vw, 300px" /></a><p id="caption-attachment-11623" class="wp-caption-text">Lesson Pager on the C# in the Cards website</p></div></p>
<p>Blazor lets me build content to be rendered on the web and I get to choose where exactly it should run. It can run in the browser with web assembly it can run statically on the server it can run interactively on the server if I want it to. In this case running as web assembly gives a really nice usability effect that makes it easy for viewers to locate the content they want to watch.</p>
<h2>Compress the Content from Kestrel</h2>
<p>By default content that&#8217;s delivered from the ASP.NET kestrel web server is uncompressed. We can add brotli compression to the Web server and deliver content in a much smaller package to our visitors with just a few simple lines of code in program.cs. This is something that I think everybody should do with their Internet facing websites:</p>
<pre>#if (!DEBUG)
builder.Services.AddResponseCompression(options =&gt;
{
options.EnableForHttps = true;
});
#endif</pre>
<p>Add response compression configures the server so that it will deliver broadly compressed content. In this application I wrap it with the conditional debug detection because hot reload does not work with compression enabled.  When we deliver the website to the production web host it will be running in release mode and compression will be enabled.</p>
<h2>Optimize all the JavaScript and CSS</h2>
<p>CSS AND JavaScript can be minified and combined to reduce the number and size of downloads for this static content that makes our websites look good.  For this website I installed and used the <a href="https://www.nuget.org/packages/LigerShark.WebOptimizer.Core" target="_blank" rel="noopener">WebOptimizer package</a> available on NuGet.  My configuration for this looks like the following:</p>
<p><script src="https://gist.github.com/csharpfritz/e3c3c40fb71574c9334fb300c19151b6.js"></script></p>
<p>This script bundles the CSS files that were delivered with my website template and minifies the one JavaScript file that I manage with my project.</p>
<h2>Set long cache-control headers for static content</h2>
<p>The last thing that I did was set long duration cash dash control headers for static content like images CSS and Javascript files. This is easy to do with just a few more lines of optional configuration when I configure the static file feature inside of ASP.NET Core:</p>
<p><script src="https://gist.github.com/csharpfritz/582b1b408d1b5480e8eac18a27898173.js"></script></p>
<h2>Summary</h2>
<p>This website&#8217;s been easy for me to build because I can rely on my normal HTML skills and the plethora of HTML templates and CSS libraries out there to make my website look good. <a href="https://blazor.net" target="_blank" rel="noopener">Blazor</a> helps me to make it interactive render quickly and grow as I add more content to it. my cost in interaction with azure is minimal, as I&#8217;m using a <a href="https://azure.microsoft.com/en-us/pricing/details/app-service/linux/" target="_blank" rel="noopener">Basic-2 instance of Azure App Service running Linux</a> to deliver this site.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://jeffreyfritz.com/2024/02/blazor-and-net-8-how-i-build-a-fast-and-flexible-website/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>How to watermark, annotate, and digitally sign a PDF with IronPDF</title>
		<link>https://jeffreyfritz.com/2023/01/how-to-watermark-annotate-and-digitally-sign-a-pdf-with-ironpdf/</link>
					<comments>https://jeffreyfritz.com/2023/01/how-to-watermark-annotate-and-digitally-sign-a-pdf-with-ironpdf/#respond</comments>
		
		<dc:creator><![CDATA[Jeff]]></dc:creator>
		<pubDate>Tue, 31 Jan 2023 03:57:04 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<guid isPermaLink="false">https://jeffreyfritz.com/?p=11567</guid>

					<description><![CDATA[Previously, I shared some code that demonstrated how the new PDF report feature was built for KlipTok.  In reviewing the feature, I wanted to give it a little more value.  When you read a report generated from KlipTok, I want you to know that it was genuine and accurate data.  I started researching the ability [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>Previously, I shared some code that demonstrated how the <a href="https://jeffreyfritz.com/2023/01/how-i-built-the-first-pdf-report-for-kliptok-using-ironpdf/">new PDF report feature was built for KlipTok</a>.  In reviewing the feature, I wanted to give it a little more value.  When you read a report generated from KlipTok, I want you to know that it was genuine and accurate data.  I started researching the ability to stamp or put an authentic indicator into the report so you could tell it was a real report from KlipTok.  I found three techniques with <a href="https://ironpdf.com">IronPDF</a> that were each amazingly easy to implement and gave different experiences when reading the PDF.  Let&#8217;s take a look at each technique: watermarks, annotations, and digital signatures.<span id="more-11567"></span></p>
<h2>Adding a watermark</h2>
<p>The most visible and easiest to visualize and pass on is a watermark.  Back in the &#8220;old days&#8221; folks would print letters on paper that had an image or text hidden in them so that you could verify it was an official document.  You&#8217;ll find this technology still used in bank checks and in paper currency, with hidden images that are only visible when the paper is held up to light.</p>
<p>With a PDF watermark, we can place content with a lower opacity on-top of or behind the content of our PDF.  A very cool technique that I can use to put a light gray KlipTok logo over the report so that folks know it&#8217;s official.</p>
<p><div id="attachment_11568" style="width: 715px" class="wp-caption aligncenter"><a href="https://jeffreyfritz.com/wp-content/uploads/2023/01/watermark-over.png"><img loading="lazy" decoding="async" aria-describedby="caption-attachment-11568" class="size-full wp-image-11568" src="https://jeffreyfritz.com/wp-content/uploads/2023/01/watermark-over.png" alt="KlipTok logo over a bar graph in a PDF" width="705" height="181" srcset="https://jeffreyfritz.com/wp-content/uploads/2023/01/watermark-over.png 705w, https://jeffreyfritz.com/wp-content/uploads/2023/01/watermark-over-300x77.png 300w, https://jeffreyfritz.com/wp-content/uploads/2023/01/watermark-over-624x160.png 624w" sizes="(max-width: 705px) 100vw, 705px" /></a><p id="caption-attachment-11568" class="wp-caption-text">KlipTok logo over a bar graph in a PDF</p></div></p>
<p>I inserted this watermark logo with a few lines of code added to my pdf generation code:</p>
<pre>private static void ApplyKlipTokWatermark(PdfDocument doc)
{
  var watermark = new HtmlStamper("&lt;img src='https://localhost:5001/wordmark_subtitle.png'/&gt;")
    Opacity = 20,
    VerticalAlignment = VerticalAlignment.Top,
    HorizontalAlignment = HorizontalAlignment.Left,
    VerticalOffset = new Length(1, MeasurementUnit.Inch),
    HorizontalOffset = new Length(2.5, MeasurementUnit.Inch),
    IsStampBehindContent = false
  };

  doc.ApplyStamp(watermark);

}</pre>
<p>That is a pretty clear definition of a watermark.  The HtmlStamper class defines the HTML that will be placed on the document, along with a location using Horizontal and Vertical alignments with offsets along with an opacity for the content.  The final parameter, &#8216;IsStampBehindContent&#8217; determines if this will be placed on top of the PDF content (like in the above sample) or should be placed behind the content like below:</p>
<p><div id="attachment_11569" style="width: 717px" class="wp-caption aligncenter"><a href="https://jeffreyfritz.com/wp-content/uploads/2023/01/watermark-under.png"><img loading="lazy" decoding="async" aria-describedby="caption-attachment-11569" class="size-full wp-image-11569" src="https://jeffreyfritz.com/wp-content/uploads/2023/01/watermark-under.png" alt="Watermark underneath the opaque graph and not visible" width="707" height="179" srcset="https://jeffreyfritz.com/wp-content/uploads/2023/01/watermark-under.png 707w, https://jeffreyfritz.com/wp-content/uploads/2023/01/watermark-under-300x76.png 300w, https://jeffreyfritz.com/wp-content/uploads/2023/01/watermark-under-624x158.png 624w" sizes="(max-width: 707px) 100vw, 707px" /></a><p id="caption-attachment-11569" class="wp-caption-text">Watermark underneath the opaque graph and not visible</p></div></p>
<p>Yea, the graph is opaque so we can&#8217;t see the content underneath of it.  For this watermark, let&#8217;s keep it on top by setting &#8216;IsStampBehindContent = false&#8217;</p>
<h2>PDF Annotations</h2>
<p>The second technique we could use would be to introduce a PDF annotation.  This allows us to add a sticky note to a document with additional information that is only available when viewed electronically.  There are configuration options available that will allow viewers to be able to include the annotation on a print out, but for my purpose, I want this to just be a check available electronically.</p>
<p>I added another simple block of code to my PDF rendering class, to add the annotation before the PDF is delivered:</p>
<pre>private static void AddDatestampAnnotation(PdfDocument doc, string channelName) {

  var annotation = new IronPdf.Annotations.TextAnnotation()
  {
    Title = "Rendered by KlipTok",
    Subject = "Genuine KlipTok Report",
    Contents = $"This is a genuine report from KlipTok for the Twitch channel {channelName}. It was rendered on {DateTime.UtcNow.ToString()} UTC",
    Icon = IronPdf.Annotations.TextAnnotation.AnnotationIcon.Help,
    Opacity = 0.2,
    Printable = false,
    Hidden = true,
    ReadOnly = true
  };

  doc.AddTextAnnotation(annotation, 0, 550, 750);
 		
}</pre>
<p>I really like how easy to read these APIs are from IronPDF.  I created a TextAnnotation object and set the title, subject, contents, an icon and some other visibility preferences.  I the add the text annotation with a method called &#8216;AddTextAnnotation&#8217; with the page number, x and y coordinates from the bottom left corner of the page.</p>
<p><div id="attachment_11570" style="width: 771px" class="wp-caption aligncenter"><a href="https://jeffreyfritz.com/wp-content/uploads/2023/01/annotation.png"><img loading="lazy" decoding="async" aria-describedby="caption-attachment-11570" class="size-full wp-image-11570" src="https://jeffreyfritz.com/wp-content/uploads/2023/01/annotation.png" alt="PDF Annotation displayed over the graph when opened" width="761" height="201" srcset="https://jeffreyfritz.com/wp-content/uploads/2023/01/annotation.png 761w, https://jeffreyfritz.com/wp-content/uploads/2023/01/annotation-300x79.png 300w, https://jeffreyfritz.com/wp-content/uploads/2023/01/annotation-624x165.png 624w" sizes="(max-width: 761px) 100vw, 761px" /></a><p id="caption-attachment-11570" class="wp-caption-text">PDF Annotation displayed over the graph when opened</p></div></p>
<p>That&#8217;s nice&#8230;  but a little too much for this document.  Can we do something a little less intruding over our content and still show some authenticity?  Let&#8217;s check out a digital signature</p>
<h2>Adding a Digital Signature</h2>
<p>Digital signatures are the ultimate in proving that a file or a document came from a genuine source.  This website you&#8217;re viewing right now is protected with Transport Layer Security and signed with a certificate.  Let&#8217;s see what&#8217;s involved in adding a digital signature to our KlipTok report.</p>
<pre>var signature = new IronPdf.Signing.PdfSignature("kliptok.pfx", "MY-PASSWORD") {

  SigningContact = "info@kliptok.com",
  SigningLocation = "Philadelphia, PA - USA",
  SigningReason = "Authentic KlipTok Report"

};
		
doc.SignPdfWithDigitalSignature(signature);</pre>
<p>That is CRAZY simple.  We create a PdfSignature object by pointing to our certificate (PFX) file, with the appropriate password for that certificate (my password is left out here to protect the innocent).  I added some information about how to contact the signer, where the signing took place, and a reason.  I could have added a visual signature image to go with this, combining features of the watermark technique from above.</p>
<h2>Summary</h2>
<p>I like the watermark and I like the digital signature techniques to prove KlipTok generated these reports.  Until I find someone attempting to forge KlipTok reports, I&#8217;m going to stick with the watermark technique.  It&#8217;s simple and looks good on my document.</p>
<p>The <a href="https://ironpdf.com">IronPDF</a> APIs proved again to be easy to read and simple to implement.  I continue to be impressed with the power provided and how thorough <a href="https://ironpdf.com/examples/using-html-to-create-a-pdf/">their examples</a> are.  I&#8217;ll be wrapping up this new watermark and deploying it to KlipTok very soon.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://jeffreyfritz.com/2023/01/how-to-watermark-annotate-and-digitally-sign-a-pdf-with-ironpdf/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>How I built the first PDF report for KlipTok using IronPDF</title>
		<link>https://jeffreyfritz.com/2023/01/how-i-built-the-first-pdf-report-for-kliptok-using-ironpdf/</link>
					<comments>https://jeffreyfritz.com/2023/01/how-i-built-the-first-pdf-report-for-kliptok-using-ironpdf/#respond</comments>
		
		<dc:creator><![CDATA[Jeff]]></dc:creator>
		<pubDate>Sat, 21 Jan 2023 19:06:53 +0000</pubDate>
				<category><![CDATA[.NET]]></category>
		<category><![CDATA[ASP.NET Core]]></category>
		<category><![CDATA[CodeProject]]></category>
		<guid isPermaLink="false">https://jeffreyfritz.com/?p=11554</guid>

					<description><![CDATA[An important goal for me in building the KlipTok web application is to be able to deliver reports that can be downloaded and referenced by streamers and their support teams to help them learn how to grow their online presence.  I investigated a few tools and landed on IronPDF and started building an initial and [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>An important goal for me in building the <a href="http://kliptok.com" target="_blank" rel="noopener">KlipTok</a> web application is to be able to deliver reports that can be downloaded and referenced by streamers and their support teams to help them learn how to grow their online presence.  I <a href="https://jeffreyfritz.com/2022/12/investigating-pdf-generation-software-for-c-and-azure/" target="_blank" rel="noopener">investigated a few tools and landed on IronPDF</a> and started building an initial and simple report showing the dashboard content from KlipTok.  In this post, I&#8217;m going to show you how I took a Blazor Web Assembly page and re-used it so that the content could be printed with <a href="https://ironpdf.com" target="_blank" rel="noopener">IronPDF</a>.<span id="more-11554"></span></p>
<h2>Preparing the ASP.NET Core site to run Blazor</h2>
<p>KlipTok runs as an <a href="https://learn.microsoft.com/azure/static-web-apps/overview" target="_blank" rel="noopener">Azure Static Website</a> with a series of <a href="https://learn.microsoft.com/azure/azure-functions/functions-overview" target="_blank" rel="noopener">Azure Functions</a> to support it.  While it is possible to run <a href="https://ironpdf.com">IronPDF</a> on Azure Functions applications, in order to use and generate the a report based on the dashboard that is already present on KlipTok I would need to run Blazor content on a server.  Fortunately, I already have an ASP.NET Core website running on a Linux-based service with other features of KlipTok using Razor Pages.  With a server-side Blazor component, I could easily re-use this dashboard user-interface from the KlipTok front-end application.</p>
<p>Our architecture strategy will be to configure the existing ASP.NET Core website to run Blazor-Server pages that host the same content as appears in the Blazor-WebAssembly application that users typically interact with.  We will add a Minimal-API feature that will trigger IronPDF to render the HTML output of the new Blazor-Server dashboard page as a PDF and return that PDF to the user.</p>
<p>I started by adding Blazor-Server configuration to my existing ASP.NET Core by updating the Program.cs file to add services for Blazor Server:</p>
<pre>builder.Services.AddServerSideBlazor();
builder.Services.AddTelerikBlazor();
...
app.MapBlazorHub();
...
// Listing to indicate relative position of the MapFallbackToPage statement
app.UseEndpoints(endpoints =&gt;
{
  endpoints.MapRazorPages();
});
app.MapFallbackToPage("/_Host");

app.Run();</pre>
<p>With this added to the website configuration, ASP.NET Core now knows how to work with Blazor content on the server.  I then brought over some Blazor files to run on the server.  This included:</p>
<ul>
<li>a Shared folder with the Blazor page layout &#8220;MainLayout.razor&#8221;</li>
<li>_Imports.razor</li>
<li>App.razor</li>
<li>Pages/_Layout.cshtml to contain the HTML that would host the running Blazor application</li>
<li>I also created a &#8216;Blazor&#8217; folder to contain the reports and content that I would build with server-side Blazor.</li>
</ul>
<p><div id="attachment_11555" style="width: 301px" class="wp-caption aligncenter"><a href="https://jeffreyfritz.com/wp-content/uploads/2023/01/newFolders.png"><img loading="lazy" decoding="async" aria-describedby="caption-attachment-11555" class="size-full wp-image-11555" src="https://jeffreyfritz.com/wp-content/uploads/2023/01/newFolders.png" alt="" width="291" height="382" srcset="https://jeffreyfritz.com/wp-content/uploads/2023/01/newFolders.png 291w, https://jeffreyfritz.com/wp-content/uploads/2023/01/newFolders-229x300.png 229w" sizes="(max-width: 291px) 100vw, 291px" /></a><p id="caption-attachment-11555" class="wp-caption-text">Solution Explorer with New Folders</p></div></p>
<h2>Adding the Streamer Dashboard report content</h2>
<p><div id="attachment_11557" style="width: 310px" class="wp-caption aligncenter"><a href="https://jeffreyfritz.com/wp-content/uploads/2023/01/liveDashboard.png"><img loading="lazy" decoding="async" aria-describedby="caption-attachment-11557" class="wp-image-11557 size-medium" src="https://jeffreyfritz.com/wp-content/uploads/2023/01/liveDashboard-300x257.png" alt="" width="300" height="257" srcset="https://jeffreyfritz.com/wp-content/uploads/2023/01/liveDashboard-300x257.png 300w, https://jeffreyfritz.com/wp-content/uploads/2023/01/liveDashboard-624x534.png 624w, https://jeffreyfritz.com/wp-content/uploads/2023/01/liveDashboard.png 762w" sizes="(max-width: 300px) 100vw, 300px" /></a><p id="caption-attachment-11557" class="wp-caption-text">Dashboard for the CSharpFritz channel</p></div></p>
<p>The Streamer Dashboard is a tab inside the Streamer page on KlipTok, and not currently managed in an isolated manner.  All of the code to render this content and interact with the data repository are mixed in to the larger Streamer page.  Yes, I have some spaghetti code to unwind and deploy. In this case, it wasn&#8217;t too bad as I was able to isolate the markup that made up the inside of the Dashboard and migrate it to StreamerDashboard.razor, and you can see it in the previous image of my Solution Explorer above.</p>
<p>I updated the path and dependencies that were injected to use server-side interactions with the datastore instead of HTTP fetch operations to Azure Function endpoints.</p>
<pre>@page "/app/streamerdashboard/{DisplayName}"
@inject IKlipRepository Repo
@inject IChannelRepository ChannelRepo
...
@code {
  public StreamerRecord StreamerRecord { get; set; }

  protected override async Task OnInitializedAsync()
  {

    StreamerRecord = StreamerRecord ?? await Repo.GetChannelSummary(DisplayName, true, false);

  }
}</pre>
<p>With this change, my StreamerDashboard.razor file could render HTML at `/app/streamerfashboard/csharpfritz` or similar location based on the Display Name of the Twitch channel sought.  Very cool</p>
<p>I also updated the razor Pages/_Layout.cshtml file to contain less flashy formatting and force a &#8216;light theme&#8217; for the rendering of content.  For printing to a PDF and a piece of paper, we would want the lighter theme to ensure all content is visible when printed.</p>
<h2>Adding the Report Features with IronPDF</h2>
<p>I next put all of the configuration for <a href="https://ironpdf.com">IronPDF</a> into a file I called ReportsExtensions.cs (you can see it in the Solution Explorer image above).  This file contains a single method that isolates all of the configuration for <a href="https://ironpdf.com">IronPDF</a> and the reports that we will enable on the website.  The source of this file is as follows:</p>
<pre>public static WebApplication MapKlipTokReports(this WebApplication app, IWebHostEnvironment host)
{

// Configure IronPDF rendering at startup
AppContext.SetSwitch("System.Drawing.EnableUnixSupport", true);
IronPdf.License.LicenseKey = app.Configuration["IronPdfKey"];
IronPdf.Installation.TempFolderPath = $@"{host.ContentRootPath}/irontemp/";
IronPdf.Installation.Initialize();
IronPdf.Installation.LinuxAndDockerDependenciesAutoConfig = true;
var Renderer = new ChromePdfRenderer();

// Warm up the renderer with simple HTML content
using var PDF = Renderer.RenderHtmlAsPdf("&lt;h2&gt;Hello World&lt;h1&gt;");

// Map the endpoint the report creator will listen to
app.MapGet("/reports/StreamerDashboard/{channel}", 
async (HttpContext ctx, string channel) =&gt;
{

var url = ctx.Request.GetDisplayUrl();
url = url.Substring(0, url.IndexOf("/reports"));

// Format the page
var ironPdfRender = new IronPdf.ChromePdfRenderer();
ironPdfRender.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.Letter;
ironPdfRender.RenderingOptions.RenderDelay = 2500;
ironPdfRender.RenderingOptions.ViewPortWidth = 1080;
ironPdfRender.RenderingOptions.CssMediaType = IronPdf.Rendering.PdfCssMediaType.Screen;
ironPdfRender.RenderingOptions.MarginTop = 10;
ironPdfRender.RenderingOptions.MarginLeft = 10;
ironPdfRender.RenderingOptions.MarginRight = 10;
ironPdfRender.RenderingOptions.MarginBottom = 10;
ironPdfRender.RenderingOptions.TextFooter = new TextHeaderFooter
{
RightText = $"As of {DateTime.UtcNow.ToString()} "
};
ironPdfRender.RenderingOptions.UseMarginsOnHeaderAndFooter = UseMargins.Bottom;
ironPdfRender.RenderingOptions.Title = $"Dashboard for Twitch.tv/{channel}";
ironPdfRender.RenderingOptions.FitToPaperMode = IronPdf.Engines.Chrome.FitToPaperModes.AutomaticFit;

// Instruct IronPDF to load the HTML from the Blazor Dashboard page
var pdf = await ironPdfRender.RenderUrlAsPdfAsync($"{url}/app/StreamerDashboard/{channel}");

// Set the Http Context and return the PDF
ctx.Response.ContentType = "application/pdf";
ctx.Response.Headers.Add("Content-Disposition", $"filename={channel.ToLowerInvariant()}.pdf");
await ctx.Response.BodyWriter.WriteAsync(pdf.Stream.ToArray());

});

return app;

}</pre>
<p>This is a great little method that shows how you can isolate business processes that configure various endpoints for APIs in your web application.  In this case, we extended the WebApplication object with this static method so that our Program.cs can reference this with a very readable command like:</p>
<pre>app.MapKlipTokReports(app.Environment);</pre>
<p>The configuration for <a href="https://ironpdf.com">IronPDF</a> at the top of the MapKlipTokReports method grabs a license key from the server configuration and activates Linux-based rendering for this application.  Additionally, there is a request to warm-up the PDF renderer with a simple bit of HTML that is discarded after its rendered.</p>
<p>The API address for this report generator is defined in the app.MapGet command.  It&#8217;s listening on /reports/StreamerDashboard and captures the last bit of the directories requested and converts that to a Twitch channel name that will be sought for the report.</p>
<p>The renderer is then configured with the information about the destination page size, headers and footers to be added.  I placed a nice &#8216;As Of&#8217; timestamp in the footer of all pages rendered by using the TextFooter property and added a title using the Title property of the renderer.  Finally, the PDF is rendered with a call to the appropriate /api/StreamerDashboard/channelName location and returned as a stream with the appropriate application/pdf content type.</p>
<p>Now that I have the simple configuration for <a href="https://ironpdf.com">IronPDF</a> built, I can evolve this method in the future so that the <a href="https://ironpdf.com">IronPDF</a> rendering capabilities are isolated in their own method and my new reports are built to inject the URL of the page to render.  The configuration and presentation of this content is great, and you can check out a sample instance of this report:</p>
<p><div id="attachment_11558" style="width: 310px" class="wp-caption aligncenter"><a href="https://jeffreyfritz.com/wp-content/uploads/2023/01/csharpfritz.pdf"><img loading="lazy" decoding="async" aria-describedby="caption-attachment-11558" class="wp-image-11558 size-medium" src="https://jeffreyfritz.com/wp-content/uploads/2023/01/pdfReport-300x253.png" alt="" width="300" height="253" srcset="https://jeffreyfritz.com/wp-content/uploads/2023/01/pdfReport-300x253.png 300w, https://jeffreyfritz.com/wp-content/uploads/2023/01/pdfReport-768x648.png 768w, https://jeffreyfritz.com/wp-content/uploads/2023/01/pdfReport-624x527.png 624w, https://jeffreyfritz.com/wp-content/uploads/2023/01/pdfReport.png 872w" sizes="(max-width: 300px) 100vw, 300px" /></a><p id="caption-attachment-11558" class="wp-caption-text">Sample PDF report</p></div></p>
<h2>Wrap-up</h2>
<p>I added a &#8216;Print&#8217; button to the dashboard page of KlipTok that opens a new tab in the browser and links to this report generated by my ASP.NET Core server and <a href="https://ironpdf.com">IronPDF</a>.  This has been a great experience to build and generate a simple PDF that can be printed and shared with my stream promotion team.  I&#8217;ve got a few more ideas that I want to try with <a href="https://ironpdf.com">IronPDF</a> and will share another blog post in a few days as I add some more advanced PDF features to this report.</p>
<p>What do you think?  Would you use a tool like <a href="https://ironpdf.com">IronPDF</a> to build PDF exports for your application?</p>
]]></content:encoded>
					
					<wfw:commentRss>https://jeffreyfritz.com/2023/01/how-i-built-the-first-pdf-report-for-kliptok-using-ironpdf/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Azure Storage Caching and Compression &#8211; The Missing Piece</title>
		<link>https://jeffreyfritz.com/2022/12/azure-storage-caching-and-compression-the-missing-piece/</link>
					<comments>https://jeffreyfritz.com/2022/12/azure-storage-caching-and-compression-the-missing-piece/#respond</comments>
		
		<dc:creator><![CDATA[Jeff]]></dc:creator>
		<pubDate>Sat, 31 Dec 2022 23:11:30 +0000</pubDate>
				<category><![CDATA[.NET]]></category>
		<category><![CDATA[Azure]]></category>
		<category><![CDATA[CodeProject]]></category>
		<guid isPermaLink="false">https://jeffreyfritz.com/?p=11543</guid>

					<description><![CDATA[I&#8217;ve written and used a lot of the Azure Storage service to work with applications that I&#8217;ve built that run in the cloud.  It&#8217;s easy enough to use, just throw some files in there and they can be served internally to other applications or you can let the public access them remotely. Something that always [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>I&#8217;ve written and used a lot of the <a href="https://azure.microsoft.com/products/storage/blobs" target="_blank" rel="noopener">Azure Storage</a> service to work with applications that I&#8217;ve built that run in the cloud.  It&#8217;s easy enough to use, just throw some files in there and they can be served internally to other applications or you can let the public access them remotely.</p>
<p>Something that always struck me about the public access to the service was that the content was never cached, never compressed, and in general felt like a simple web server delivering content without some of those features that we <em>REALLY</em> like about the modern web.  Adding cache headers and compressing the content will reduce the number of queries to the server and the bandwidth needed to deliver the content when it is requested.</p>
<p>In this post, I&#8217;m going to show you how I activated caching and compression for an Azure Storage blob cache that I wanted to use with my web application <a href="https://kliptok.com" target="_blank" rel="noopener">KlipTok</a>.</p>
<p><span id="more-11543"></span></p>
<h2>Saving Data to Azure Storage &#8211; The Basics</h2>
<p>I have a process that runs in the background every 30 minutes to identify the most popular clips on KlipTok and cache their data on Azure storage as a JSON file that can be downloaded on first visit to the website.  The code to save this data looks like the following:</p>
<pre>var blobServiceClient = new BlobServiceClient(EnvironmentConfiguration.TableStorageConnectionString);
var containerClient = blobServiceClient.GetBlobContainerClient("cache");
var client = containerClient.GetBlobClient("fileToCache.json");
await client.UploadAsync(myContentToCacheAsStream, true);</pre>
<p>This creates a blob client, connects to the container named cache, defines a file named <strong>fileToCache.json</strong> and uploads the content with overwrite permissions turned on (with the <strong>true</strong> argument in the last line)</p>
<p><strong>Note: </strong> I have this code embedded in an Azure Function and <em>could</em> use the output binding feature of Azure Functions to write this content.  After running my application with that configuration for a month, I <em><strong>STRONGLY</strong></em> recommend you never use that feature.  When the function is triggered, the output binding immediately deletes the content of the blob.  If there is an error in the function or the function takes time to complete then the rest of your system that depends on that content is going to struggle.  Instead, build a BlobServiceClient and connect to your storage in this way so that you can properly error-handle and minimize the downtime while you are uploading content.</p>
<h2>Adding Cache Headers</h2>
<p>We can easily add caching headers for this file by adding a few lines to the HttpHeaders using the BlobClient.  I&#8217;d like this file to be cached for 30 minutes and available on any public proxy or server.  Let&#8217;s add those headers after the UploadAsync call:</p>
<pre>await client.SetHttpHeadersAsync(new BlobHttpHeaders
{
    CacheControl = "public, max-age=3600, must-revalidate",
    ContentType = "application/json, charset=utf-8"
});</pre>
<p>That&#8217;s easy enough.  The <a href="https://developer.mozilla.org/docs/Web/HTTP/Headers/Cache-Control" target="_blank" rel="noopener"><strong>CacheControl</strong> header</a> has a clear definition that&#8217;s been around for a long time and takes a max-age in seconds.  The must-revalidate directive states that caching servers must request a new copy of the file when the cache max-age expires and should not serve a file that is older than max-age.</p>
<h2>Compressing Content</h2>
<p>Azure Storage does not have a way to gzip compress or brotli compress content and serve it automatically like most webservers do.  On IIS there is a panel and you can clearly activate compression by just checking a box.  With other webservers, there is an entry in the server&#8217;s configuration file that will enable gzip compression.</p>
<p><div id="attachment_11545" style="width: 310px" class="wp-caption aligncenter"><a href="https://jeffreyfritz.com/wp-content/uploads/2022/12/iis-compression.png"><img loading="lazy" decoding="async" aria-describedby="caption-attachment-11545" class="size-full wp-image-11545" src="https://jeffreyfritz.com/wp-content/uploads/2022/12/iis-compression.png" alt="" width="300" height="202" /></a><p id="caption-attachment-11545" class="wp-caption-text">IIS Compression setting</p></div></p>
<p>With Azure Storage, we need to do the compression manually and add an appropriate header entry to indicate the compression.  The .NET BCL has a GZipStream object available and we can route content through that using the standard System.Text.JsonSerializer with this code:</p>
<pre>var MyContent = GetClipsToCache().ToArray();

using var myContentToCacheAsStream = new MemoryStream();
using var compressor = new GZipStream(myContentToCacheAsStream, CompressionLevel.Optimal);
await JsonSerializer.SerializeAsync(compressor, MyContent, options);
compressor.Flush();

myContentToCacheAsStream.Position = 0;</pre>
<p>I get the data to cache with the first line, GetClipsToCache() and put that as an array into the MyContent variable.  A MemoryStream is allocated that will receive the compressed content and a GZipStream is also allocated that will deliver its compressed output into the <strong>myContentToCacheAsStream</strong>.  Finally, I serialize the data as JSON and write it to the compressor GZipStream that will do the compression and deliver it to the <strong>myContentToCacheAsStream</strong> variable.  The GZipStream is flushed to ensure all of the data is compressed and the position of the target stream is reset to 0 so that it can be written to the blob storage.</p>
<p>We need to update the headers to include an indication that the content being served is GZip compressed.  Easy enough to update the code we just added for caching headers to include this:</p>
<pre>await client.SetHttpHeadersAsync(new BlobHttpHeaders
{
  CacheControl = "public, max-age=3600, must-revalidate",
  ContentType = "application/json, charset=utf-8",
  ContentEncoding = "gzip"
});</pre>
<h2>Bonus points &#8211; Just Use Frontdoor</h2>
<p>Maybe you don&#8217;t want to write this code and you just want to offload it to another service.  You could configure <a href="https://azure.microsoft.com/products/frontdoor/" target="_blank" rel="noopener">Azure Frontdoor</a> to deliver this content as another route with compression and caching by just checking a box on the route configuration panel:</p>
<p><div id="attachment_11546" style="width: 707px" class="wp-caption aligncenter"><a href="https://jeffreyfritz.com/wp-content/uploads/2022/12/frontdoor-cache.png"><img loading="lazy" decoding="async" aria-describedby="caption-attachment-11546" class="wp-image-11546 size-full" src="https://jeffreyfritz.com/wp-content/uploads/2022/12/frontdoor-cache.png" alt="" width="697" height="238" srcset="https://jeffreyfritz.com/wp-content/uploads/2022/12/frontdoor-cache.png 697w, https://jeffreyfritz.com/wp-content/uploads/2022/12/frontdoor-cache-300x102.png 300w, https://jeffreyfritz.com/wp-content/uploads/2022/12/frontdoor-cache-624x213.png 624w" sizes="(max-width: 697px) 100vw, 697px" /></a><p id="caption-attachment-11546" class="wp-caption-text">Azure Frontdoor Caching and Compression Configuration</p></div></p>
<h2>Summary</h2>
<p>With this additional bit of code, I&#8217;ve reduced the size of the file used in the initial download when visiting <a href="https://kliptok.com" target="_blank" rel="noopener">KlipTok</a> from 250k to 60k, a 76% reduction in bandwidth used.</p>
<p>With the cache-control headers added, browsers and caching servers between my application and my users will keep copies of this file and reduce the number of requests for new content from my server.  As the number of users for my site goes up, and the amount of data I need to deliver goes up, this was a welcome feature to reduce bandwidth and consequently my hosting costs as well.  This saved me more than $400 in my December Azure invoice, so I definitely recommend you try this configuration if you are serving content from Azure Storage.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://jeffreyfritz.com/2022/12/azure-storage-caching-and-compression-the-missing-piece/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>How to Configure Extensions for Azure Postgres Flexible Server</title>
		<link>https://jeffreyfritz.com/2022/12/how-to-configure-extensions-for-azure-postgres-flexible-server/</link>
					<comments>https://jeffreyfritz.com/2022/12/how-to-configure-extensions-for-azure-postgres-flexible-server/#comments</comments>
		
		<dc:creator><![CDATA[Jeff]]></dc:creator>
		<pubDate>Wed, 21 Dec 2022 17:08:15 +0000</pubDate>
				<category><![CDATA[Azure]]></category>
		<category><![CDATA[CodeProject]]></category>
		<guid isPermaLink="false">https://jeffreyfritz.com/?p=11532</guid>

					<description><![CDATA[I received an offer recently, to migrate my Azure Postgres database to a Flexible Server that would use less resources and cost me less while delivering the same functionality to my application users. Sounds like a GREAT offer, and I proceeded with the migration. Over the last week, I received a bug report and started [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>I received an offer recently, to migrate my Azure Postgres database to a Flexible Server that would use less resources and cost me less while delivering the same functionality to my application users. Sounds like a GREAT offer, and I proceeded with the migration. Over the last week, I <a href="https://feedback.kliptok.com/posts/174/feedback-site-500-errors" target="_blank" rel="noopener">received a bug report</a> and started scratching my head. What could be causing this error?<span id="more-11532"></span></p>
<h2>Investigation</h2>
<p>In my case, the application in question is <a href="https://fider.io" target="_blank" rel="noopener">Fider</a> and it manages the <a href="https://feedback.kliptok.com" target="_blank" rel="noopener">feedback site</a> for <a href="https://kliptok.com" target="_blank" rel="noopener">KlipTok</a>.  It runs out of a container on Azure App Service.  I popped open the logs and found this entry when users would key in new feedback reports:</p>
<p><div id="attachment_11533" style="width: 635px" class="wp-caption aligncenter"><a href="https://jeffreyfritz.com/wp-content/uploads/2022/12/postgres-error.png"><img loading="lazy" decoding="async" aria-describedby="caption-attachment-11533" class="wp-image-11533 size-large" src="https://jeffreyfritz.com/wp-content/uploads/2022/12/postgres-error-1024x126.png" alt="" width="625" height="77" srcset="https://jeffreyfritz.com/wp-content/uploads/2022/12/postgres-error-1024x126.png 1024w, https://jeffreyfritz.com/wp-content/uploads/2022/12/postgres-error-300x37.png 300w, https://jeffreyfritz.com/wp-content/uploads/2022/12/postgres-error-768x94.png 768w, https://jeffreyfritz.com/wp-content/uploads/2022/12/postgres-error-624x77.png 624w, https://jeffreyfritz.com/wp-content/uploads/2022/12/postgres-error.png 1399w" sizes="(max-width: 625px) 100vw, 625px" /></a><p id="caption-attachment-11533" class="wp-caption-text">Logs are reporting that the function &#8216;similarity&#8217; does not exist</p></div></p>
<p>The logs are indicating that Postgres is looking for a function called &#8216;similarity&#8217;.</p>
<p>That&#8217;s strange.. I don&#8217;t remember configuring anything for that function in Postgres or Fider.  I did some digging, and according to the <a href="https://www.postgresql.org/docs/current/pgtrgm.html" target="_blank" rel="noopener">Postgres documentation</a>, this is a function that is added through the pg_term Postgres extension.  Ok, so let&#8217;s check the <a href="https://learn.microsoft.com/azure/postgresql/flexible-server/concepts-extensions" target="_blank" rel="noopener">Azure documentation</a> to see if it is enabled.</p>
<p>According to the docs, the extension is available, but it needs to be added to the server.  I browsed to the server on the Azure portal and set the Server Parameter &#8220;azure.extensions&#8221; to include the PG_TRGM option:</p>
<p><div id="attachment_11534" style="width: 833px" class="wp-caption aligncenter"><a href="https://jeffreyfritz.com/wp-content/uploads/2022/12/configure_server.png"><img loading="lazy" decoding="async" aria-describedby="caption-attachment-11534" class="wp-image-11534 size-full" src="https://jeffreyfritz.com/wp-content/uploads/2022/12/configure_server.png" alt="Azure Portal showing how to configure the PG_TRGM extension" width="823" height="480" srcset="https://jeffreyfritz.com/wp-content/uploads/2022/12/configure_server.png 823w, https://jeffreyfritz.com/wp-content/uploads/2022/12/configure_server-300x175.png 300w, https://jeffreyfritz.com/wp-content/uploads/2022/12/configure_server-768x448.png 768w, https://jeffreyfritz.com/wp-content/uploads/2022/12/configure_server-624x364.png 624w" sizes="(max-width: 823px) 100vw, 823px" /></a><p id="caption-attachment-11534" class="wp-caption-text">Azure Portal showing how to configure the PG_TRGM extension</p></div></p>
<p>That&#8217;s it!  Right? After saving that configuration, the server restarted and that&#8217;s all that&#8217;s needed&#8230; right?</p>
<p>Nope.</p>
<p>The missing bits in the documentation are that you need to activate the extension for your database.  I connected to the database using the handy &#8216;Connect&#8217; button in the Azure portal.</p>
<p><div id="attachment_11540" style="width: 635px" class="wp-caption aligncenter"><a href="https://jeffreyfritz.com/wp-content/uploads/2022/12/portal_connect.png"><img loading="lazy" decoding="async" aria-describedby="caption-attachment-11540" class="wp-image-11540 size-large" src="https://jeffreyfritz.com/wp-content/uploads/2022/12/portal_connect-1024x268.png" alt="" width="625" height="164" srcset="https://jeffreyfritz.com/wp-content/uploads/2022/12/portal_connect-1024x268.png 1024w, https://jeffreyfritz.com/wp-content/uploads/2022/12/portal_connect-300x79.png 300w, https://jeffreyfritz.com/wp-content/uploads/2022/12/portal_connect-768x201.png 768w, https://jeffreyfritz.com/wp-content/uploads/2022/12/portal_connect-624x163.png 624w, https://jeffreyfritz.com/wp-content/uploads/2022/12/portal_connect.png 1042w" sizes="(max-width: 625px) 100vw, 625px" /></a><p id="caption-attachment-11540" class="wp-caption-text">Connect to Postgres database using the Azure Portal</p></div></p>
<p>Once connected, I executed the following &#8216;Create Extension&#8217; command to activate the extension:</p>
<pre>CREATE EXTENSION pg_trgm;</pre>
<p>Done!  No restart needed.  The database is updated and my application now runs properly.  The Azure documentation does show that you need to execute the &#8216;Create Extension&#8217; statement, but its outside of the numeric list of steps to add extensions to the server. I&#8217;ve submitted <a href="https://github.com/MicrosoftDocs/azure-docs/issues/103026" target="_blank" rel="noopener">some feedback</a> about that, and called it a day.</p>
<p>&nbsp;</p>
]]></content:encoded>
					
					<wfw:commentRss>https://jeffreyfritz.com/2022/12/how-to-configure-extensions-for-azure-postgres-flexible-server/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Investigating PDF Generation Software for C# and Azure</title>
		<link>https://jeffreyfritz.com/2022/12/investigating-pdf-generation-software-for-c-and-azure/</link>
					<comments>https://jeffreyfritz.com/2022/12/investigating-pdf-generation-software-for-c-and-azure/#comments</comments>
		
		<dc:creator><![CDATA[Jeff]]></dc:creator>
		<pubDate>Mon, 12 Dec 2022 21:19:28 +0000</pubDate>
				<category><![CDATA[ASP.NET Core]]></category>
		<category><![CDATA[CodeProject]]></category>
		<guid isPermaLink="false">https://jeffreyfritz.com/?p=11522</guid>

					<description><![CDATA[Over the last month or two, I&#8217;ve been investigating various tools that help .NET developers generate PDF documents.  In particular, I want to be able to create a few PDF reports for the KlipTok application. I compared a set of four popular tools (IronPDF, iText, SyncFusion, and Aspose) and published a YouTube video recently to [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>Over the last month or two, I&#8217;ve been investigating various tools that help .NET developers generate PDF documents.  In particular, I want to be able to create a few PDF reports for the KlipTok application. I compared a set of four popular tools (IronPDF, iText, SyncFusion, and Aspose) and published a YouTube video recently to show off the results:</p>
<p style="text-align: center;"><iframe loading="lazy" title="YouTube video player" src="https://www.youtube-nocookie.com/embed/Tv3VHsr7pog" width="560" height="315" frameborder="0" allowfullscreen="allowfullscreen"></iframe></p>
<p>In this post, I want to show the code I wrote for the winning IronPDF renderer and explore some of the features as I prepare to connect it to render a report from the <a href="https://kliptok.com/csharpfritz/dashboard" target="_blank" rel="noopener">Streamer Dashboard page</a> of KlipTok.</p>
<h2>Why not just print a PDF of the dashboard page?</h2>
<p>The dashboard on KlipTok is rendered by JavaScript and WebAssembly.  In the tests from the comparison (<a href="https://github.com/csharpfritz/PdfComparisons" target="_blank" rel="noopener">source code on GitHub</a>) I originally loaded the full-featured sports website ESPN.com with code like this:</p>
<p><script src="https://gist.github.com/csharpfritz/8bbae2a433d7ee8eaf055fd6e337d119.js"></script></p>
<p>When I attempt to simply update this code to render KlipTok at kliptok.com, I get the following result:</p>
<p><div id="attachment_11524" style="width: 223px" class="wp-caption aligncenter"><a href="https://jeffreyfritz.com/wp-content/uploads/2022/12/simple_kliptok.png"><img loading="lazy" decoding="async" aria-describedby="caption-attachment-11524" class="wp-image-11524 size-medium" title="Simple KlipTok Rendering" src="https://jeffreyfritz.com/wp-content/uploads/2022/12/simple_kliptok-213x300.png" alt="Simple KlipTok Rendering - only seeing splashscreen" width="213" height="300" srcset="https://jeffreyfritz.com/wp-content/uploads/2022/12/simple_kliptok-213x300.png 213w, https://jeffreyfritz.com/wp-content/uploads/2022/12/simple_kliptok.png 456w" sizes="(max-width: 213px) 100vw, 213px" /></a><p id="caption-attachment-11524" class="wp-caption-text">Simple KlipTok Rendering &#8211; Only the splash screen renders</p></div></p>
<p>Clearly, I need to dig in a little further and just attempting to render the Blazor Webassembly site is not going to work.  I also tried a few configuration options:</p>
<p><script src="https://gist.github.com/csharpfritz/feef93c1e6be05f28ea6654bfe561489.js"></script></p>
<p>No.. no dice. I kept getting the same splashscreen again and again. I tried extending the timeout periods, thinking I had a race condition where the page wasn&#8217;t finished rendering. Let&#8217;s take a look at some of the IronPDF features that I can use after I do a little refactoring of KlipTok to get this rendering.</p>
<p>Additional IronPDF features are going to allow me to format the new KlipTok dashboard page nicely for printing.  Let&#8217;s take a look at some of these features and apply them to the static KlipTok page used for SEO.</p>
<h2>IronPDF document rendering options</h2>
<p>The first feature I activated was visible in the test code I used above, <code>EnableJavaScript</code>.  This feature is intended to allow websites that layout their pages with a framework like React, Angular, or Vue to perform their layout before the PDF is rendered.</p>
<p>I also attempted to use the <code>RenderDelay</code> feature to render KlipTok.  RenderDelay directs IronPDF to wait a specific number of seconds to wait before rendering a PDF.  Usually, IronPDF will render immediately after the HTML is loaded.</p>
<p>I also added a <code>TextHeader</code> to the code block above.  This is very cool because we can add some information about the content of the page inside the header.  In this case, I added an &#8220;As Of&#8221; date.  I could also add a <code>TextFooter</code> with similar <code>LeftText</code>, <code>RightText</code> and <code>CenterText</code> options to place content in the footer.  Even BETTER, I can use an <code>HtmlFooter</code> or <code>HtmlHeader</code> to place an <code>HTMLFragment</code> in the header or footer.  I&#8217;m going to use a little bit of HTML with the <code>{page}</code> and <code>{total-page}</code> macros to place a pager in the footer of the pages.</p>
<p>There are <code>MarginLeft</code>, <code>MarginRight</code>, <code>MarginTop</code>, and <code>MarginBottom</code> properties that you can use to set those margins around your content in millimeters.  I found a setting of 10mm in IronPDF with a margin of 0px in my HTML content achieved a nice balance between stretching the content to fill the paper and some reading room around the edge of the content.</p>
<p>By default, IronPDF attempts to render a <code>print</code> media version of a website.  You can force the CSS media to <code>screen</code> mode by setting the <code>CSSMediaType</code> to <code>PdfCssMediaType.Screen</code> and you should render the same content in your PDF that you see on the screen.</p>
<p>Additionally, you can force IronPDF to render a smaller version of the website by setting the ViewPort size in pixels with the <code>ViewPortHeight</code> and <code>ViewPortWidth</code> options.  It&#8217;s a good idea to tinker with these two settings and the <code>FitToPaperMode</code> option which lets you specify how you would like IronPDF to place content on the page.  I liked setting a large ViewPort and setting <code>FitToPaperMode</code> to <code>FitToPaperModes.AutomaticFit</code> so that the page content will be stretched to fill the ViewPort and then rendered on the page.</p>
<p>Finally, you&#8217;re going to want to specify the paper size to render the HTML content to.  With a <code>PaperOrientation</code> setting for landscape vs. portrait, and a <code>PaperSize</code> property as well, you have a wealth of options for the format you wish to deliver.</p>
<p><script src="https://gist.github.com/csharpfritz/4c86731ea1b9c566feb466ea43fd119a.js"></script></p>
<p><div id="attachment_11528" style="width: 201px" class="wp-caption aligncenter"><a href="https://jeffreyfritz.com/wp-content/uploads/2022/12/seo-kliptok-formatted.png"><img loading="lazy" decoding="async" aria-describedby="caption-attachment-11528" class="wp-image-11528 size-medium" title="The SEO-optimized version of KlipTok rendered as a PDF with margins, header, and footer." src="https://jeffreyfritz.com/wp-content/uploads/2022/12/seo-kliptok-formatted-191x300.png" alt="The SEO-optimized version of KlipTok rendered as a PDF with margins, header, and footer." width="191" height="300" srcset="https://jeffreyfritz.com/wp-content/uploads/2022/12/seo-kliptok-formatted-191x300.png 191w, https://jeffreyfritz.com/wp-content/uploads/2022/12/seo-kliptok-formatted.png 455w" sizes="(max-width: 191px) 100vw, 191px" /></a><p id="caption-attachment-11528" class="wp-caption-text">The SEO-optimized version of KlipTok rendered as a PDF with margins, header, and footer.</p></div></p>
<h2>Just the beginning&#8230;</h2>
<p>I had my problems getting the KlipTok content that was built with Blazor Web Assembly to render using IronPDF.  Fortunately, I&#8217;m in a process of refactoring the location and delivery of Blazor components in the application and I can probably move that dashboard content to server-size Blazor and render it in the SEO version of the website.</p>
<p>In my next blog post, I&#8217;ll move those components and configure IronPDF to render content as an ASP.NET Core Minimal API running on Azure using the format I just defined in the code above.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://jeffreyfritz.com/2022/12/investigating-pdf-generation-software-for-c-and-azure/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>I built an Android app on my Linux machine using .NET 7 and MAUI</title>
		<link>https://jeffreyfritz.com/2022/12/i-built-an-android-app-on-my-linux-machine-using-net-7-and-maui/</link>
					<comments>https://jeffreyfritz.com/2022/12/i-built-an-android-app-on-my-linux-machine-using-net-7-and-maui/#comments</comments>
		
		<dc:creator><![CDATA[Jeff]]></dc:creator>
		<pubDate>Sun, 04 Dec 2022 03:04:08 +0000</pubDate>
				<category><![CDATA[.NET]]></category>
		<category><![CDATA[.NET MAUI]]></category>
		<category><![CDATA[KlipTok]]></category>
		<category><![CDATA[linux]]></category>
		<guid isPermaLink="false">https://jeffreyfritz.com/?p=11512</guid>

					<description><![CDATA[I&#8217;ve been tinkering and preparing to build a mobile app for the KlipTok website for the past few months. KlipTok is a personal project, and a joy to work on when I have an hour here and there to spend on the site. The most requested feature I have, is for a mobile app to [&#8230;]]]></description>
										<content:encoded><![CDATA[<p>I&#8217;ve been tinkering and preparing to build a mobile app for the <a href="https://kliptok.com" target="_blank" rel="noopener">KlipTok website</a> for the past few months. KlipTok is a personal project, and a joy to work on when I have an hour here and there to spend on the site. The most requested feature I have, is for a mobile app to complement the site, and this week I started focusing on that effort&#8230; and was even able to build an Android app using .NET MAUI, Blazor, and my Linux laptop. In this post, I&#8217;ll describe how I went about getting the development environment working on Ubuntu Linux.</p>
<h3>NOTE: Your experience might be different</h3>
<p>Every Linux system is different, and I can point you in the direction that worked for me.  I cannot guarantee that you will find similar success or answer questions for your configuration.</p>
<p>I started this project on my Twitch stream on Friday December 2nd, and was able to get a simple Blazor MAUI application that played a Twitch clip running on my Windows machine.  I used Visual Studio 2022 and the Android emulator that came with it and was happy to have an initial proof-of-concept application running.</p>
<p><iframe loading="lazy" src="https://clips.twitch.tv/embed?clip=KnottyScrumptiousLionBleedPurple-5MpjbzgT5pQciLQv&amp;parent=jeffreyfritz.com" width="620" height="378" frameborder="0" scrolling="no" allowfullscreen="allowfullscreen"></iframe></p>
<p>I prefer to use my Linux laptop from KFocus when I am not in my home office. It&#8217;s a great machine with plenty of power for development. With a quick web search, I found an article where someone was setting up <a href="https://rjj-software.co.uk/blog/building-net-maui-android-apps-on-linux-based-operating-systems/" target="_blank" rel="noopener">Android development on Linux</a>.  I read through those steps, updated to .NET 7, and wrote a few scripts to help with my development.  Here&#8217;s what I did:</p>
<ol>
<li>I installed Android Studio from the Ubuntu app repository using the Discover app.</li>
<li>I opened Android Studio and grabbed the SDK folder from the Android SDK Manager tool, depicted below.  In my case, its sitting in /home/csharpfritz/Android/Sdk.  <img loading="lazy" decoding="async" class="aligncenter size-large wp-image-11513" src="https://jeffreyfritz.com/wp-content/uploads/2022/12/AndroidSdkMenu-1024x681.png" alt="Android Studio SDK Manager screen" width="625" height="416" srcset="https://jeffreyfritz.com/wp-content/uploads/2022/12/AndroidSdkMenu-1024x681.png 1024w, https://jeffreyfritz.com/wp-content/uploads/2022/12/AndroidSdkMenu-300x200.png 300w, https://jeffreyfritz.com/wp-content/uploads/2022/12/AndroidSdkMenu-768x511.png 768w, https://jeffreyfritz.com/wp-content/uploads/2022/12/AndroidSdkMenu-624x415.png 624w, https://jeffreyfritz.com/wp-content/uploads/2022/12/AndroidSdkMenu.png 1052w" sizes="(max-width: 625px) 100vw, 625px" /></li>
<li>Installed the .NET MAUI workload into .NET 7 at the command-line with the command: `sudo dotnet workload install maui-android`</li>
<li>Installed the Android Debug Bridge (ADB) with `sudo apt install adb`</li>
<li>Started and ran the emulator from Android Studio, verifying that it was running with `adb devices`</li>
</ol>
<p><img loading="lazy" decoding="async" class="aligncenter size-medium wp-image-11517" src="https://jeffreyfritz.com/wp-content/uploads/2022/12/Android-Linux-147x300.png" alt="Android Emulator running on Linux" width="147" height="300" srcset="https://jeffreyfritz.com/wp-content/uploads/2022/12/Android-Linux-147x300.png 147w, https://jeffreyfritz.com/wp-content/uploads/2022/12/Android-Linux.png 422w" sizes="(max-width: 147px) 100vw, 147px" /></p>
<p>Now that I have the emulator running, I could build and deploy my application to the emulator with the command:</p>
<pre>dotnet build -t:Run -f net7.0-android /p:AndroidSdkDirectory=/home/csharpfritz/Android/Sdk</pre>
<p>On first test, it failed miserably and complained about not supporting MacCatalyst and iOS development on Linux.  Not a problem, so I updated the csproj file to have supported frameworks like the following:</p>
<pre>&lt;TargetFrameworks&gt;net7.0-android;&lt;/TargetFrameworks&gt;
&lt;TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('osx'))"&gt;
  $(TargetFrameworks);net7.0-ios;net7.0-maccatalyst
&lt;/TargetFrameworks&gt;
&lt;TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))"&gt;
  $(TargetFrameworks);net7.0-windows10.0.19041.0
&lt;/TargetFrameworks&gt;</pre>
<p>When I now build on Linux, just the Android configuration runs.  I tried to recompile and got errors about the compiler not finding the Java jar tool.  Easy enough, I installed the latest tool with this command:</p>
<pre>sudo apt install openjdk-11-jdk-headless</pre>
<p>I compiled and was able to deploy to the emulator in 55 seconds!  Progress!</p>
<p><img loading="lazy" decoding="async" class="aligncenter wp-image-11516 size-full" src="https://jeffreyfritz.com/wp-content/uploads/2022/12/KlipTok-Android-Linux.png" alt="KlipTok running on an Android emulator on LInux" width="422" height="863" /></p>
<p>Last steps, I wanted to get the app building and recompiling with dotnet watch.  This way, I can make changes and the app will reflect those changes in the emulator.  My dotnet watch command is:</p>
<pre>dotnet watch build -t:Run -f net7.0-android /p:AndroidSdkDirectory=/home/csharpfritz/Android/Sdk</pre>
<p>I found that the app didn&#8217;t rebuild and deploy when I made changes to the razor files.  Easy enough, I made another addition to my csproj file to include a definition for watching the razor files:</p>
<pre>&lt;ItemGroup&gt;
  &lt;Watch Include="**\*.razor" /&gt;
&lt;/ItemGroup&gt;</pre>
<p>With this patched, I was able to build and run the app in watch mode.  As I changed razor files, the app would redeploy to the emulator in 6 seconds!  That&#8217;s such a better development experience.  Last thing that I tried, was to add a switch to my script to shut off the analyzers when I&#8217;m in watch mode:</p>
<pre>dotnet watch build -t:Run -f net7.0-android /p:AndroidSdkDirectory=/home/csharpfritz/Android/Sdk /p:RunAnalyzers=false</pre>
<p>That didn&#8217;t give me a big boost in performance, but I feel better without the analyzers running when I&#8217;m in watch mode.</p>
<p>Have you tried building an Android app on Linux with MAUI?  Do you have any tips or tricks for developers to use in order to have a better experience?  Share with us in the comments below</p>
]]></content:encoded>
					
					<wfw:commentRss>https://jeffreyfritz.com/2022/12/i-built-an-android-app-on-my-linux-machine-using-net-7-and-maui/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
	</channel>
</rss>
