<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Sailing the Sharp Sea]]></title><description><![CDATA[.NET developer with a passion for mobile and DevOps. I build cross platform apps using Xamarin, backend systems using Azure and GitHub Actions using Docker.]]></description><link>https://blog.taranissoftware.com</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1655679419808/XVggHFs9I.png</url><title>Sailing the Sharp Sea</title><link>https://blog.taranissoftware.com</link></image><generator>RSS for Node</generator><lastBuildDate>Tue, 21 Apr 2026 00:40:03 GMT</lastBuildDate><atom:link href="https://blog.taranissoftware.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Secure your workflows with StepSecurity Harden Runner]]></title><description><![CDATA[In my previous article, Secure your .NET builds with StepSecurity and GitHub Actions, I discussed software supply chain attacks and showed how to use StepSecurity Secure Workflows to improve the security of your GitHub Actions software supply chain. ...]]></description><link>https://blog.taranissoftware.com/secure-your-workflows-with-stepsecurity-harden-runner</link><guid isPermaLink="true">https://blog.taranissoftware.com/secure-your-workflows-with-stepsecurity-harden-runner</guid><category><![CDATA[github-actions]]></category><category><![CDATA[Devops]]></category><category><![CDATA[DevSecOps]]></category><category><![CDATA[.NET]]></category><category><![CDATA[software-supply-chain-security]]></category><dc:creator><![CDATA[Dave Murray]]></dc:creator><pubDate>Sun, 21 Aug 2022 19:35:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1661042073378/GN79phu3M.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In my previous article, <a target="_blank" href="https://blog.taranissoftware.com/secure-your-net-builds-with-stepsecurity-and-github-actions">Secure your .NET builds with StepSecurity and GitHub Actions</a>, I discussed software supply chain attacks and showed how to use StepSecurity <a target="_blank" href="https://github.com/step-security/secure-workflows">Secure Workflows</a> to improve the security of your GitHub Actions software supply chain. In this article I show how to use the <a target="_blank" href="https://github.com/step-security/harden-runner">Harden Runner</a> security agent to audit and block outbound connections to further improve workflow security.</p>
<h2 id="heading-a-quick-recap">A Quick Recap</h2>
<p>Software supply chain attacks are an increasing threat that targets source code, build processes or update mechanisms to infect legitimate apps and distribute malware. Incidents like the <a target="_blank" href="https://www.businessinsider.com/solarwinds-hack-explained-government-agencies-cyber-security-2020-12">SolarWinds attack</a> have breached all types of organisation ranging from startups to Fortune 500 companies and government agencies.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661029698275/681_Ae77A.webp" alt="Software Supply Chain Attacks" class="image--center mx-auto" /></p>
<p><a target="_blank" href="https://www.stepsecurity.io/">StepSecurity</a> helps secure your software release and distribution supply chain. <a target="_blank" href="https://github.com/step-security/secure-workflows">Secure Workflows</a> provides remediation of vulnerabilities in GitHub Actions workflows and the <a target="_blank" href="https://github.com/step-security/harden-runner">Harden Runner</a> security agent prevents exfiltration of credentials and helps detect compromised dependencies, tools or source code.</p>
<h2 id="heading-what-is-harden-runner">What is Harden Runner?</h2>
<p>As seen in recent supply chain attacks, including the <a target="_blank" href="https://www.securityweek.com/codecov-bash-uploader-dev-tool-compromised-supply-chain-hack">Codecov breach</a> and <a target="_blank" href="https://blog.sonatype.com/npm-project-used-by-millions-hijacked-in-supply-chain-attack">malicious npm cryptomining packages</a>, compromised dependencies and build tools typically make outbound calls. Restricting outbound traffic to only the endpoints needed for the workflow thwarts many software supply chain attack vectors.</p>
<p>When added to a job in a GitHub Actions workflow, the <a target="_blank" href="https://github.com/step-security/harden-runner">Harden Runner</a> security agent can audit and block outbound traffic from the runner executing that job. There are some limitations:</p>
<ul>
<li>Harden Runner only works for GitHub hosted runners, self-hosted runners are not supported.</li>
<li>Only Ubuntu runners are supported, Windows and macOS runners are being <a target="_blank" href="https://github.com/step-security/harden-runner/discussions/121">discussed</a>.</li>
<li>Harden Runner does not support running an entire job in a container, which is unusual for GitHub Actions.</li>
</ul>
<p>Harden Runner supports both public and private repositories. If you use Harden Runner with a private repository the generated audit report is not public, authentication is required and only those who have access to the repository can view the report.</p>
<h3 id="heading-how-do-i-set-up-harden-runner">How do I set up Harden Runner?</h3>
<p>The easiest way to add Harden Runner to a workflow is to use the <a target="_blank" href="https://app.stepsecurity.io/">Secure Workflows app</a>. This can set the minimum <code>GITHUB_TOKEN</code> permissions for the workflow, pin the Actions used by a full length commit SHA and add the Harden Runner Action to the start of each job in <code>audit</code> mode. Future runs of the workflow will include a link to an audit report that lists all the outbound calls made in the workflow log.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661030579910/8qQNawaQP.webp" alt="StepSecurity Harden Runner Action Log" class="image--center mx-auto" /></p>
<p>For private repositories you also need to install the <a target="_blank" href="https://github.com/marketplace/harden-runner-app">Harden Runner app</a> on GitHub which requires <code>actions: read</code> permissions to your repositories. I've also installed the app for my public repositories because otherwise you have to wait 30 seconds for the results and I'm impatient.😄</p>
<h3 id="heading-how-do-i-configure-block-mode">How do I configure block mode?</h3>
<p>With Harden Runner added to your workflows you continue to develop your project while it audits outbound connections. Once you have completed several runs of a workflow you can check the audit reports and build a list of allowed connections. Simple workflows like project management tasks may always make the same connections but more complicated workflows may include optional calls so it is worth checking several runs rather than relying on the results of just one audit.</p>
<p>The audit report lists every step in a job. If a step makes any outbound connections the domain is listed along with the name of the process that made the connection. If a workflow includes multiple jobs then each job has its own tab in the report.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661036682724/tOLRzcHmw.webp" alt="StepSecurity Harden Runner Audit Report" class="image--center mx-auto" /></p>
<p>The audit report also provides the YAML needed to configure Harden Runner to block any outbound connections from the job other than those listed in the report.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661037323762/kH9w-43wa.png" alt="StepSecurity Harden Runner Audit Policy YAML" class="image--center mx-auto" /></p>
<p>Once you configure Harden Runner in <code>block</code> mode, it will restrict outbound traffic from the workflow to only the domains listed.</p>
<h3 id="heading-what-happens-when-a-call-is-blocked">What happens when a call is blocked?</h3>
<p>Calls to any domain not allowed by Harden Runner will fail DNS resolution and cause the step that made them, and the job, to fail. So let's test that out...</p>
<p>I added the following step to the same CI build workflow used for the audit screenshots above:</p>
<pre><code class="lang-yml">    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Test</span> <span class="hljs-string">Harden</span> <span class="hljs-string">Runner</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">curl</span> <span class="hljs-string">-X</span> <span class="hljs-string">GET</span> <span class="hljs-string">https://blog.taranissoftware.com/</span>
</code></pre>
<p>This uses <code>curl</code> to access a URL that is not in the list of allowed endpoints provided to Harden Runner. Immediately we can see in the pull request that there is a problem:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661104895432/uOB41XYbj.webp" alt="StepSecurity Harden Runner Blocked PR" class="image--center mx-auto" /></p>
<p>Clicking through to the workflow summary we can see that an attempt was made to access a restricted endpoint and was blocked at the DNS level:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661104975764/t_BH94Zwd.webp" alt="StepSecurity Harden Runner Blocked Action Summary" class="image--center mx-auto" /></p>
<p>The workflow logs show it was our test step that tried to access the restricted domain:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661105115550/LPy82yV2A.webp" alt="StepSecurity Harden Runner Blocked Action Log" class="image--center mx-auto" /></p>
<p>And, we can also see the details of the step, process and blocked endpoint in the Harden Runner report:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661105189193/Mki2NwUrQ.webp" alt="StepSecurity Harden Runner Blocked Report" class="image--center mx-auto" /></p>
<h2 id="heading-tips-for-net-developers">Tips for .NET Developers</h2>
<p>Since Harden Runner only works on an Ubuntu runner you can't use it for jobs that require a Windows or macOS runner such as building .NET Framework, Xamarin or .NET MAUI apps but it does work for building .NET Core, .NET 5 and .NET 6 apps that will build on Linux.</p>
<h3 id="heading-disable-net-telemetry">Disable .NET Telemetry</h3>
<p>The .NET CLI includes telemetry that sends data back to Microsoft. When running the CLI in a build environment you should disable the telemetry because otherwise commands like <code>dotnet build</code> and <code>dotnet restore</code> will make additional calls. To disable the telemetry add the following environment variables to your workflow:</p>
<pre><code class="lang-yml"><span class="hljs-attr">env:</span>
  <span class="hljs-attr">DOTNET_NOLOGO:</span> <span class="hljs-literal">true</span>                     <span class="hljs-comment"># Disable the .NET logo in the console output</span>
  <span class="hljs-attr">DOTNET_SKIP_FIRST_TIME_EXPERIENCE:</span> <span class="hljs-literal">true</span> <span class="hljs-comment"># Disable the .NET first time experience</span>
  <span class="hljs-attr">DOTNET_CLI_TELEMETRY_OPTOUT:</span> <span class="hljs-literal">true</span>       <span class="hljs-comment"># Disable sending .NET CLI telemetry to Microsoft</span>
</code></pre>
<p>This should also reduce the workflow duration slightly, which is a nice bonus.</p>
<h3 id="heading-setup-net">Setup .NET</h3>
<p>In my projects the action to set up .NET (<a target="_blank" href="https://github.com/actions/setup-dotnet">actions/setup-dotnet</a>) is inconsistent - sometimes it makes two outbound calls and other times it makes three. This is a good example of why you need to audit multiple runs of a workflow before enabling <code>block</code> mode because the list of allowed connections needs to include all possible calls. From my experience the set up .NET action needs these connections:</p>
<pre><code class="lang-txt">dotnetbuilds.azureedge.net:443
dotnetcli.azureedge.net:443
dotnetcli.blob.core.windows.net:443
</code></pre>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>Harden Runner requires some time to fully configure but once blocking is enabled it thwarts many common attack vectors. Between Secure Workflows and Harden Runner, StepSecurity have added two powerful tools to the DevSecOps toolkit for developers using GitHub Actions. Whether you're working on open or closed source projects I reccommend you check them out and improve the security of your software supply chain.</p>
<p> </p>
<p> </p>
<p>Cover image contains a vector created by <strong>upklyak</strong> from <a target="_blank" href="https://www.freepik.com/vectors/web-server">www.freepik.com</a>.</p>
<p>Software Supply Chain Attacks chart is from Sonatype's <a target="_blank" href="https://www.sonatype.com/hubfs/SSSC-Report-2021_0913_PM_2.pdf">2021 State of the Software Supply Chain</a> report.</p>
]]></content:encoded></item><item><title><![CDATA[Secure your .NET builds with StepSecurity and GitHub Actions]]></title><description><![CDATA[Software supply chain attacks are increasing in severity and frequency. StepSecurity are working to help secure DevOps workflows using GitHub Actions. In this article I show how you can use StepSecurity's tools to improve the security of your workflo...]]></description><link>https://blog.taranissoftware.com/secure-your-net-builds-with-stepsecurity-and-github-actions</link><guid isPermaLink="true">https://blog.taranissoftware.com/secure-your-net-builds-with-stepsecurity-and-github-actions</guid><category><![CDATA[github-actions]]></category><category><![CDATA[.NET]]></category><category><![CDATA[Devops]]></category><category><![CDATA[DevSecOps]]></category><category><![CDATA[software-supply-chain-security]]></category><dc:creator><![CDATA[Dave Murray]]></dc:creator><pubDate>Sun, 07 Aug 2022 20:07:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1659888843577/dB2PH7xT6.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Software supply chain attacks are increasing in severity and frequency. StepSecurity are working to help secure DevOps workflows using GitHub Actions. In this article I show how you can use StepSecurity's tools to improve the security of your workflows with an example for .NET. </p>
<h2 id="heading-what-are-software-supply-chain-attacks">What are Software Supply Chain Attacks?</h2>
<p>Software supply chain attacks are an emerging threat that targets software developers and suppliers. The goal of attackers is to access source code, build processes or update mechanisms to infect legitimate apps and distribute malware. There has been a surge in supply chain attacks following the well known <a target="_blank" href="https://www.businessinsider.com/solarwinds-hack-explained-government-agencies-cyber-security-2020-12">SolarWinds attack</a> in 2020. These attacks have breached all types of organisation ranging from startups to Fortune 500 companies, government agencies and open source projects. Attackers are now focusing their efforts on breaching software suppliers because a successful attack may affect a large number of victims that use the supplier's software. </p>
<p><a target="_blank" href="https://www.sonatype.com/hubfs/SSSC-Report-2021_0913_PM_2.pdf"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659896022247/Ve0u4kdmZ.webp" alt="Software Supply Chain Attacks" class="image--center mx-auto" /></a></p>
<h2 id="heading-what-is-stepsecurity">What is StepSecurity?</h2>
<p><a target="_blank" href="https://www.stepsecurity.io/">StepSecurity</a> aims to help secure your software release and distribution supply chain as simply as possible. The <a target="_blank" href="https://github.com/step-security/secure-workflows">Secure Workflows</a> app provides remediation of vulnerabilities in GitHub Actions workflows, significantly reducing the time and effort required to apply security settings. And, the <a target="_blank" href="https://github.com/step-security/harden-runner">Harden Runner</a> Action audits and blocks outbound traffic to prevent exfiltration of credentials, detect compromised dependencies or tools and detect code tampering during builds.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659893176379/OILwhHmHw.webp" alt="StepSecurity Overview" class="image--center mx-auto" /></p>
<p>StepSecurity was recently recognized by the <a target="_blank" href="https://www.linuxfoundation.org/">Linux Foundation</a> for "high-impact and lasting improvements that almost certainly prevent major vulnerabilities in the affected code or supporting infrastructure." Secure Workflows is used by many of the <a target="_blank" href="https://openssf.org/">OpenSSF's</a> critical open source projects including Python, Gatsby and Babel as well as products from Microsoft, Google and Cisco.</p>
<p>StepSecurity currently supports GitHub Actions but support for GitLab, CircleCI and more providers is on the roadmap.</p>
<h2 id="heading-secure-workflows">Secure Workflows</h2>
<p>The <a target="_blank" href="https://app.stepsecurity.io/">Secure Workflows app</a> can automatically:</p>
<ul>
<li>Set minimum <code>GITHUB_TOKEN</code> permissions</li>
<li>Pin Actions to a full length commit SHA</li>
<li>Add the Harden Runner Action to each job</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659889805054/4xo50rVBf.webp" alt="StepSecurity Secure Workflows" class="image--center mx-auto" /></p>
<h3 id="heading-token-permissions">Token Permissions</h3>
<p>At the start of a workflow run, GitHub generates a unique <code>GITHUB_TOKEN</code> that is passed to the runner to be used for authentication and expires when the workflow finishes or after a maximum of 24 hours. By default the token has <code>write</code> permissions for all scopes except when generated by a pull request from a fork, in which case it has only <code>read</code> permissions.</p>
<p>A compromised token with write access could be used to push malicious code into a project. By setting all the token permissions to read except where required by the workflow, Secure Workflows follows the principle of least privilege and reduces the risk of a compromised token.</p>
<p>StepSecurity maintains a knowledge base of GitHub Actions that is used by Secure Workflows to determine the permissions needed by each step in your workflow and recommend the minimum permissions. If you maintain a GitHub Action please <a target="_blank" href="https://github.com/step-security/secure-workflows/blob/main/knowledge-base/actions/README.md">contribute to the knowledge base</a> so users of your Action can know they have the correct permissions set. Both my Actions - <a target="_blank" href="https://github.com/marketplace/actions/code-coverage-summary">Code Coverage Summary</a> &amp; <a target="_blank" href="https://github.com/marketplace/actions/edit-release">Edit Release</a> - are already in the knowledge base.</p>
<h3 id="heading-pin-actions-by-sha">Pin Actions by SHA</h3>
<p>Git and Docker tags are mutable. This poses a security risk because if the tag changes you will not have a chance to review the change before it gets used. The current <a target="_blank" href="https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-third-party-actions">GitHub recommended best practice</a> for using Actions is pinning them to a full length commit SHA rather than a version tag. This helps mitigate the risk of an attacker compromising the Action's repository because they would need to generate an SHA-1 collision.</p>
<p>Although pinning to a commit SHA is the most secure option, specifying a tag is more convenient and easier to read so it is widely used. Secure Workflows allows you to write your workflow using tags then update it with the Secure Workflows app to pin all the Actions by commit SHA. If you use <a target="_blank" href="https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/about-dependabot-version-updates">Dependabot</a> to keep your workflow dependencies up to date, don't worry, it also understands pinning by SHA so will continue to keep your workflows updated and following best practice.</p>
<h3 id="heading-add-harden-runner-action">Add Harden Runner Action</h3>
<p>Secure Workflows can add a step that installs the Harden Runner security agent to the start of each job in a workflow, configuring it in audit mode. Harden Runner only supports Ubuntu runners so disable this option if you are using a Windows or macOS runner, more on this below.</p>
<h2 id="heading-harden-runner">Harden Runner</h2>
<p>Compromised dependencies and build tools typically make outbound calls. This was seen in the <a target="_blank" href="https://www.securityweek.com/codecov-bash-uploader-dev-tool-compromised-supply-chain-hack">Codecov breach</a>, <a target="_blank" href="alex.birsan">Alex Birsan's research</a> on dependency confusion attacks, <a target="_blank" href="https://blog.sonatype.com/npm-project-used-by-millions-hijacked-in-supply-chain-attack">malicious npm cryptomining packages</a> and other recent attacks. Restricting outbound traffic to only the endpoints needed for the workflow thwarts many attack vectors that target the build server.</p>
<p><a target="_blank" href="https://github.com/step-security/harden-runner">Harden Runner</a> is a GitHub Action that installs a security agent on the runner to audit and block outbound traffic to prevent exfiltration of credentials, help detect compromised dependencies or tools and detect code tampering during builds. There are a few limitations:</p>
<ul>
<li>Harden Runner only works for GitHub hosted runners, self-hosted runners are not supported.</li>
<li>Only Ubuntu runners are supported, Windows and macOS runners are not currently supported but are being <a target="_blank" href="https://github.com/step-security/harden-runner/discussions/121">discussed</a>.</li>
<li>Harden Runner does not support running a job in a container. It can monitor jobs that use containers to run steps, the limitation is if the entire job is run in a container which is not common for GitHub Actions workflows.</li>
</ul>
<p>Since Harden Runner will only work on an Ubuntu runner you can't use it for jobs that require a Windows or macOS runner such as building .NET Framework, Xamarin or .NET MAUI apps. It does work for .NET Core and .NET 5/6 apps that will build on Linux and can be used for other jobs that don't require a Windows or macOS runner such as project management workflows.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659891611738/MBGfKko38.webp" alt="StepSecurity Harden Runner Action" class="image--center mx-auto" /></p>
<p>Harden Runner supports both public and private repositories. For private repositories you need to install the <a target="_blank" href="https://github.com/marketplace/harden-runner-app">Harden Runner App</a> in GitHub which requires <code>actions: read</code> permissions to your repositories. I also recommend installing the app for public repositories because otherwise you have to wait 30 seconds for the results and I'm impatient. If you use Harden Runner in a private repository the generated audit report is not public, authentication is required to access the report and only those who have access to the repository can view it.</p>
<p>The Secure Workflows app can be used to add Harden Runner to every job in a workflow, configuring it in <code>audit</code> mode. Future runs of the workflow will include a link to the audit report for that run. Once you have audited several runs you can configure Harden Runner in <code>block</code> mode with a list of the allowed endpoints for the workflow. From then on, outbound traffic from the workflow will be restricted to only those domains on the list. Calls to any other domain will fail DNS resolution and cause the step that made them and the workflow to fail.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659891649441/9nMPRpNwY.webp" alt="StepSecurity Harden Runner Audit Result" class="image--center mx-auto" /></p>
<h2 id="heading-how-do-i-use-all-this">How do I use all this?</h2>
<p>If all that security talk sounds complicated don't worry, StepSecurity have made implementing all the recommendations a simple process. </p>
<p>To secure a GitHub Actions workflow:</p>
<ol>
<li>Copy the workflow YAML file</li>
<li>Paste it into the <a target="_blank" href="https://app.stepsecurity.io/">Secure Workflows app</a></li>
<li>Click the <code>Secure Workflows</code> button</li>
<li>Click the <code>Copy</code> button</li>
<li>Paste the workflow back into your codebase</li>
</ol>
<p>If there are any problems securing your workflow, such as using an Action not in the knowledge base, the errors will be listed with guidance. Whenever the app discovers an Action that is missing from the knowledge base it automatically opens an issue in the Secure Workflows repository so it will be added in the near future.</p>
<h3 id="heading-net-build-example">.NET Build Example</h3>
<p>Let's look at a simple .NET 6 CI build workflow:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">CI</span> <span class="hljs-string">Build</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span> [ <span class="hljs-string">master</span> ]
  <span class="hljs-attr">pull_request:</span>
    <span class="hljs-attr">branches:</span> [ <span class="hljs-string">master</span> ]

<span class="hljs-attr">env:</span>
  <span class="hljs-attr">DOTNET_NOLOGO:</span> <span class="hljs-literal">true</span>                     <span class="hljs-comment"># Disable the .NET logo in the console output</span>
  <span class="hljs-attr">DOTNET_SKIP_FIRST_TIME_EXPERIENCE:</span> <span class="hljs-literal">true</span> <span class="hljs-comment"># Disable the .NET first time experience</span>
  <span class="hljs-attr">DOTNET_CLI_TELEMETRY_OPTOUT:</span> <span class="hljs-literal">true</span>       <span class="hljs-comment"># Disable sending .NET CLI telemetry to Microsoft</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">build:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">CI</span> <span class="hljs-string">Build</span>
    <span class="hljs-attr">steps:</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">fetch-depth:</span> <span class="hljs-number">0</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">.NET</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-dotnet@v2</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">dotnet-version:</span> <span class="hljs-number">6.0</span><span class="hljs-string">.x</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Restore</span> <span class="hljs-string">Dependencies</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">restore</span> <span class="hljs-string">src/Example.sln</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">App</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">build</span> <span class="hljs-string">src/Example.sln</span> <span class="hljs-string">--configuration</span> <span class="hljs-string">Release</span> <span class="hljs-string">--no-restore</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Run</span> <span class="hljs-string">Unit</span> <span class="hljs-string">Tests</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">test</span> <span class="hljs-string">src/Example.sln</span> <span class="hljs-string">--configuration</span> <span class="hljs-string">Release</span> <span class="hljs-string">--no-build</span> <span class="hljs-string">--verbosity</span> <span class="hljs-string">normal</span> <span class="hljs-string">--collect:"XPlat</span> <span class="hljs-string">Code</span> <span class="hljs-string">Coverage"</span> <span class="hljs-string">--results-directory</span> <span class="hljs-string">./coverage</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Code</span> <span class="hljs-string">Coverage</span> <span class="hljs-string">Summary</span> <span class="hljs-string">Report</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">irongut/CodeCoverageSummary@v1.3.0</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">filename:</span> <span class="hljs-string">coverage/**/coverage.cobertura.xml</span>
        <span class="hljs-attr">badge:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">format:</span> <span class="hljs-string">'md'</span>
        <span class="hljs-attr">output:</span> <span class="hljs-string">'both'</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Add</span> <span class="hljs-string">Coverage</span> <span class="hljs-string">PR</span> <span class="hljs-string">Comment</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">marocchino/sticky-pull-request-comment@v2</span>
      <span class="hljs-attr">if:</span> <span class="hljs-string">github.event_name</span> <span class="hljs-string">==</span> <span class="hljs-string">'pull_request'</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">recreate:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">path:</span> <span class="hljs-string">code-coverage-results.md</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Upload</span> <span class="hljs-string">Coverage</span> <span class="hljs-string">Artifact</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/upload-artifact@v3.1.0</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">test-coverage-report</span>
        <span class="hljs-attr">path:</span> <span class="hljs-string">code-coverage-results.md</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Upload</span> <span class="hljs-string">Build</span> <span class="hljs-string">Artifact</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/upload-artifact@v3.1.0</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">ci-nuget</span>
        <span class="hljs-attr">path:</span> <span class="hljs-string">src/Example/bin/Release/net6.0</span>
</code></pre>
<p>This workflow runs on all pull requests or pushes to the <code>master</code> branch and builds a .NET 6 app on an Ubuntu runner. It does not set any token permissions and pins its Actions using the easier to read version tags. It consists of one job with several steps:</p>
<ul>
<li>checkout the code</li>
<li>set up .NET</li>
<li>restore dependencies</li>
<li>build the .NET code</li>
<li>run unit tests</li>
<li>create a code coverage summary report</li>
<li>add the report to the PR</li>
<li>upload the report and build artifacts</li>
</ul>
<h3 id="heading-secured-net-build">Secured .NET Build</h3>
<p>Now let's secure the workflow using the StepSecurity Secure Workflows app:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">CI</span> <span class="hljs-string">Build</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span> [ <span class="hljs-string">master</span> ]
  <span class="hljs-attr">pull_request:</span>
    <span class="hljs-attr">branches:</span> [ <span class="hljs-string">master</span> ]

<span class="hljs-attr">env:</span>
  <span class="hljs-attr">DOTNET_NOLOGO:</span> <span class="hljs-literal">true</span>                     <span class="hljs-comment"># Disable the .NET logo in the console output</span>
  <span class="hljs-attr">DOTNET_SKIP_FIRST_TIME_EXPERIENCE:</span> <span class="hljs-literal">true</span> <span class="hljs-comment"># Disable the .NET first time experience</span>
  <span class="hljs-attr">DOTNET_CLI_TELEMETRY_OPTOUT:</span> <span class="hljs-literal">true</span>       <span class="hljs-comment"># Disable sending .NET CLI telemetry to Microsoft</span>

<span class="hljs-attr">permissions:</span>  <span class="hljs-comment"># added using https://github.com/step-security/secure-workflows</span>
  <span class="hljs-attr">contents:</span> <span class="hljs-string">read</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">build:</span>
    <span class="hljs-attr">permissions:</span>
      <span class="hljs-attr">contents:</span> <span class="hljs-string">read</span>  <span class="hljs-comment"># for actions/checkout to fetch code</span>
      <span class="hljs-attr">pull-requests:</span> <span class="hljs-string">write</span>  <span class="hljs-comment"># for marocchino/sticky-pull-request-comment to create or update PR comment</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">CI</span> <span class="hljs-string">Build</span>
    <span class="hljs-attr">steps:</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Harden</span> <span class="hljs-string">Runner</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">step-security/harden-runner@74b568e8591fbb3115c70f3436a0c6b0909a8504</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">egress-policy:</span> <span class="hljs-string">audit</span> <span class="hljs-comment"># <span class="hljs-doctag">TODO:</span> change to 'egress-policy: block' after couple of runs</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@d0651293c4a5a52e711f25b41b05b2212f385d28</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">fetch-depth:</span> <span class="hljs-number">0</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">.NET</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-dotnet@c0d4ad69d8bd405d234f1c9166d383b7a4f69ed8</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">dotnet-version:</span> <span class="hljs-number">6.0</span><span class="hljs-string">.x</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Restore</span> <span class="hljs-string">Dependencies</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">restore</span> <span class="hljs-string">src/Example.sln</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">build</span> <span class="hljs-string">src/Example.sln</span> <span class="hljs-string">--configuration</span> <span class="hljs-string">Release</span> <span class="hljs-string">--no-restore</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Run</span> <span class="hljs-string">Unit</span> <span class="hljs-string">Tests</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">test</span> <span class="hljs-string">src/Example.sln</span> <span class="hljs-string">--configuration</span> <span class="hljs-string">Release</span> <span class="hljs-string">--no-build</span> <span class="hljs-string">--verbosity</span> <span class="hljs-string">normal</span> <span class="hljs-string">--collect:"XPlat</span> <span class="hljs-string">Code</span> <span class="hljs-string">Coverage"</span> <span class="hljs-string">--results-directory</span> <span class="hljs-string">./coverage</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Code</span> <span class="hljs-string">Coverage</span> <span class="hljs-string">Summary</span> <span class="hljs-string">Report</span>
      <span class="hljs-attr">uses:</span>  <span class="hljs-string">irongut/CodeCoverageSummary@51cc3a756ddcd398d447c044c02cb6aa83fdae95</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">filename:</span> <span class="hljs-string">coverage/**/coverage.cobertura.xml</span>
        <span class="hljs-attr">badge:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">format:</span> <span class="hljs-string">'md'</span>
        <span class="hljs-attr">output:</span> <span class="hljs-string">'both'</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Add</span> <span class="hljs-string">Coverage</span> <span class="hljs-string">PR</span> <span class="hljs-string">Comment</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">marocchino/sticky-pull-request-comment@39c5b5dc7717447d0cba270cd115037d32d28443</span>
      <span class="hljs-attr">if:</span> <span class="hljs-string">github.event_name</span> <span class="hljs-string">==</span> <span class="hljs-string">'pull_request'</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">recreate:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">path:</span> <span class="hljs-string">code-coverage-results.md</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Upload</span> <span class="hljs-string">Coverage</span> <span class="hljs-string">Summary</span> <span class="hljs-string">Artifact</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">test-coverage-report</span>
        <span class="hljs-attr">path:</span> <span class="hljs-string">code-coverage-results.md</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Upload</span> <span class="hljs-string">Artifact</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">ci-nuget</span>
        <span class="hljs-attr">path:</span> <span class="hljs-string">src/Example/bin/Release/net6.0</span>
</code></pre>
<p>As you can see, the app makes several changes to the workflow.</p>
<p>It starts by setting the default token permissions for all jobs to <code>read</code>:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">permissions:</span>  <span class="hljs-comment"># added using https://github.com/step-security/secure-workflows</span>
  <span class="hljs-attr">contents:</span> <span class="hljs-string">read</span>
</code></pre>
<p>In the <code>build</code> job it sets the required token permissions for the Actions used:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">build:</span>
    <span class="hljs-attr">permissions:</span>
      <span class="hljs-attr">contents:</span> <span class="hljs-string">read</span>  <span class="hljs-comment"># for actions/checkout to fetch code</span>
      <span class="hljs-attr">pull-requests:</span> <span class="hljs-string">write</span>  <span class="hljs-comment"># for marocchino/sticky-pull-request-comment to create or update PR comment</span>
</code></pre>
<p>The Harden Runner Action is added in audit mode as the first step in the job:</p>
<pre><code class="lang-yaml">    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Harden</span> <span class="hljs-string">Runner</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">step-security/harden-runner@74b568e8591fbb3115c70f3436a0c6b0909a8504</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">egress-policy:</span> <span class="hljs-string">audit</span> <span class="hljs-comment"># <span class="hljs-doctag">TODO:</span> change to 'egress-policy: block' after couple of runs</span>
</code></pre>
<p>And, every Action is now pinned by commit SHA instead of a version tag, for example:</p>
<pre><code class="lang-yaml">    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">.NET</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-dotnet@c0d4ad69d8bd405d234f1c9166d383b7a4f69ed8</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">dotnet-version:</span> <span class="hljs-number">6.0</span><span class="hljs-string">.x</span>
</code></pre>
<h2 id="heading-where-do-we-go-from-here">Where do we go from here?</h2>
<p>Using Secure Workflows to restrict token permissions and pin Actions to a commit SHA is quick and easy to do for all your workflows. The next step is to continue to develop your project while Harden Runner audits outbound connections from your workflows.</p>
<p>In a <a target="_blank" href="https://blog.taranissoftware.com/secure-your-workflows-with-stepsecurity-harden-runner">follow-up article</a> I show how to use the audit reports and configure Harden Runner in block mode to restrict outbound connections and further improve the security of your GitHub Actions software supply chain.</p>
<p> </p>
<p> </p>
<p>Cover image contains a vector created by <strong>fullvector</strong> from <a target="_blank" href="https://www.freepik.com/vectors/web-programming">www.freepik.com</a>.</p>
<p>Software Supply Chain Attacks chart is from Sonatype's <a target="_blank" href="https://www.sonatype.com/hubfs/SSSC-Report-2021_0913_PM_2.pdf">2021 State of the Software Supply Chain</a> report.</p>
]]></content:encoded></item><item><title><![CDATA[My new app doesn't use .NET MAUI! Why?]]></title><description><![CDATA[I have a confession to make, I'm working on a new cross platform app and we're using Xamarin.Forms rather than .NET MAUI.😲 Xamarin.Forms will be unsupported after November 2023 so why did we make this choice?

Background
First a little background......]]></description><link>https://blog.taranissoftware.com/my-new-app-doesnt-use-net-maui-why</link><guid isPermaLink="true">https://blog.taranissoftware.com/my-new-app-doesnt-use-net-maui-why</guid><category><![CDATA[Xamarin]]></category><category><![CDATA[#dotnet-maui]]></category><category><![CDATA[.NET]]></category><category><![CDATA[dotnet]]></category><dc:creator><![CDATA[Dave Murray]]></dc:creator><pubDate>Sun, 31 Jul 2022 19:17:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1659295902683/QJV8n51gL.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I have a confession to make, I'm working on a new cross platform app and we're using Xamarin.Forms rather than .NET MAUI.😲 Xamarin.Forms will be unsupported after November 2023 so why did we make this choice?</p>
<p><a target="_blank" href="https://dotnet.microsoft.com/en-us/platform/support/policy/xamarin"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658165191838/2gnl7QoS6.webp" alt="Xamarin Roadmap" class="image--center mx-auto" /></a></p>
<h2 id="heading-background">Background</h2>
<p>First a little background...</p>
<p>For the last two years I've been maintaining three iOS apps written with Xamarin.iOS and <a target="_blank" href="https://docs.microsoft.com/xamarin/ios/user-interface/monotouch.dialog/">MonoTouch.Dialog</a> (anyone remember MT.D?). These apps are part of a larger compliance management product used internationally to solve complex asset management, auditing, permitting &amp; compliance challenges and had previously been languishing without any love for a while. I've been working to get them back in-step with the rest of the product and following the latest Apple guidelines.</p>
<p>This has been an interesting experience since I hadn't actually used MonoTouch.Dialog before but fortunately it is easy to learn and surprisingly powerful. I've even written several custom controls for it. Kudos to <a target="_blank" href="https://github.com/migueldeicaza">Miguel de Icaza</a> and the original Xamarin team that MonoTouch.Dialog not only works well for our purpose but is still working 12 years and many versions of iOS later!🙇‍♂️ Genius. Now that our existing iOS apps have achieved parity with the rest of the product the time has come to create the first cross platform version of the most used app.🎉</p>
<p>The app itself is used for on-site data collection on civil engineering projects like road, railway and airport construction. It needs to connect to an existing backend web service to sync data, take photos, get the device's location and process different types of customisable data entry form using the same business logic as the existing app. Nothing out of the ordinary for an enterprise app so why did we choose the older Xamarin.Forms rather than the new .NET MAUI hotness?</p>
<h2 id="heading-deciding-factors">Deciding Factors</h2>
<p>A number of factors fed into our decision to use Xamarin.Forms over .NET MAUI.</p>
<h3 id="heading-tooling">Tooling</h3>
<p>The tooling for .NET MAUI still requires a preview version of Visual Studio 2022 and there is no UI for archiving and publishing an app. Although the Windows tooling should be updated in August, the macOS version won't be ready until November (hopefully) and we have concerns about what will be included and it's stability. We don't want to get to the end of development only to run into problems publishing our app.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658165085956/gamQ3_LuL.webp" alt=".NET MAUI Roadmap" class="image--center mx-auto" /></p>
<h3 id="heading-library-support">Library Support</h3>
<p>Although library developers are working towards a .NET MAUI release some of the libraries I normally use for Xamarin.Forms apps are not ready yet and a few don't have plans for a .NET MAUI version which means waiting, finding alternatives or creating our own libraries before we can start developing the app.</p>
<h3 id="heading-experience">Experience</h3>
<p>We have a small team of developers with Xamarin.iOS and Xamarin.Forms experience and a larger team of .NET developers with no Xamarin experience but only one developer has any experience with .NET MAUI. (me!👋)</p>
<h3 id="heading-documentation">Documentation</h3>
<p>While the .NET MAUI documentation is improving it is still quite sparse, especially when compared to the wealth of documentation for Xamarin.Forms. There are also a lot more books, blog posts, FAQs, Stack Overflow answers, videos, podcasts, etc to help with Xamarin.Forms than .NET MAUI which will help the members of the team with less experience.</p>
<h3 id="heading-time">Time</h3>
<p>This is the most important consideration and contributes to several of the other factors I already listed. Due to wider business goals and customer requests we need an Android version of this app as soon as possible. Preferably next month, or sooner.😉 Development needs to be fast and we can't wait for Visual Studio &amp; .NET MAUI to mature.</p>
<p>In a lot of ways we've been unlucky with the timing of events. If .NET MAUI had released with .NET 6 last year or if the tooling had been complete with the GA in May or if we had more time for development we probably would have picked MAUI.</p>
<h2 id="heading-technical-debt">Technical Debt</h2>
<p>Given the support dates for Xamarin.Forms we will need to move this app to .NET MAUI some time next year so how can we manage this technical debt and reduce the time to repay it? I've proposed several strategies based on my knowledge of both platforms.</p>
<h3 id="heading-mvvm-framework">MVVM Framework</h3>
<p>I made the decision not to use a third-party MVVM framework in our Xamarin.Forms app. Instead we will use the in-built MVVM features of the platform with base classes to handle <code>INotifyPropertyChanged</code> and the required boilerplate code. Not using a third-party framework means no-one on the team has to learn it before they can contribute. We also don't run the risk of picking a framework that doesn't get ported to .NET MAUI. And, we're not restricted in our choice of dependency injection library.</p>
<h3 id="heading-dependency-injection">Dependency Injection</h3>
<p>By using the <strong>Microsoft.Extensions.DependencyInjection</strong> library for dependency injection and inversion of control, the code we write for Xamarin.Forms should be compatible with .NET MAUI with minimal changes.</p>
<h3 id="heading-json-library">JSON Library</h3>
<p>The existing apps use the old <strong>System.Json</strong> namespace to serialise and deserialise data. <strong>System.Json</strong> was designed for Silverlight and is not the most performant way to work with JSON in .NET today. Until recently I would have suggested we replace it with the familiar <strong>Newtonsoft.Json</strong>. But, having read Jon Pepper's post about <a target="_blank" href="https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui">Perfomance Improvements in .NET MAUI</a> and the <a target="_blank" href="https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#remove-newtonsoft-json-usage">reduction in app size</a> they were able to achieve with the <a target="_blank" href="https://github.com/microsoft/dotnet-podcasts">.NET Podcasts</a> sample app by switching from <strong>Newtonsoft.Json</strong> to <strong>System.Text.Json</strong>, I proposed we use <strong>System.Text.Json</strong> instead. This might take us a little more time now but we should see similar improvements in app size and it will save us time when moving to .NET MAUI.</p>
<h3 id="heading-vector-images">Vector Images</h3>
<p>.NET MAUI can generate an icon, splash screen and all the required images for an app from SVG images. Using the <a target="_blank" href="https://github.com/luberda-molinet/FFImageLoading">FFImageLoading</a> library we can also use SVG images in Xamarin.Forms so I've requested all image assets in SVG format. This should reduce the time spent worrying over images when developing both versions of the app and when we must use a PNG in Xamarin.Forms (e.g. for the Android splash screen) we can export the necessary resolutions from the vector image.</p>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>I'm still excited about the future of .NET MAUI but we felt the platform needs to mature a little more before we can commit to it. I think with the release of .NET 7 and the tooling for macOS in November the picture will look better and I can start to plan moving all our apps to .NET MAUI next year.</p>
<p> </p>
<p> </p>
<p>Cover image includes a vector created by <a target="_blank" href="https://www.freepik.com/vectors/tumbleweed">upklyak - www.freepik.com</a></p>
]]></content:encoded></item><item><title><![CDATA[Code Coverage Summary v1.3.0 Released!]]></title><description><![CDATA[I'm pleased to announce the latest release of my popular GitHub Action Code Coverage Summary. Code Coverage Summary v1.3.0 brings new features, compatibility with more test frameworks and improvements to supply chain security.
Code Coverage Summary r...]]></description><link>https://blog.taranissoftware.com/code-coverage-summary-v130-released</link><guid isPermaLink="true">https://blog.taranissoftware.com/code-coverage-summary-v130-released</guid><category><![CDATA[github-actions]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[Devops]]></category><category><![CDATA[.NET]]></category><dc:creator><![CDATA[Dave Murray]]></dc:creator><pubDate>Fri, 29 Jul 2022 16:09:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1659046502434/kIbdX5HxT.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I'm pleased to announce the latest release of my popular GitHub Action <a target="_blank" href="https://github.com/marketplace/actions/code-coverage-summary">Code Coverage Summary</a>. Code Coverage Summary v1.3.0 brings new features, compatibility with more test frameworks and improvements to supply chain security.</p>
<p>Code Coverage Summary reads Cobertura format coverage files from your test suite and outputs a text or markdown summary that can be posted as a Pull Request comment or included in Release Notes to give you an immediate insight into the health of your code without using a third-party site.</p>
<p>Code Coverage Summary is designed for use with any test framework that outputs coverage in Cobertura XML format including <a target="_blank" href="https://github.com/coverlet-coverage/coverlet">Coverlet</a>, <a target="_blank" href="https://github.com/gcovr/gcovr">gcovr</a>, <a target="_blank" href="https://github.com/simplecov-ruby/simplecov">simplecov</a> and <a target="_blank" href="https://uk.mathworks.com/help/matlab/ref/matlab.unittest.plugins.codecoverageplugin-class.html">MATLAB</a>. See the <a target="_blank" href="https://github.com/irongut/CodeCoverageSummary/wiki/Frequently-Asked-Questions#which-testing-tools-does-ccs-work-with">FAQ</a> for more details. </p>
<h2 id="heading-release-notes">Release Notes</h2>
<ul>
<li>🗃 Now supports glob pattern matching for multiple coverage files</li>
<li>✔ Improved compatibility with simplecov &amp; simplecov-cobertura</li>
<li>➕ Now compatible with MATLAB's code coverage plugin</li>
<li>🔐 Docker image signed using <a target="_blank" href="https://www.sigstore.dev/">Sigstore</a></li>
<li>🕵️‍♂️ Compatible with <a target="_blank" href="https://github.com/step-security/secure-workflows">StepSecurity Secure Workflows</a></li>
</ul>
<h2 id="heading-links">Links</h2>
<ul>
<li>🛍 <a target="_blank" href="https://github.com/marketplace/actions/code-coverage-summary">GitHub Marketplace</a></li>
<li>👨‍💻 <a target="_blank" href="https://github.com/irongut/CodeCoverageSummary">Repository</a></li>
<li>📚 <a target="_blank" href="https://github.com/irongut/CodeCoverageSummary/wiki">Documentation</a></li>
</ul>
<p> </p>
<p> </p>
<p>Blue vector created by <a target="_blank" href="https://www.freepik.com/vectors/blue">vectorjuice - www.freepik.com</a></p>
]]></content:encoded></item><item><title><![CDATA[Thank-you GitHub!]]></title><description><![CDATA[I woke up this morning...🎶
No, I'm not writing a blues song and actually this was yesterday but I coudn't resist that opening line.😄 I awoke to find a text message telling me I had a donation via GitHub Sponsors! Who was this and which project had ...]]></description><link>https://blog.taranissoftware.com/thank-you-github</link><guid isPermaLink="true">https://blog.taranissoftware.com/thank-you-github</guid><category><![CDATA[GitHub]]></category><category><![CDATA[Open Source]]></category><dc:creator><![CDATA[Dave Murray]]></dc:creator><pubDate>Sat, 23 Jul 2022 22:24:13 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1658613412884/yx9L8bgAS.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I woke up this morning...🎶</p>
<p>No, I'm not writing a blues song and actually this was yesterday but I coudn't resist that opening line.😄 I awoke to find a text message telling me I had a donation via GitHub Sponsors! Who was this and which project had sparked their donation? Once I grabbed a coffee I investigated...</p>
<p>It turns out I'd missed a recent email from <a target="_blank" href="https://github.com/sponsors">GitHub Sponsors</a> telling me I would receive a payment from GitHub itself. GitHub hosted <a target="_blank" href="https://maintainermonth.github.com/">Maintainer Month</a> in June, an event for open source maintainers to gather, share and be celebrated. This included conferences, meetups and <a target="_blank" href="https://www.dotnetrocks.com/?show=1798">podcasts</a> where maintainers shared their knowledge and experience. It was also a chance to reflect on the challenges that face open source maintainers, which aren't always technical and can come with a lack of boundaries, vacations or emotional support.</p>
<p>As part of Maintainer Month, GitHub wanted to celebrate and give additional support to the open source projects they depend on to build and run GitHub and all the software they maintain. So they are distributing $500,000 to over 900 maintainers of their open source dependencies who are also signed up with GitHub Sponsors!</p>
<p>So I knew who and why but which project was harder to determine because the GitHub Sponsors programme doesn't tell you which project your sponsor uses. Looking through the dependency graphs for my open source projects I was able to determine that GitHub are using <a target="_blank" href="https://github.com/marketplace/actions/code-coverage-summary">Code Coverage Summary</a>, my GitHub Action that reads coverage files from your test suite and outputs a summary to give you immediate insights into the health of your code.</p>
<p>This has been a tough year for me personally and GitHub's donation put a smile on my face and really made my week so I want to thank them for supporting me and the other people who also received donations as part of Maintainer Month.</p>
<p>Thank-you GitHub! 🥰
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658613712179/byJWwStx6.webp" alt="mona-heart.webp" /></p>
]]></content:encoded></item><item><title><![CDATA[Build .NET MAUI apps with GitHub Actions]]></title><description><![CDATA[Writing a workflow to build a cross platform .NET MAUI app with GitHub Actions is more complicated than the average .NET app. You have to install the .NET MAUI workload and take into consideration which runner can be used to build each platform. In t...]]></description><link>https://blog.taranissoftware.com/build-net-maui-apps-with-github-actions</link><guid isPermaLink="true">https://blog.taranissoftware.com/build-net-maui-apps-with-github-actions</guid><category><![CDATA[.NET]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[#dotnet-maui]]></category><category><![CDATA[github-actions]]></category><category><![CDATA[Xamarin]]></category><dc:creator><![CDATA[Dave Murray]]></dc:creator><pubDate>Sun, 17 Jul 2022 10:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1658022672200/82eTRFFaW.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Writing a workflow to build a cross platform .NET MAUI app with GitHub Actions is more complicated than the average .NET app. You have to install the .NET MAUI workload and take into consideration which runner can be used to build each platform. In this article I update an <a target="_blank" href="/building-net-maui-apps-with-github-actions">earlier workflow</a> to build a cross platform .NET MAUI app for Android, iOS, MacCatalyst and Windows using GitHub Actions, explaining each step.</p>
<h2 id="heading-introduction">Introduction</h2>
<p>The .NET MAUI app this workflow builds is my demo app <a target="_blank" href="https://github.com/irongut/MauiBeach">MAUI Beach</a> but it should be easily adaptable for any .NET MAUI app.</p>
<p>This is a <abbr title="Continuous Integration">CI</abbr> build workflow without any testing, signing or deployment steps that will build a .NET MAUI app for Android, iOS, MacCatalyst and Windows in four separate jobs that run in parallel. Each build job will usually take between 3 and 10 minutes to run, Windows and macOS runners are charged at an <abbr title="2x for Windows, 10x for macOS">increased rate</abbr> so be aware this workflow can eat into your GitHub Actions minutes. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658018993415/aJz-57Lbg.webp" alt=".NET MAUI GitHub Actions CI Build" class="image--center mx-auto" /></p>
<h2 id="heading-set-up-the-workflow">Set up the Workflow</h2>
<p>The first part of the workflow is standard stuff for GitHub Actions - set the workflow name, the events it responds to and any environment variables.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">CI</span> <span class="hljs-string">Build</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span> [ <span class="hljs-string">master</span> ]
    <span class="hljs-attr">paths-ignore:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'**/*.md'</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'**/*.gitignore'</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'**/*.gitattributes'</span>
  <span class="hljs-attr">pull_request:</span>
    <span class="hljs-attr">branches:</span> [ <span class="hljs-string">master</span> ]
  <span class="hljs-attr">workflow_dispatch:</span>
<span class="hljs-attr">permissions:</span>
  <span class="hljs-attr">contents:</span> <span class="hljs-string">read</span>

<span class="hljs-attr">env:</span>
  <span class="hljs-attr">DOTNET_NOLOGO:</span> <span class="hljs-literal">true</span>                     <span class="hljs-comment"># Disable the .NET logo</span>
  <span class="hljs-attr">DOTNET_SKIP_FIRST_TIME_EXPERIENCE:</span> <span class="hljs-literal">true</span> <span class="hljs-comment"># Disable the .NET first time experience</span>
  <span class="hljs-attr">DOTNET_CLI_TELEMETRY_OPTOUT:</span> <span class="hljs-literal">true</span>       <span class="hljs-comment"># Disable sending .NET CLI telemetry</span>
</code></pre>
<p>As you can see, this workflow will respond to push and pull request events on the <code>master</code> branch and also has a manual <code>workflow_dispatch</code> trigger. To prevent unnecessary builds, the push event ignores changes that only involve markdown files, <code>.gitignore</code> or <code>.gitattributes</code>.</p>
<p>The environment variables tell the .NET CLI not to show the logo, to skip the first time experience and disable telemetry. This should help improve build times a little.</p>
<h2 id="heading-android-build-job">Android Build Job</h2>
<pre><code class="lang-yaml"><span class="hljs-comment"># MAUI Android Build</span>
  <span class="hljs-attr">build-android:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">windows-2022</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">Android</span> <span class="hljs-string">Build</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">.NET</span> <span class="hljs-number">6</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-dotnet@v2</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">dotnet-version:</span> <span class="hljs-number">6.0</span><span class="hljs-string">.x</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">MAUI</span> <span class="hljs-string">Workload</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">workload</span> <span class="hljs-string">install</span> <span class="hljs-string">maui</span> <span class="hljs-string">--ignore-failed-sources</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Restore</span> <span class="hljs-string">Dependencies</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">restore</span> <span class="hljs-string">src/MauiBeach/MauiBeach.csproj</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">MAUI</span> <span class="hljs-string">Android</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">publish</span> <span class="hljs-string">src/MauiBeach/MauiBeach.csproj</span> <span class="hljs-string">-c</span> <span class="hljs-string">Release</span> <span class="hljs-string">-f</span> <span class="hljs-string">net6.0-android</span> <span class="hljs-string">--no-restore</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Upload</span> <span class="hljs-string">Android</span> <span class="hljs-string">Artifact</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/upload-artifact@v3.1.0</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">name:</span> <span class="hljs-string">mauibeach-android-ci-build</span>
          <span class="hljs-attr">path:</span> <span class="hljs-string">src/MauiBeach/bin/Release/net6.0-android/*Signed.a*</span>
</code></pre>
<p>Building a .NET MAUI app for Android can be done on macOS or Windows so this build job uses a Windows runner as they are less expensive than macOS. Visual Studio 2022 is required so we use a <code>windows-2022</code> runner. The <code>windows-latest</code> runner currently uses the same Windows Server 2022 image so it could be used instead.</p>
<p>The job starts by checking out the code as normal and then installs .NET 6. During preview a pre-release version of .NET 6 was required but since .NET MAUI is now GA we can use the standard release.</p>
<p>In the earlier version of this workflow I installed a distribution of JDK 11 at this point. This is no longer required. 🎉</p>
<p>The .NET MAUI workload is not included in GitHub's <code>windows-2022</code> runner so it must also be installed. The MAUI workload installs the required workloads for every platform so we only need one install command: <code>dotnet workload install maui</code></p>
<p>Now we get to the meat of the job - restore dependencies and build the app. We can use a standard <code>dotnet publish</code> command to build our app for Android. The important argument here is <code>-f net6.0-android</code> to set the framework we want to build. I like my CI builds to be release builds so I've used the argument <code>-c Release</code> as well as <code>--no-restore</code> so dependencies are only restored once.</p>
<p>Finally the job uploads the build artifacts, MAUI Beach creates <abbr title="Android App Bundle">AAB</abbr> and <abbr title="Android Package">APK</abbr> files so both are uploaded.</p>
<h2 id="heading-windows-build-job">Windows Build Job</h2>
<pre><code class="lang-yaml"><span class="hljs-comment"># MAUI Windows Build</span>
  <span class="hljs-attr">build-windows:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">windows-2022</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">Windows</span> <span class="hljs-string">Build</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">.NET</span> <span class="hljs-number">6</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-dotnet@v2</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">dotnet-version:</span> <span class="hljs-number">6.0</span><span class="hljs-string">.x</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">MAUI</span> <span class="hljs-string">Workload</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">workload</span> <span class="hljs-string">install</span> <span class="hljs-string">maui</span> <span class="hljs-string">--ignore-failed-sources</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Restore</span> <span class="hljs-string">Dependencies</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">restore</span> <span class="hljs-string">src/MauiBeach/MauiBeach.csproj</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">MAUI</span> <span class="hljs-string">Windows</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">publish</span> <span class="hljs-string">src/MauiBeach/MauiBeach.csproj</span> <span class="hljs-string">-c</span> <span class="hljs-string">Release</span> <span class="hljs-string">-f</span> <span class="hljs-string">net6.0-windows10.0.19041.0</span> <span class="hljs-string">--no-restore</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Upload</span> <span class="hljs-string">Windows</span> <span class="hljs-string">Artifact</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/upload-artifact@v3.1.0</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">name:</span> <span class="hljs-string">mauibeach-windows-ci-build</span>
          <span class="hljs-attr">path:</span> <span class="hljs-string">src/MauiBeach/bin/Release/net6.0-windows10.0.19041.0/win10-x64/AppPackages/MauiBeach*/MauiBeach*.msix</span>
</code></pre>
<p>Building a .NET MAUI app for the Windows platform requires Windows and Visual Studio 2022 so the Windows build job also uses a <code>windows-2022</code> runner. The <code>windows-latest</code> runner would also work.</p>
<p>The job starts by checking out the code then installs .NET 6 and the .NET MAUI workload just like the Android build job.</p>
<p>During preview we also needed to set up MSBuild but with GA we can now build .NET MAUI apps for Windows with the .NET CLI commands. 🥳</p>
<p>Again I want to create a release build and not restore dependencies during build. We use a standard <code>dotnet publish</code> command to build our .NET MAUI Windows app, to set the framework we want to build we use the <code>-f net6.0-windows10.0.19041.0</code> argument. The framework must match the Target Framework specified for Windows in the application's project file.</p>
<p>The last step for this job is to upload the Windows app package as a build artifact.</p>
<h2 id="heading-ios-build-job">iOS Build Job</h2>
<pre><code class="lang-yaml"><span class="hljs-comment"># MAUI iOS Build</span>
  <span class="hljs-attr">build-ios:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">macos-12</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">iOS</span> <span class="hljs-string">Build</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">.NET</span> <span class="hljs-number">6</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-dotnet@v2</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">dotnet-version:</span> <span class="hljs-number">6.0</span><span class="hljs-string">.x</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">MAUI</span> <span class="hljs-string">Workload</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">workload</span> <span class="hljs-string">install</span> <span class="hljs-string">maui</span> <span class="hljs-string">--ignore-failed-sources</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Restore</span> <span class="hljs-string">Dependencies</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">restore</span> <span class="hljs-string">src/MauiBeach/MauiBeach.csproj</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">MAUI</span> <span class="hljs-string">iOS</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">build</span> <span class="hljs-string">src/MauiBeach/MauiBeach.csproj</span> <span class="hljs-string">-c</span> <span class="hljs-string">Release</span> <span class="hljs-string">-f</span> <span class="hljs-string">net6.0-ios</span> <span class="hljs-string">--no-restore</span> <span class="hljs-string">/p:buildForSimulator=True</span> <span class="hljs-string">/p:packageApp=True</span> <span class="hljs-string">/p:ArchiveOnBuild=False</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Upload</span> <span class="hljs-string">iOS</span> <span class="hljs-string">Artifact</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/upload-artifact@v3.1.0</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">name:</span> <span class="hljs-string">mauibeach-ios-ci-build</span>
          <span class="hljs-attr">path:</span> <span class="hljs-string">src/MauiBeach/bin/Release/net6.0-ios/iossimulator-x64/**/*.app</span>
</code></pre>
<p>Building a .NET MAUI iOS app requires macOS, Visual Studio 2022 and Xcode 13.3 so the iOS build job needs to use a <code>macos-12</code> runner.</p>
<p>⚠ The <code>macos-latest</code> runner currently uses a macOS 11 image so will not work. ⚠</p>
<p>As before, the job starts by checking out the code then installs .NET 6 and the .NET MAUI workload.</p>
<p>Now we can restore our dependencies and build our app. Unlike the other build jobs we can't use <code>dotnet publish</code> for the iOS build job because it signs the app for distribution and fails if a provisioning profile is not present. I don't want to include that in this workflow so use <code>dotnet build</code> instead. To build an iOS app we need the <code>-f net6.0-ios</code> argument to set the framework.</p>
<p>Finally the job uploads the <code>*.app</code> folder as a build artifact.</p>
<h2 id="heading-maccatalyst-build-job">MacCatalyst Build Job</h2>
<pre><code class="lang-yaml"><span class="hljs-comment"># MAUI MacCatalyst Build</span>
  <span class="hljs-attr">build-mac:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">macos-12</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">MacCatalyst</span> <span class="hljs-string">Build</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">.NET</span> <span class="hljs-number">6</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-dotnet@v2</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">dotnet-version:</span> <span class="hljs-number">6.0</span><span class="hljs-string">.x</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">MAUI</span> <span class="hljs-string">Workload</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">workload</span> <span class="hljs-string">install</span> <span class="hljs-string">maui</span> <span class="hljs-string">--ignore-failed-sources</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Restore</span> <span class="hljs-string">Dependencies</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">restore</span> <span class="hljs-string">src/MauiBeach/MauiBeach.csproj</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">MAUI</span> <span class="hljs-string">MacCatalyst</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">publish</span> <span class="hljs-string">src/MauiBeach/MauiBeach.csproj</span> <span class="hljs-string">-c</span> <span class="hljs-string">Release</span> <span class="hljs-string">-f</span> <span class="hljs-string">net6.0-maccatalyst</span> <span class="hljs-string">--no-restore</span> <span class="hljs-string">-p:BuildIpa=True</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Upload</span> <span class="hljs-string">MacCatalyst</span> <span class="hljs-string">Artifact</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/upload-artifact@v3.1.0</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">name:</span> <span class="hljs-string">mauibeach-macos-ci-build</span>
          <span class="hljs-attr">path:</span> <span class="hljs-string">src/MauiBeach/bin/Release/net6.0-maccatalyst/maccatalyst-x64/publish/*.pkg</span>
</code></pre>
<p>Building a .NET MAUI app for MacCatalyst requires macOS, Visual Studio 2022 and Xcode 13.3 so the MacCatalyst build job needs to use a <code>macos-12</code> runner.</p>
<p>⚠ The <code>macos-latest</code> runner currently uses a macOS 11 image so will not work. ⚠</p>
<p>The MacCatalyst build job is very similar to the other build jobs. Using the <code>macos-12</code> runner it checks out the code then installs .NET 6 and the .NET MAUI workload.</p>
<p>We can use a standard <code>dotnet publish</code> command to build an app for MacCatalyst. For MacCatalyst the framework argument is <code>-f net6.0-maccatalyst</code> and we use the <code>-p:BuildIpa=True</code> argument to create a macOS app package. </p>
<p>The last step uploads the macOS app package as a build artifact.</p>
<h2 id="heading-and-finally">And Finally...</h2>
<p>The complete workflow can be found in my MAUI Beach repo:<br />
👩‍💻 <a target="_blank" href="https://github.com/irongut/MauiBeach/blob/master/.github/workflows/ci-build.yml">.github/workflows/ci-build.yml</a></p>
<p>And, you can see the workflow runs <a target="_blank" href="https://github.com/irongut/MauiBeach/actions/workflows/ci-build.yml">here</a>. 🏗</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1658021672696/l8YQ1I5So.webp" alt=".NET MAUI GitHub Actions Build Artifacts" class="image--center mx-auto" /></p>
<p>If I had tests for this app they would be run as an extra step in each job between the build step and uploading the build artifacts.</p>
<p>This workflow could be expanded to create a <abbr title="Continuous Delivery">CD</abbr> workflow that releases a .NET MAUI app on each platform. This requires the relevant certificate, provisioning profile or keystore for each platform, which should be saved as secrets in GitHub and then copied to the runner before the build step. For Android a signing step is added after the build step but for the other platforms this is handled by the <code>dotnet publish</code> command. The process should be similar to that for Xamarin.Forms and you can find more information <a target="_blank" href="https://devblogs.microsoft.com/dotnet/devops-for-dotnet-maui/">here</a>.</p>
<p> </p>
<p> </p>
<p>Cover image includes a vector created by brgfx from <a target="_blank" href="https://www.freepik.com/vectors/">www.freepik.com</a>.</p>
]]></content:encoded></item><item><title><![CDATA[GitHub Action - Edit Release v1.2.0]]></title><description><![CDATA[Edit Release is a GitHub Action for editing an existing release. Edit the Name, Draft status and Pre-release status of a release as well as adding text and the content of markdown files to the Body of a release.
Edit Release is built with .NET 6 and ...]]></description><link>https://blog.taranissoftware.com/github-action-edit-release-v120</link><guid isPermaLink="true">https://blog.taranissoftware.com/github-action-edit-release-v120</guid><category><![CDATA[github-actions]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[Devops]]></category><category><![CDATA[.NET]]></category><dc:creator><![CDATA[Dave Murray]]></dc:creator><pubDate>Fri, 08 Jul 2022 12:54:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1657242927030/wGxxrZPXt.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Edit Release is a GitHub Action for editing an existing release. Edit the Name, Draft status and Pre-release status of a release as well as adding text and the content of markdown files to the Body of a release.</p>
<p>Edit Release is built with .NET 6 and Docker using GitHub Container Registry and <a target="_blank" href="https://www.sigstore.dev/">sigstore</a>.</p>
<h2 id="heading-release-notes">Release Notes</h2>
<ul>
<li>Signed the Docker image</li>
<li>Updated libraries</li>
</ul>
<h2 id="heading-how-to-verify-the-docker-image">How to Verify the Docker Image</h2>
<ol>
<li>Install <a target="_blank" href="https://github.com/sigstore/cosign">sigstore/cosign</a>.</li>
<li>Run: <code>COSIGN_EXPERIMENTAL=1 cosign verify ghcr.io/irongut/editrelease:v1.2.0</code></li>
</ol>
<pre><code class="lang-txt">$ COSIGN_EXPERIMENTAL=1 cosign verify ghcr.io/irongut/editrelease:v1.2.0

Verification for ghcr.io/irongut/editrelease:v1.2.0 --
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - Existence of the claims in the transparency log was verified offline
  - Any certificates were verified against the Fulcio roots.
</code></pre>
<p>In the JSON data that follows the <code>docker-manifest-digest</code> values should contain the hash <code>sha256:a68ecd1ac7ba32ca07bc03b069f250200cf9e532e9496872aa4a3cb1d82bb167</code>.</p>
<h2 id="heading-links">Links</h2>
<ul>
<li>🛍 <a target="_blank" href="https://github.com/marketplace/actions/edit-release">GitHub Marketplace</a></li>
<li>👨‍💻 <a target="_blank" href="https://github.com/irongut/EditRelease">Repository</a></li>
</ul>
<p> </p>
<p> </p>
<p>People vector created by pch.vector - <a target="_blank" href="https://www.freepik.com/vectors/people">www.freepik.com</a></p>
]]></content:encoded></item><item><title><![CDATA[MAUI Beach - Updating to .NET MAUI GA]]></title><description><![CDATA[With .NET MAUI reaching GA release at the end of May I decided it was time to update my playground app. The last time I did any work on MAUI Beach it was with Preview 11 so a lot of things have changed. In this article I'll explain the steps I took a...]]></description><link>https://blog.taranissoftware.com/maui-beach-updating-to-net-maui-ga</link><guid isPermaLink="true">https://blog.taranissoftware.com/maui-beach-updating-to-net-maui-ga</guid><category><![CDATA[dotnet]]></category><category><![CDATA[.NET]]></category><category><![CDATA[#dotnet-maui]]></category><category><![CDATA[Xamarin]]></category><dc:creator><![CDATA[Dave Murray]]></dc:creator><pubDate>Sun, 03 Jul 2022 17:07:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1656812441607/N43xBPl6g.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>With .NET MAUI reaching GA release at the end of May I decided it was time to update my playground app. The last time I did any work on MAUI Beach it was with Preview 11 so a lot of things have changed. In this article I'll explain the steps I took and the issues I ran into.</p>
<p>The code for my .NET MAUI articles is available on GitHub: <a target="_blank" href="https://github.com/irongut/MauiBeach">irongut/MauiBeach</a></p>
<h2 id="heading-visual-studio-2022-preview">Visual Studio 2022 Preview?</h2>
<p>Although .NET MAUI is GA, Microsoft are still working on the tooling so <a target="_blank" href="https://visualstudio.microsoft.com/vs/preview/">preview versions of Visual Studio 2022</a> are still required. The completed .NET MAUI tooling should appear in a full release of Visual Studio 2022 in August for Windows and November for macOS.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1656809009462/xxYAriqZC.webp" alt=".NET MAUI Roadmap" class="image--center mx-auto" /></p>
<p>Currently the required versions are <strong>Visual Studio 2022 17.3 Preview 2</strong> on Windows or <strong>Visual Studio 2022 for Mac 17.3 Preview 2.1</strong> on macOS. When installing select the <strong>.NET Multi-platform App UI development</strong> workload. If you also develop Xamarin.Forms applications ensure the optional <strong>Xamarin</strong> item is ticked; see <a target="_blank" href="https://blog.taranissoftware.com/vs2022-173-preview-11-missing-xamarin-sdks">VS2022 17.3 Preview 1.1 - Missing Xamarin SDKs</a> for more details.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1656808979618/dyyjh6Jbb.webp" alt="Visual Studio 2022 Installer" class="image--center mx-auto" /></p>
<h2 id="heading-the-plan">The Plan</h2>
<p>There have been many changes to .NET MAUI since I last worked on MAUI Beach so I decided the best approach was to start fresh with a new project, update the code from the old project and then replace it in the repository. That way changes to required Nuget packages, project configuration and the platform specific files would all be handled and I would only need to make minor code changes.</p>
<p>This is a similar process to how I would convert a Xamarin.Forms app to .NET MAUI. When the .NET Upgrade Assistant gains MAUI support I will reappraise this opinion.</p>
<h2 id="heading-creating-a-new-net-maui-project">Creating a New .NET MAUI Project</h2>
<p>Creating a new .NET MAUI app in Visual Studio 2022 is a simple process. Select <strong>MAUI</strong> from the Project Types drop down and Visual Studio presents you with three options - <strong>.NET MAUI App</strong>, <strong>.NET MAUI Blazor App</strong> and <strong>.NET MAUI Class Library</strong>. Select <strong>.NET MAUI App</strong> and click <strong>Next</strong>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1656808864549/hp_AWrsj8.webp" alt="Create a New .NET MAUI Project" class="image--center mx-auto" /></p>
<p>Visual Studio then asks for the project name, location and solution name, like any other .NET project. I gave the project and solution the same names as my original project but a different location.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1656808902036/l7s-yC91C.webp" alt="Configure a New .NET MAUI Project" class="image--center mx-auto" /></p>
<p>Finally the Framework should be <strong>.NET 6.0 (Long-term support)</strong>. Click <strong>Create</strong> and a new cross platform app is created from the .NET MAUI template.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1656808947390/_m5ghc6Th.webp" alt="Additional .NET MAUI Project Information" class="image--center mx-auto" /></p>
<p>The default template includes platform folders for Android, iOS, MacCatalyst, Tizen and Windows. I'm not interested in the Tizen platform so I deleted the <code>Platforms/Tizen</code> folder. There is also a Target Framework entry for Tizen in the project file but it is commented out so we can ignore it.</p>
<p>At this point my next step was to build and run the template app on all platforms to ensure my development setup was working correctly. Which it was, phew! 😅</p>
<h2 id="heading-styles-and-resources">Styles and Resources</h2>
<p>Retracing my <a target="_blank" href="https://blog.taranissoftware.com/first-steps-on-maui-beach">First Steps on MAUI Beach</a> I decided to copy over my custom styles and colour palette to the new app first.</p>
<p>Back in .NET MAUI Preview 11 my styles and colours were all in one file: <code>Resources/Styles/DefaultTheme.xaml</code></p>
<p>In .NET MAUI GA the default template provides two files: <code>Resources/Styles/Colors.xaml</code> and <code>Resources/Styles/Styles.xaml</code>.</p>
<p>I replaced the contents of <code>Colors.xaml</code> with my colour pallete, removing all the default values. I also copied over the contents of <code>Platforms/Android/Resources/values/colors.xml</code> from the old project to the new project.</p>
<p>Styles was slightly more complicated, checking the template styles against my previous custom styles I spotted an extra property that I had not set for Shell (<code>TabBarForegroundColor</code>) and also decided to add a style for <code>TabbedPage</code>, based on the style in the template.</p>
<p>There have been some changes in the <code>Resources</code> folder structure too. Previously the images used to construct the application icon were in the <code>Resources</code> folder, now they live in <code>Resources/AppIcon</code>. Similarly the splash screen image has now moved to the <code>Resources/Splash</code> folder. I copied the images from my old project to the relevant locations in the new project and in Visual Studio set their <code>Build Action</code> properties.</p>
<p>The template project provides two custom fonts but I'm not using both of them in MAUI Beach. I deleted <code>OpenSans-Semibold.ttf</code> from the <code>Resources/Fonts</code> folder and removed the line of code that adds it to the app from <code>MauiProgram.cs</code>.</p>
<h3 id="heading-missing-image-build-issue">Missing Image Build Issue</h3>
<p>Initially I deleted the images provided by the MAUI template project but when I came to build my app I ran into errors that Resizetizer couldn't find the old images for the icon and splash screen despite them not being listed anywhere in the project file. Resizetizer is part of the tooling that creates the app icon, splash screen and other images for your app so hopefully this issue will be fixed as Visual Studio 2022 is updated. To get past it I restored the template images and was able to build my app, after that first build I was able to delete them again and everything worked. 🤷‍♂️</p>
<h2 id="heading-the-project-file">The Project File</h2>
<p>With my resouces in place I decided it was time to update the project file - <code>MauiBeach.csproj</code>. Examining the new project file I noticed a number of changes from the MAUI preview version, including the syntax for target frameworks and supported OS versions, so it was definitely worth recreating the app from scratch. I copied over my Application Title and Id then setup my icon and splash screen using the new paths to the images.</p>
<h2 id="heading-pages-viewmodels-and-services">Pages, ViewModels and Services</h2>
<p>Now we get into the actual C# code.</p>
<p>I was able to copy my ViewModels directly from the old project. The only modification required was to remove some <code>using</code> statements that are no longer needed because <a target="_blank" href="https://devblogs.microsoft.com/dotnet/welcome-to-csharp-10/#implicit-usings">implicit usings</a> is turned on in the new project file.</p>
<p>MAUI Beach contains one service - <code>DeviceInfoService</code>, which improves on .NET MAUI's built-in <code>DeviceInfo</code> class and is implemented using <a target="_blank" href="https://blog.taranissoftware.com/platform-specific-code-using-partial-classes-in-net-maui">platform specific partial classes</a>. I'll come back to this in the next section but for now I copied the cross platform partial class <code>Services/DeviceInfoService.cs</code> into the new project.</p>
<p>MAUI Beach contains three pages so I created three new pages with the same names in the new project. When creating pages be sure to pick <strong>.NET MAUI Content Page (XAML)</strong> not <strong>Content Page (XAML)</strong> or you'll get the Xamarin version! (Yes, I did that wrong. 🤦‍♂️) I copied the contents of the XAML and C# code-behind files from my old project into the new pages and modified the <code>using</code> statements.</p>
<h2 id="heading-platform-specific-code">Platform Specific Code</h2>
<p>MAUI Beach uses <a target="_blank" href="https://blog.taranissoftware.com/platform-specific-code-using-partial-classes-in-net-maui">platform specific partial classes</a> to implement <code>DeviceInfoService</code>. I copied the <code>Platforms/&lt;platform&gt;/Services/DeviceInfoService.cs</code> files into the new project. No changes were required to the Android and Windows versions of these classes. The MacCatalyst version only required me to delete the existing <code>using</code> statement.</p>
<p>The iOS version of <code>DeviceInfoService.cs</code> required an additional change as Visual Studio recommended I change the marshalling argument for the <code>sysctlbyname</code> system call from <code>LPStr</code> to <code>LPWStr</code>.</p>
<p>The About page in MAUI Beach has two buttons that open websites in the default browser which requires permission on Android. I copied the required <code>intent</code> into <code>Platforms/Android/AndroidManifest.xml</code> from the old project.</p>
<h2 id="heading-shell">Shell</h2>
<p>The UI for MAUI Beach is based around a flyout menu built using Shell. In the original version this was defined in <code>App.xaml</code> but the new template provides <code>AppShell.xaml</code>. Copying the XAML into the new file was straightforward with no surprises.</p>
<h3 id="heading-flyout-menu-issue">Flyout Menu Issue</h3>
<p>When I ran MAUI Beach on Windows I discovered a problem with the flyout menu items - the images were to the right of where they should be and the text labels were not visible at all! 😲 The menu items were defined as a Grid with two columns using proportional or <code>*</code> sizing:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Grid</span> <span class="hljs-attr">ColumnDefinitions</span>=<span class="hljs-string">"0.25*, 0.75*"</span>
      <span class="hljs-attr">Style</span>=<span class="hljs-string">"{StaticResource shellItem}"</span>
      <span class="hljs-attr">Padding</span>=<span class="hljs-string">"0, 10"</span>&gt;</span>
</code></pre>
<p> It seems that .NET MAUI is now using the full width of the window to calculate those sizes instead of the width of the flyout menu as it did before. Once I realised that it was simple to fix by setting a fixed width for the first column:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Grid</span> <span class="hljs-attr">ColumnDefinitions</span>=<span class="hljs-string">"85, *"</span>
      <span class="hljs-attr">Style</span>=<span class="hljs-string">"{StaticResource shellItem}"</span>
      <span class="hljs-attr">Padding</span>=<span class="hljs-string">"0, 10"</span>&gt;</span>
</code></pre>
<h2 id="heading-update-complete">Update Complete!</h2>
<p>With all the code moved over, and the minor image issue mentioned earlier fixed, I was able to build and run the new version of MAUI Beach on all platforms. And, once I fixed the flyout menu items it even looks better than the old preview version because bugs have been fixed. 😁</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1656867858703/6ghKBP_hq.webp" alt="MAUI Beach Screenshots" class="image--center mx-auto" /></p>
<p>In order to maintain version history I deleted my original project from the Git repository and copied the new project in its place. I ran into some issues with missing resources when I tried to build &amp; run the moved project, I reset the <code>Build Action</code> properties of all the resources and cleaned up the changes that caused in the project file. Another Clean &amp; Rebuild and my app was working again. 🎉</p>
<h2 id="heading-and-finally">And Finally...</h2>
<p>After committing and pushing my changes I created a <a target="_blank" href="https://github.com/irongut/MauiBeach/pull/18">Pull Request</a> and discovered my CI builds were broken on iOS and Windows. I'll write about building a .NET MAUI app with GitHub Actions again soon but if you want a sneak peak 🕵️‍♀️ you can find my working <a target="_blank" href="https://github.com/irongut/MauiBeach/blob/7adb82c5f693acb847295681b19254ed0d7b4a05/.github/workflows/ci-build.yml">CI Build</a> workflow on GitHub.</p>
<p> </p>
<p> </p>
<p>Cover image includes a vector created by brgfx from <a target="_blank" href="https://www.freepik.com/vectors/beach-cartoon">www.freepik.com</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Achievement Unlocked - Build mobile and desktop apps with .NET MAUI!]]></title><description><![CDATA[I've had a productive weekend, I completed the new .NET MAUI course from Microsoft Learn while watching the awesome MotoGP racing from Sachsenring! 🥳🏍🏍🏍
Starting with an explanation of the .NET MAUI architecture and creation of a simple app, the ...]]></description><link>https://blog.taranissoftware.com/achievement-unlocked-build-mobile-and-desktop-apps-with-net-maui</link><guid isPermaLink="true">https://blog.taranissoftware.com/achievement-unlocked-build-mobile-and-desktop-apps-with-net-maui</guid><category><![CDATA[dotnet]]></category><category><![CDATA[.NET]]></category><category><![CDATA[Learning Journey]]></category><category><![CDATA[#dotnet-maui]]></category><category><![CDATA[microsoft-learn]]></category><dc:creator><![CDATA[Dave Murray]]></dc:creator><pubDate>Sun, 19 Jun 2022 22:39:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1655677451435/BuD5OCcmi.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I've had a productive weekend, I completed the new <a target="_blank" href="https://docs.microsoft.com/learn/paths/build-apps-with-dotnet-maui/">.NET MAUI course</a> from Microsoft Learn while watching the awesome MotoGP racing from Sachsenring! 🥳🏍🏍🏍</p>
<p>Starting with an explanation of the .NET MAUI architecture and creation of a simple app, the course includes modules on designing your UI with XAML, using Shell to create tab or flyout based apps, consuming REST services and even storing data with SQLite. The content was easy to follow and the examples were good but I wouldn't recommend it for a complete novice with .NET and Visual Studio because I did run into a few issues and errors (all reported of course) that could confuse a beginner.</p>
<p>I'll write more about my experiences learning .NET MAUI soon but first I plan to watch video courses from Gerald Versluis and James Montemagno. 📺</p>
<p><a target="_blank" href="https://docs.microsoft.com/en-gb/learn/achievements/learn.dotnet-maui.build-apps-with-dotnet-maui.trophy?username=MurrayDave"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1655677218686/S9gXW7yWd.webp" alt="Microsoft Learn Trophy" class="image--center mx-auto" /></a></p>
<p> </p>
<p> </p>
<p>Cover image includes a vector created by upklyak from <a target="_blank" href="https://www.freepik.com/vectors/education-app">www.freepik.com</a>.</p>
]]></content:encoded></item><item><title><![CDATA[VS2022 17.3 Preview 1.1 - Missing Xamarin SDKs]]></title><description><![CDATA[.NET MAUI went GA last week 🎉 with the release of Visual Studio 2022 17.3 Preview 1.1 on Windows. Unfortunately updating to this version of Visual Studio breaks development of Xamarin apps for iOS because by default it uninstalls the Xamarin SDKs! �...]]></description><link>https://blog.taranissoftware.com/vs2022-173-preview-11-missing-xamarin-sdks</link><guid isPermaLink="true">https://blog.taranissoftware.com/vs2022-173-preview-11-missing-xamarin-sdks</guid><category><![CDATA[visual studio]]></category><category><![CDATA[Xamarin]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[.NET]]></category><dc:creator><![CDATA[Dave Murray]]></dc:creator><pubDate>Thu, 02 Jun 2022 19:27:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1654197869817/7bC3rPOAY.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>.NET MAUI went GA last week 🎉 with the release of Visual Studio 2022 17.3 Preview 1.1 on Windows. Unfortunately updating to this version of Visual Studio breaks development of Xamarin apps for iOS because by default it uninstalls the Xamarin SDKs! 🙈 Fortunately I have a quick fix so you can continue to develop Xamarin.iOS and Xamarin.Forms apps alongside your .NET MAUI apps with Visual Studio 2022 17.3 Preview 1.1. 😁</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654192738678/xrIlD5sNz.webp" alt="Visual Studio Installer" class="image--center mx-auto" /></p>
<ol>
<li>Open the Visual Studio Installer and click the <strong>Modify</strong> button on your Visual Studio 2022 Preview installation.</li>
<li>On the right-hand side open the <strong>.NET Multi-platform App UI</strong> node.</li>
<li>Open the <strong>Optional</strong> node under .NET MAUI.</li>
<li>Check the <strong>Xamarin SDKs</strong> node to install the Xamarin SDKs.</li>
<li>Click <strong>Modify</strong>.</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654192747832/81fpL79Uu.webp" alt="Install Xamarin SDKs" class="image--center mx-auto" /></p>
<p>The Visual Studio installer will now download and re-install your missing Xamarin SDKs. 🥳</p>
<p> </p>
<p> </p>
<p>Cover image includes a background vector created by sentavio from <a target="_blank" href="https://www.freepik.com/vectors/isometric-laptop">www.freepik.com</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Microsoft Build 2022: Xamarin & MAUI Recap]]></title><description><![CDATA[Build is Microsoft's premier developer conference, taking place annually in May. As with the past two editions, Build 2022 was an entirely online conference with live sessions over three days and more available on-demand. Most of the live sessions ar...]]></description><link>https://blog.taranissoftware.com/microsoft-build-2022-xamarin-and-maui-recap</link><guid isPermaLink="true">https://blog.taranissoftware.com/microsoft-build-2022-xamarin-and-maui-recap</guid><category><![CDATA[.NET]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[Blazor ]]></category><category><![CDATA[Xamarin]]></category><category><![CDATA[#dotnet-maui]]></category><dc:creator><![CDATA[Dave Murray]]></dc:creator><pubDate>Mon, 30 May 2022 22:06:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1653784855045/UlyrVUcp3.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Build is Microsoft's premier developer conference, taking place annually in May. As with the past two editions, <a target="_blank" href="https://mybuild.microsoft.com/">Build 2022</a> was an entirely online conference with live sessions over three days and more available on-demand. Most of the live sessions are now available to watch again so I will highlight and discuss the sessions of interest for .NET MAUI, Xamarin, Blazor Hybrid and React Native developers building cross platform native apps.</p>
<h2 id="heading-live-sessions">Live Sessions</h2>
<p>There were two live sessions about .NET MAUI and Blazor Hybrid apps which now have recordings available.</p>
<h3 id="heading-build-native-apps-for-any-device-with-net-and-visual-studio">Build native apps for any device with .NET and Visual Studio</h3>
<p><strong>Tim Heuer | Maddy Montaquila | Leslie Richardson | Daniel Roth</strong></p>
<p><a target="_blank" href="https://mybuild.microsoft.com/en-US/sessions/599c82b6-0c5a-4add-9961-48b85d9ffde0"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653947387010/9VVrksMY0.webp" alt="Build native apps for any device with .NET and Visual Studio" class="image--center mx-auto" /></a></p>
<p>In the initial segment, Tim Heuer discusses the fast adoption of .NET 6 by the community and features that aid with cross platform development like unified base libraries &amp; SDK, Hot Reload support everywhere and Apple Silicon support. He then announces the release of <a target="_blank" href="https://dot.net/maui">.NET MAUI</a>, providing a summary of its features, and introduces the <a target="_blank" href="https://github.com/microsoft/dotnet-podcasts">.NET Podcasts</a> reference application.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653791850184/zSdBclK8w.webp" alt=".NET MAUI in .NET 6" class="image--center mx-auto" /></p>
<p>Starting at 07:30, Maddy Montaquila shows us how to get started with .NET MAUI development. Maddy demonstrates the features of MAUI including the single project solution, creating shared icons and splash images for every platform, XAML Live Preview, the Live Visual Tree and Hot Reload for XAML and C# on Android, iOS and Windows.</p>
<p>Starting at 20:30, Tim introduces Hybrid apps with <a target="_blank" href="https://blazor.net/">Blazor</a> &amp; .NET MAUI which allow you to combine web and native UI technology in a native client application. Built on top of .NET MAUI, Blazor Hybrid apps have access to the device to call native APIs, access the file system or use native controls if you want them.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653791874078/oXe-41Zwk.webp" alt="Choose your stack" class="image--center mx-auto" /></p>
<p>At 21:38 Dan Roth demonstrates the features of a modern web app built with Blazor using the .NET Podcasts application. Dan then shows us how to get started building a hybrid app with Blazor &amp; .NET MAUI using Blazor components in a native MAUI app. Maddy joins Dan to demonstrate the Listen Together feature in the .NET MAUI version of the podcasts app which is built using a Blazor web view and Blazor components.</p>
<p>Starting at 30:30, Tim introduces <a target="_blank" href="https://visualstudio.com/download/">Visual Studio 2022</a> and discusses some of the new features that help with cross platform development including WSL integration, IntelliCode for whole line completions and suggestions for common code refactorings as you type and improved integration with GitHub and Azure. He also announces Visual Studio 2022 17.3 Preview 2 which will natively support building and debugging ARM64 apps on ARM-based processors with Windows 11 and upcomming support for ARM in .NET Framework v4.8.1.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653791898071/h45Ciyjyz.webp" alt="Visual Studio 2022" class="image--center mx-auto" /></p>
<p>At 33:14 Leslie Richardson shows us some of the new tools in Visual Studio 2022 including IntelliCode, Hot Reload, temporary breakpoints and the new built-in spell checker. Leslie also demonstrates Port Tunneling, an upcomming feature that makes it easier to debug a backend API from a mobile device. If your organisation uses Azure Active Directory you can signup for the <a target="_blank" href="https://aka.ms/tunnels-signup">Microsoft Tunneling Private Preview</a>.</p>
<p>Starting at 37:50, Tim discusses how Azure can help you build a backend for your mobile apps using .NET 6. Along the way he touches on Azure Static Web Apps, Azure Functions, Azure Container Apps and using GitHub Actions to create a code to cloud pipeline with Visual Studio 2022. He also demonstrates two new features of GitHub - GitHub Advanced Security with push protection to help prevent unwanted secrets being committed into your repo and Actions Job Summaries which append job information to your workflows such as test results and visualisation.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653791924129/uvRqURjCp.webp" alt="Visual Studio - GitHub - Azure: Code to cloud" class="image--center mx-auto" /></p>
<p>Finally at 49:15 Tim announces the latest <a target="_blank" href="https://dotnet.microsoft.com/download/dotnet/7.0">.NET 7 Preview 4</a> release and <a target="_blank" href="https://www.dotnetconf.net/">.NET Conf 2022</a> on 8th - 10th November.</p>
<h3 id="heading-ask-the-experts-net-and-visual-studio">Ask the Experts: .NET and Visual Studio</h3>
<p><strong>Tim Heuer | Mads Kristensen | Maddy Montaquila | David Ortinau | Daniel Roth</strong></p>
<p><a target="_blank" href="https://mybuild.microsoft.com/en-US/sessions/81056450-6f77-4296-9ae7-1eda6b3cd9bd"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653947645623/nk_5JV9aj.webp" alt="Ask the Experts: .NET and Visual Studio" class="image--center mx-auto" /></a></p>
<p>This was a Q&amp;A session on Teams following the session above which summarised the earlier announcements and discussed .NET MAUI, Blazor, hybrid apps and Visual Studio 2022.</p>
<p>There are too many great questions and answers for me to summarise so I suggest you watch it. In particular Dave &amp; Maddy answer questions about moving from Xamarin Forms to MAUI, updating Nuget libraries, testing tool support and what's next for MAUI.</p>
<h2 id="heading-on-demand-sessions">On-Demand Sessions</h2>
<p>Several shorter on-demand sessions are of interest to developers of .NET MAUI, Xamarin, Blazor Hybrid and React Native apps. There are also sessions for developers looking to publish Android apps on Windows 11 via the Amazon AppStore and Windows Subsystem for Android or Windows apps on the Microsoft Store.</p>
<h3 id="heading-net-maui-updates-and-roadmap">.NET MAUI - Updates and Roadmap</h3>
<p><strong>Maddy Montaquila</strong></p>
<p><a target="_blank" href="https://mybuild.microsoft.com/en-US/sessions/802e54dd-5b4a-460f-8e74-240e4a78f9c9"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653947670311/6Axt7w3wy.webp" alt=".NET MAUI - Updates and Roadmap" class="image--center mx-auto" /></a></p>
<p><a target="_blank" href="https://dot.net/maui">.NET Multi-platform App UI</a> has shipped! Maddy Montaquila explains what .NET MAUI is, how to get it, new capabilities, changes from Xamarin and what to look forward to in coming releases. Maddy also demonstrates improvements in Android startup performance versus Xamarin Forms and Xamarin Android.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653791987980/suNRLmJbZ.webp" alt=".NET MAUI Roadmap" class="image--center mx-auto" /></p>
<p>Although .NET MAUI is GA the tooling is still in preview so you will require the latest <a target="_blank" href="https://visualstudio.com/preview">preview version of Visual Studio 2022</a> on Windows (17.3) or Mac (17.1). On Windows the tooling should GA in August and on Mac in November.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653792020408/Pd2H_s2Hd.webp" alt=".NET MAUI Roadmap Milestones" class="image--center mx-auto" /></p>
<p>This is a really good session to watch, with Maddy's usual enthusiasm and lots of information for cross platform .NET developers.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653792060594/u23t4wpfJ.webp" alt="Xamarin Roadmap" class="image--center mx-auto" /></p>
<h3 id="heading-native-client-apps-with-blazor-hybrid">Native client apps with Blazor Hybrid</h3>
<p><strong>Daniel Roth</strong></p>
<p><a target="_blank" href="https://mybuild.microsoft.com/en-US/sessions/d2a86fe6-abb9-4939-a44d-ed660f9bb675"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653947695044/ypLmOnHIt.webp" alt="Native client apps with Blazor Hybrid" class="image--center mx-auto" /></a></p>
<p><a target="_blank" href="https://blazor.net/">Blazor</a> is expanding beyond the web to enable support for building native client apps using a hybrid of web technologies and .NET. In this session Dan Roth explains the Blazor hosting model, discusses the latest Blazor improvements in .NET 6 and how to use your Blazor skills to build native apps with .NET MAUI. Dan shows us how to get started building a hybrid app with Blazor &amp; .NET MAUI, using native device APIs and Blazor Hybrid UI support for modernising WPF and WinForms apps. Finally he discusses the new features coming to Blazor in .NET 7.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653792103382/QGCMgpnC7.webp" alt="Blazor in .NET 7" class="image--center mx-auto" /></p>
<p>This session is largely an expansion of Dan's segment in the main live session but if you're interested in Blazor &amp; MAUI hybrid apps it is worth watching.</p>
<h3 id="heading-make-your-cross-platform-apps-best-on-windows">Make your cross-platform apps best on Windows</h3>
<p><strong>Maddy Montaquila | Steven Moyes</strong></p>
<p><a target="_blank" href="https://mybuild.microsoft.com/en-US/sessions/a5dcb608-5fcd-4957-9c92-faaa3b058589"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653947714618/DXdJiqepO.webp" alt="Make your cross-platform apps best on Windows" class="image--center mx-auto" /></a></p>
<p>Extend your cross platform apps with native capabilities to build more engaging experiences. In this session, Steven Moyes and Maddy Montaquila discuss options for cross platform app development using <a target="_blank" href="https://reactnative.dev/">React Native</a> and <a target="_blank" href="https://dot.net/maui">.NET MAUI</a> and why you might choose one platform over the other. Many of Microsoft's own apps are built using React Native including parts of Office, the XBox app on PC and Power Apps.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653792143974/uhrrFNu4Y.webp" alt="React Native &amp; .NET MAUI Comparison" class="image--center mx-auto" /></p>
<p>This session is more of an overview rather than technical but would be a good introduction if you don't know anything about React Native or .NET MAUI.</p>
<h3 id="heading-bring-your-android-apps-to-windows">Bring your Android apps to Windows</h3>
<p><strong>Andrew Leader | Hamza Usmani | Mario Vivani (Amazon)</strong></p>
<p><a target="_blank" href="https://mybuild.microsoft.com/en-US/sessions/241d43b6-130d-4e83-a263-22c94872ff24"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653947739207/-_O7MFr2u.webp" alt="Bring your Android apps to Windows" class="image--center mx-auto" /></a></p>
<p>Hamza Usmani and Andrew Leader discuss publishing your existing Android apps on Windows 11 using <a target="_blank" href="https://docs.microsoft.com/windows/android/wsa/">Windows Subsystem for Android</a>. They show how to install the Amazon AppStore, which installs WSA in the background, how to install an app from the AppStore and how to debug your app using WSA from Android Studio. Mario Vivani from Amazon explains how to <a target="_blank" href="https://m.amazonappservices.com/developer-interest">sign up for the Amazon AppStore</a> and get progress updates. He also shows how to identify that your app was installed from the Amazon AppStore when you need to use Amazon specific APIs instead of Google ones. They also discuss best practices and optimisations.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653792170170/pPDEp0wfr.webp" alt="What is Windows Subsystem for Android?" class="image--center mx-auto" /></p>
<p>This session is really targetted at native Android developers but if you are thinking about publishing the Android version of a Xamarin Forms or Xamarin Android application on Windows 11 using WSA it is still useful.</p>
<p>Note: Windows Subsystem for Android is currently in preview and only available in the U.S. market. Developer participation in the Amazon AppStore on Windows 11 is in closed beta and only available to a selection of apps.</p>
<h3 id="heading-finding-your-success-in-the-microsoft-store">Finding your success in the Microsoft Store</h3>
<p><strong>Pete Brown</strong></p>
<p><a target="_blank" href="https://mybuild.microsoft.com/en-US/sessions/19f00ab9-9d02-43dc-923b-1fe19006b58a"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653947759979/whtrPFgny.webp" alt="Finding your success in the Microsoft Store" class="image--center mx-auto" /></a></p>
<p>Pete Brown discusses tips and techniques for getting your app through the certification process in the Microsoft Store on Windows. Including common certification blockers, a few specialized situations, tips for navigating the certification process itself and what to do if you need support.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1653945877695/9yEGTCjrQ.webp" alt="Common Certification Blockers" class="image--center mx-auto" /></p>
<p>This session isn't specifically targetted at Xamarin or .NET MAUI developers but if you are planning to publish your cross platform app on Windows it is worth watching.</p>
]]></content:encoded></item><item><title><![CDATA[Goodbye Emma]]></title><description><![CDATA[As you may have noticed by the lack of content I have been on hiatus for the last couple of months. My beloved partner of 22 years, Emma passed away unexpectedly at the beginning of March and I have been unable to find motivation to write since.
I ho...]]></description><link>https://blog.taranissoftware.com/goodbye-emma</link><guid isPermaLink="true">https://blog.taranissoftware.com/goodbye-emma</guid><category><![CDATA[personal]]></category><dc:creator><![CDATA[Dave Murray]]></dc:creator><pubDate>Mon, 16 May 2022 18:17:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/unsplash/wX1GSlEHzuc/upload/v1653415598339/gYK4Zjs496.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As you may have noticed by the lack of content I have been on hiatus for the last couple of months. My beloved partner of 22 years, Emma passed away unexpectedly at the beginning of March and I have been unable to find motivation to write since.</p>
<p>I hope that the announcements at Microsoft Build later this month will spark my creativity and enthusiasm again. Till then here is one of my favourite poems written by Emma, who wrote and performed poetry at many venues in our home city.</p>
<hr />

<h3 id="heading-prelude-to-a-dream">Prelude To A Dream</h3>
<h3 id="heading-insomnia-midnight">(Insomnia: Midnight)</h3>
<h4 id="heading-by-emma-pandolfi-1980-2022">By Emma Pandolfi (1980 - 2022)</h4>
<p><br /></p>
<p>Oh, deep exquisite embrace of sleep,<br />
I invite you to take me.<br />
Take me away,<br />
slow but sure<br />
to the land of Nod.<br />
In the land of dreams.<br />
<br /></p>
<p>Morpheus, dear Morpheus<br />
Gothic prince of the night.<br />
As delight fades to delirium<br />
and dreams are my destiny.<br />
So take me now and<br />
keep me safe<br />
in your land of dreams<br />
tonight.<br />
<br /></p>
<p>Oh, deep exquisite embrace of sleep,<br />
I implore you to take me.<br />
Carry me gently<br />
but carry me swift<br />
through the land of Nod.<br />
In the land of dreams.<br />
<br /></p>
<p>Morpheus, dear Morpheus<br />
endless giver of dreams.<br />
Give unto me,<br />
I beg of you.<br />
The sleep of the just<br />
tonight.<br />
<br /></p>
<p>Oh, deep exquisite embrace of sleep<br />
I urge you to take me.<br />
Whisk me away<br />
to sub-conscious adventures<br />
in the land of Nod.<br />
In the land of dreams.<br />
<br /></p>
]]></content:encoded></item><item><title><![CDATA[.NET Frontend Day 2022: Xamarin & MAUI Recap]]></title><description><![CDATA[.NET Frontend Day 2022 took place on 10th February with a great line-up of speakers from the community & Microsoft and topics for all .NET developers. I will highlight the sessions that were of most interest to Xamarin and .NET MAUI developers.
What ...]]></description><link>https://blog.taranissoftware.com/net-frontend-day-2022-xamarin-and-maui-recap</link><guid isPermaLink="true">https://blog.taranissoftware.com/net-frontend-day-2022-xamarin-and-maui-recap</guid><category><![CDATA[dotnet]]></category><category><![CDATA[.NET]]></category><category><![CDATA[Xamarin]]></category><category><![CDATA[Blazor ]]></category><category><![CDATA[#dotnet-maui]]></category><dc:creator><![CDATA[Dave Murray]]></dc:creator><pubDate>Sat, 19 Feb 2022 22:02:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1645307424825/n1UsTALo9.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://dotnet-frontend.com/">.NET Frontend Day 2022</a> took place on 10th February with a great line-up of speakers from the community &amp; Microsoft and topics for all .NET developers. I will highlight the sessions that were of most interest to Xamarin and .NET MAUI developers.</p>
<h2 id="heading-what-is-net-frontend-day">What is .NET Frontend Day?</h2>
<p>.NET Frontend Day is a virtual conference that started in 2021. It is organised and hosted by the creators of two of my favourtie podcasts - Jessica &amp; Jimmy Engström from <a target="_blank" href="http://www.codingafterwork.se/">Coding After Work</a> and Daniel Hindrikes from <a target="_blank" href="https://podtail.com/en/podcast/app-in-the-cloud/">App in the Cloud</a>. The first edition had eight speakers and some great sessions for .NET desktop and web developers. All the <a target="_blank" href="https://www.youtube.com/playlist?list=PLRPCjWNXEQguH9vngwbcjJGPWp6gm4X06">sessions from 2021</a> are available on YouTube.</p>
<p>This year's conference followed a similar format but had a strong focus on .NET MAUI and Blazor. Speakers included the incredibly prolific Gerald Versluis (if you haven't subscribed to his <a target="_blank" href="https://www.youtube.com/c/GeraldVersluis">YouTube channel</a> you should!), Maddy Leger Montaquila, James Montemagno and Daniel Roth from Microsoft, Sam Basu from Progress Software (Telerik) and MVPs Stacy Cashmore, Ed Charbeneau and Layla Porter.</p>
<p>Thanks to the hosts and all the speakers for a great afternoon of content!</p>
<h2 id="heading-net-frontend-day-2022-sessions">.NET Frontend Day 2022 Sessions</h2>
<ul>
<li>Introduction to App Development with .NET MAUI - Gerald Versluis</li>
<li>Authenticating in Azure Static Web Apps - Stacy Cashmore</li>
<li>Writing JavaScript for C#'s Blazor - Ed Charbeneau</li>
<li>Top Tips For Blazor - Layla Porter</li>
<li>Visual Studio 2022 and .NET MAUI - Maddy Leger Montaquila</li>
<li>Migration &amp; Modernization with .NET MAUI - Sam Basu</li>
<li>Go Hybrid across Web, Desktop, &amp; Mobile with Blazor Hybrid - James Montemagno</li>
<li>Blazor for the Web and Beyond - Daniel Roth</li>
</ul>
<p>A playlist for <a target="_blank" href="https://www.youtube.com/playlist?list=PLRPCjWNXEQguKsiM-TXedb8fRL7LjnBM5">all 2022 sessions</a> is available on YouTube.</p>
<h3 id="heading-introduction-to-app-development-with-net-maui">Introduction to App Development with .NET MAUI</h3>
<p><strong>Gerald Versluis</strong></p>
<p>.NET MAUI enables developers to create cross platform apps for Android, iOS, MacOS, and Windows. In this session, Gerald introduces .NET MAUI, explains how it fits into the .NET ecosystem, how it differs from Xamarin and how to get started building great cross platform apps.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=xGhoQf4xha4">https://www.youtube.com/watch?v=xGhoQf4xha4</a></div>
<h3 id="heading-visual-studio-2022-and-net-maui">Visual Studio 2022 and .NET MAUI</h3>
<p><strong>Maddy Leger Montaquila</strong></p>
<p>Visual Studio 2022 shipped at the end of last year with a focus on speed and developer productivity, Visual Studio 2022 for Mac is being rebuilt from the ground up and .NET MAUI improves the developer experience for cross platform development. Maddy demonstrates how to be more productive building your .NET MAUI apps and provides the latest updates on the VS 2022, VS 2022 for Mac and .NET MAUI developer experiences.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=5n_Bf-2_fbA">https://www.youtube.com/watch?v=5n_Bf-2_fbA</a></div>
<h3 id="heading-migration-andamp-modernization-with-net-maui">Migration &amp; Modernization with .NET MAUI</h3>
<p><strong>Sam Basu</strong></p>
<p>.NET MAUI is the evolution of Xamarin.Forms running on .NET 6 but what if you have existing mobile apps built with Xamarin? How much work will it be to migrate to .NET MAUI? Would the Upgrade Assistants help? What about your custom renderers and what are MAUI handlers? Could Blazor help you share code with your web apps? Sam covers this and more with a real-world look at migration strategies and app modernisation with .NET MAUI.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=oF42MYA0CeA">https://www.youtube.com/watch?v=oF42MYA0CeA</a></div>
<h3 id="heading-go-hybrid-across-web-desktop-andamp-mobile-with-blazor-hybrid">Go Hybrid across Web, Desktop, &amp; Mobile with Blazor Hybrid</h3>
<p><strong>James Montemagno</strong></p>
<p>.NET MAUI Blazor apps enable you to re-use all of your Blazor knowledge, razor components, business logic and Blazor libraries to create native apps for desktop and mobile devices. Possibly the most friendly guy in the world 😄, James shows us how to wrap your existing Blazor web app or go full hybrid, including native UI and APIs, to create the best apps for any platform.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=m12HDgDwAOo">https://www.youtube.com/watch?v=m12HDgDwAOo</a></div>
<h3 id="heading-blazor-for-the-web-and-beyond">Blazor for the Web and Beyond</h3>
<p><strong>Daniel Roth</strong></p>
<p>Blazor enables .NET developers to build applications using web UI for any platform or device. In this session, Dan looks at the many improvements to Blazor in .NET 6, the integration of Blazor into .NET MAUI and the future of Blazor. This is a must-watch session for the extensive Q&amp;A at the end.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=AHH9QTZvkEg">https://www.youtube.com/watch?v=AHH9QTZvkEg</a></div>
]]></content:encoded></item><item><title><![CDATA[Platform Specific Code using Partial Classes in .NET MAUI]]></title><description><![CDATA[When writing a cross platform app it is common to need some platform specific code. In Xamarin.Forms we use DependencyService, in .NET MAUI we can use a similar dependency injection technique or take advantage of MAUI's multi-targeting and partial cl...]]></description><link>https://blog.taranissoftware.com/platform-specific-code-using-partial-classes-in-net-maui</link><guid isPermaLink="true">https://blog.taranissoftware.com/platform-specific-code-using-partial-classes-in-net-maui</guid><category><![CDATA[.NET]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[Xamarin]]></category><category><![CDATA[#dotnet-maui]]></category><dc:creator><![CDATA[Dave Murray]]></dc:creator><pubDate>Thu, 27 Jan 2022 21:26:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1643250208818/lcnRZpstV.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When writing a cross platform app it is common to need some platform specific code. In Xamarin.Forms we use <a target="_blank" href="https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/dependency-service/introduction">DependencyService</a>, in .NET MAUI we can use a similar dependency injection technique or take advantage of MAUI's multi-targeting and partial classes to write platform specific code. In this article I demonstrate how to use partial classes in .NET MAUI to retrieve device information.</p>
<p>As always, the code for my .NET MAUI articles is available on GitHub: <a target="_blank" href="https://github.com/irongut/MauiBeach">irongut/MauiBeach</a></p>
<h2 id="heading-the-problem">The Problem</h2>
<p>Xamarin Essentials and MAUI Essentials include a <a target="_blank" href="https://docs.microsoft.com/en-us/xamarin/essentials/device-information?tabs=ios">DeviceInfo</a> class which provides information about the device the application is running on but on Android it doesn't include the SDK version and on iOS it reports the model using Apple hardware identifiers, which are not very user friendly. In my Xamarin.Forms applications I use <code>DependencyService</code> and platform specific code to improve on the information provided by Xamarin Essentials, here I'll use partial classes to achieve the same results in .NET MAUI.</p>
<h2 id="heading-the-solution">The Solution</h2>
<p>In C# we normally write a class in a single file but it is possible to split a class over multiple files that are combined when the application is compiled by using <a target="_blank" href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/partial-classes-and-methods">partial classes</a>. A partial class is created by using the <code>partial</code> keyword and every part of the partial class must be in the same assembly and namespace. Partial classes are part of the code that enables XAML to work so you will have used them before even if you didn't realise it.</p>
<h3 id="heading-cross-platform-partial-class">Cross Platform Partial Class</h3>
<p>First we need to create a cross platform partial class that defines partial methods which will be implemented by our platform specific partial classes, think of this class as like an interface.</p>
<pre><code class="lang-c#"><span class="hljs-keyword">namespace</span> <span class="hljs-title">MauiBeach.Services</span>;

<span class="hljs-keyword">internal</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">DeviceInfoService</span>
{
    <span class="hljs-function"><span class="hljs-keyword">internal</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">string</span> <span class="hljs-title">Model</span>(<span class="hljs-params"></span>)</span>;

    <span class="hljs-function"><span class="hljs-keyword">internal</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">string</span> <span class="hljs-title">Platform</span>(<span class="hljs-params"></span>)</span>;
}
</code></pre>
<p>Our platform specific code will return two strings:</p>
<ul>
<li><code>Model</code> such as <em>iPhone 13 Pro</em></li>
<li><code>Platform</code> which will include the platform name and version, for example <em>Android 9 (API 28 - Pie)</em></li>
</ul>
<p>Remember, all the parts of our partial class must be in the same namespace - in this case <code>MauiBeach.Services</code>.</p>
<h3 id="heading-android-partial-class">Android Partial Class</h3>
<p>The Android implementation of our platform specific code goes in the <code>Platforms\Android</code> folder hierarchy. </p>
<pre><code class="lang-c#"><span class="hljs-keyword">using</span> Android.OS;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">MauiBeach.Services</span>;

<span class="hljs-keyword">internal</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">DeviceInfoService</span>
{
    <span class="hljs-function"><span class="hljs-keyword">internal</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">string</span> <span class="hljs-title">Model</span>(<span class="hljs-params"></span>)</span> =&gt; Build.Model;

    <span class="hljs-function"><span class="hljs-keyword">internal</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">string</span> <span class="hljs-title">Platform</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">return</span> <span class="hljs-string">$"Android <span class="hljs-subst">{Build.VERSION.Release}</span> (API <span class="hljs-subst">{AndroidSDK}</span> - <span class="hljs-subst">{AndroidCodename()}</span>)"</span>;
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">string</span> <span class="hljs-title">AndroidCodename</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">return</span> (<span class="hljs-keyword">int</span>)Build.VERSION.SdkInt <span class="hljs-keyword">switch</span>
        {
            (<span class="hljs-keyword">int</span>)BuildVersionCodes.<span class="hljs-function">Lollipop <span class="hljs-title">or</span> (<span class="hljs-params"><span class="hljs-keyword">int</span></span>)BuildVersionCodes.LollipopMr1</span> =&gt; <span class="hljs-string">"Lollipop"</span>,
            (<span class="hljs-keyword">int</span>)BuildVersionCodes.M =&gt; <span class="hljs-string">"Marshmallow"</span>,
            (<span class="hljs-keyword">int</span>)BuildVersionCodes.<span class="hljs-function">N <span class="hljs-title">or</span> (<span class="hljs-params"><span class="hljs-keyword">int</span></span>)BuildVersionCodes.NMr1</span> =&gt; <span class="hljs-string">"Nougat"</span>,
            (<span class="hljs-keyword">int</span>)BuildVersionCodes.<span class="hljs-function">O <span class="hljs-title">or</span> (<span class="hljs-params"><span class="hljs-keyword">int</span></span>)BuildVersionCodes.OMr1</span> =&gt; <span class="hljs-string">"Oreo"</span>,
            (<span class="hljs-keyword">int</span>)BuildVersionCodes.P =&gt; <span class="hljs-string">"Pie"</span>,
            (<span class="hljs-keyword">int</span>)BuildVersionCodes.Q =&gt; <span class="hljs-string">"Q"</span>,
            (<span class="hljs-keyword">int</span>)BuildVersionCodes.R =&gt; <span class="hljs-string">"R"</span>,
            (<span class="hljs-keyword">int</span>)BuildVersionCodes.S =&gt; <span class="hljs-string">"S"</span>,
            <span class="hljs-number">32</span> =&gt; <span class="hljs-string">"Sv2"</span>,
            _ =&gt; <span class="hljs-string">"Unknown"</span>,
        };
    }

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">int</span> AndroidSDK =&gt; (<span class="hljs-keyword">int</span>)Build.VERSION.SdkInt;
}
</code></pre>
<p>On Android the information we want is available from the <a target="_blank" href="https://developer.android.com/reference/android/os/Build">Build</a> class. I've only included SDK codenames for versions of Android supported by .NET MAUI.</p>
<h3 id="heading-ios-partial-class">iOS Partial Class</h3>
<p>The iOS implementation of our platform specific code goes in the <code>Platforms\iOS</code> folder hierarchy. </p>
<pre><code class="lang-c#"><span class="hljs-keyword">using</span> Foundation;
<span class="hljs-keyword">using</span> Microsoft.Maui.Essentials;
<span class="hljs-keyword">using</span> ObjCRuntime;
<span class="hljs-keyword">using</span> System;
<span class="hljs-keyword">using</span> System.Runtime.InteropServices;
<span class="hljs-keyword">using</span> UIKit;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">MauiBeach.Services</span>;

<span class="hljs-keyword">internal</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">DeviceInfoService</span>
{
    <span class="hljs-comment">// based on code from https://github.com/dannycabrera/Get-iOS-Model</span>

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> HardwareProperty = <span class="hljs-string">"hw.machine"</span>;

    [<span class="hljs-meta">DllImport(Constants.SystemLibrary)</span>]
    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">extern</span> <span class="hljs-keyword">int</span> <span class="hljs-title">sysctlbyname</span>(<span class="hljs-params">[MarshalAs(UnmanagedType.LPStr</span>)] <span class="hljs-keyword">string</span> property,
                                            IntPtr output,
                                            IntPtr oldLen,
                                            IntPtr newp,
                                            <span class="hljs-keyword">uint</span> newlen)</span>;

    <span class="hljs-function"><span class="hljs-keyword">internal</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">string</span> <span class="hljs-title">Model</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">string</span> version = FindVersion();
        <span class="hljs-keyword">if</span> (version == <span class="hljs-string">"i386"</span> || version == <span class="hljs-string">"x86_64"</span>)
        {
            <span class="hljs-keyword">return</span> GetModel(NSProcessInfo.ProcessInfo.Environment[<span class="hljs-string">"SIMULATOR_MODEL_IDENTIFIER"</span>].ToString()) + <span class="hljs-string">" Simulator"</span>;
        }
        <span class="hljs-keyword">return</span> GetModel(version);
    }

    <span class="hljs-function"><span class="hljs-keyword">internal</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">string</span> <span class="hljs-title">Platform</span>(<span class="hljs-params"></span>)</span> =&gt; <span class="hljs-string">$"<span class="hljs-subst">{DeviceInfo.Platform}</span> <span class="hljs-subst">{UIDevice.CurrentDevice.SystemVersion}</span>"</span>;

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">string</span> <span class="hljs-title">FindVersion</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">try</span>
        {
            <span class="hljs-comment">// get the length of the string that will be returned</span>
            <span class="hljs-keyword">var</span> pLen = Marshal.AllocHGlobal(<span class="hljs-keyword">sizeof</span>(<span class="hljs-keyword">int</span>));
            _ = sysctlbyname(HardwareProperty, IntPtr.Zero, pLen, IntPtr.Zero, <span class="hljs-number">0</span>);

            <span class="hljs-keyword">var</span> length = Marshal.ReadInt32(pLen);

            <span class="hljs-comment">// check to see if we got a length</span>
            <span class="hljs-keyword">if</span> (length == <span class="hljs-number">0</span>)
            {
                Marshal.FreeHGlobal(pLen);
                <span class="hljs-keyword">return</span> <span class="hljs-string">"Unknown"</span>;
            }

            <span class="hljs-comment">// get the hardware string</span>
            <span class="hljs-keyword">var</span> pStr = Marshal.AllocHGlobal(length);
            _ = sysctlbyname(HardwareProperty, pStr, pLen, IntPtr.Zero, <span class="hljs-number">0</span>);

            <span class="hljs-comment">// convert the native string into a C# string</span>
            <span class="hljs-keyword">var</span> hardwareStr = Marshal.PtrToStringAnsi(pStr);

            <span class="hljs-comment">// cleanup</span>
            Marshal.FreeHGlobal(pLen);
            Marshal.FreeHGlobal(pStr);

            <span class="hljs-keyword">return</span> hardwareStr;
        }
        <span class="hljs-keyword">catch</span> (Exception ex)
        {
            Console.WriteLine(<span class="hljs-string">"DeviceHardware.Version Ex: "</span> + ex.Message);
        }

        <span class="hljs-keyword">return</span> <span class="hljs-string">"Unknown"</span>;
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">string</span> <span class="hljs-title">GetModel</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> version</span>)</span>
    {
        <span class="hljs-keyword">if</span> (version.StartsWith(<span class="hljs-string">"iPhone"</span>))
        {
            <span class="hljs-keyword">switch</span> (version)
            {
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone14,2"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPhone 13 Pro"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone14,3"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPhone 13 Pro Max"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone14,4"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPhone 13 mini"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone14,5"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPhone 13"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone13,1"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPhone 12 mini"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone13,2"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPhone 12"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone13,3"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPhone 12 Pro"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone13,4"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPhone 12 Pro Max"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone12,8"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPhone SE (2nd generation)"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone12,5"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPhone 11 Pro Max"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone12,3"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPhone 11 Pro"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone12,1"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPhone 11"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone11,2"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPhone XS"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone11,4"</span>:
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone11,6"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPhone XS Max"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone11,8"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPhone XR"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone10,3"</span>:
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone10,6"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPhone X"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone10,2"</span>:
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone10,5"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPhone 8 Plus"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone10,1"</span>:
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone10,4"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPhone 8"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone9,2"</span>:
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone9,4"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPhone 7 Plus"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone9,1"</span>:
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone9,3"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPhone 7"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone8,4"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPhone SE"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone8,2"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPhone 6S Plus"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone8,1"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPhone 6S"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone7,1"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPhone 6 Plus"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone7,2"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPhone 6"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone6,2"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPhone 5S Global"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone6,1"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPhone 5S GSM"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone5,4"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPhone 5C Global"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone5,3"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPhone 5C GSM"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone5,2"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPhone 5 Global"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPhone5,1"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPhone 5 GSM"</span>;
            }
        }

        <span class="hljs-keyword">if</span> (version.StartsWith(<span class="hljs-string">"iPod"</span>))
        {
            <span class="hljs-keyword">switch</span> (version)
            {
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPod9,1"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPod touch 7G"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPod7,1"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPod touch 6G"</span>;
            }
        }

        <span class="hljs-keyword">if</span> (version.StartsWith(<span class="hljs-string">"iPad"</span>))
        {
            <span class="hljs-keyword">switch</span> (version)
            {
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad14,2"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad mini (6th generation) Wi-FI + Cellular"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad14,1"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad mini (6th generation) Wi-FI"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad13,11"</span>:
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad13,10"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad Pro (12.9-inch) (5th generation) Wi-Fi + Cellular"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad13,9"</span>:
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad13,8"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad Pro (12.9-inch) (5th generation) Wi-Fi"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad13,7"</span>:
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad13,6"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad Pro (11-inch) (3rd generation) Wi-Fi + Cellular"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad13,5"</span>:
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad13,4"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad Pro (11-inch) (3rd generation) Wi-Fi"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad13,2"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad Air (4th generation) Wi-Fi + Cellular"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad13,1"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad Air (4th generation) Wi-Fi"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad12,2"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad (9th Generation) Wi-Fi + Cellular"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad12,1"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad (9th generation) Wi-Fi"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad11,7"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad (8th Generation) Wi-Fi + Cellular"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad11,6"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad (8th Generation) Wi-Fi"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad11,4"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad Air (3rd generation) Wi-Fi + Cellular"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad11,3"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad Air (3rd generation) Wi-Fi"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad11,2"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad mini (5th generation) Wi-Fi + Cellular"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad11,1"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad mini (5th generation) Wi-Fi"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad8,12"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad Pro (12.9-inch) (4th generation) Wi-Fi + Cellular"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad8,11"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad Pro (12.9-inch) (4th generation) Wi-Fi"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad8,10"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad Pro (11-inch) (2nd generation) Wi-Fi + Cellular"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad8,9"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad Pro (11-inch) (2nd generation) Wi-Fi"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad8,8"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad Pro 12.9-inch (3rd Generation)"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad8,7"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad Pro 12.9-inch (3rd generation) Wi-Fi + Cellular"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad8,6"</span>:
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad8,5"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad Pro 12.9-inch (3rd Generation)"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad8,4"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad Pro 11-inch"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad8,3"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad Pro 11-inch Wi-Fi + Cellular"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad8,2"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad Pro 11-inch"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad8,1"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad Pro 11-inch Wi-Fi"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad7,12"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad (7th generation) Wi-Fi + Cellular"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad7,11"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad (7th generation) Wi-Fi"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad7,6"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad (6th generation) Wi-Fi + Cellular"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad7,5"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad (6th generation) Wi-Fi"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad7,4"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad Pro (10.5-inch) Wi-Fi + Cellular"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad7,3"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad Pro (10.5-inch) Wi-Fi"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad7,2"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad Pro 12.9-inch (2nd generation) Wi-Fi + Cellular"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad7,1"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad Pro 12.9-inch (2nd generation) Wi-Fi"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad6,12"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad (5th generation) Wi-Fi + Cellular"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad6,11"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad (5th generation) Wi-Fi"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad6,8"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad Pro 12.9-inch Wi-Fi + Cellular"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad6,7"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad Pro 12.9-inch Wi-Fi"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad6,4"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad Pro (9.7-inch) Wi-Fi + Cellular"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad6,3"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad Pro (9.7-inch) Wi-Fi"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad5,4"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad Air 2 Wi-Fi + Cellular"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad5,3"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad Air 2 Wi-Fi"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad5,2"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad mini 4 Wi-Fi + Cellular"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad5,1"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad mini 4 Wi-Fi"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad4,9"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad mini 3 Wi-Fi + Cellular (TD-LTE)"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad4,8"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad mini 3 Wi-Fi + Cellular"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad4,7"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad mini 3 Wi-Fi"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad4,6"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad mini 2 Wi-Fi + Cellular (TD-LTE)"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad4,5"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad mini 2 Wi-Fi + Cellular"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad4,4"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad mini 2 Wi-Fi"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad4,3"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad Air Wi-Fi + Cellular (TD-LTE)"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad4,2"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad Air Wi-Fi + Cellular"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad4,1"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad Air Wi-Fi"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad3,6"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad (4th generation) Wi-Fi + Cellular (MM)"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad3,5"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad (4th generation) Wi-Fi + Cellular"</span>;
                <span class="hljs-keyword">case</span> <span class="hljs-string">"iPad3,4"</span>:
                    <span class="hljs-keyword">return</span> <span class="hljs-string">"iPad (4th generation) Wi-Fi"</span>;
            }
        }

        <span class="hljs-keyword">return</span> <span class="hljs-keyword">string</span>.IsNullOrWhiteSpace(version) ? <span class="hljs-string">"Unknown"</span> : version;
    }
}
</code></pre>
<p>The iOS implemntation is a lot more complicated because Apple does not provide an API for developers to get the model of a device. The <code>hw.machine</code> string provides a hardware identifier like <code>iPhone10,6</code> which we need to map to the device model. Apple doesn't provide official mappings but non-official mappings can be found at <a target="_blank" href="https://www.theiphonewiki.com/wiki/Models">The iPhone Wiki</a> or Danny Cabrera's <a target="_blank" href="https://github.com/dannycabrera/Get-iOS-Model">Get iOS Model</a> project. My mapping is based on Danny's but I only include devices capable of running versions of iOS supported by .NET MAUI.</p>
<h3 id="heading-windows-partial-class">Windows Partial Class</h3>
<p>The Windows implementation of our platform specific code goes in the <code>Platforms\Windows</code> folder hierarchy. </p>
<pre><code class="lang-c#"><span class="hljs-keyword">using</span> Windows.Security.ExchangeActiveSyncProvisioning;
<span class="hljs-keyword">using</span> Windows.System.Profile;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">MauiBeach.Services</span>;

<span class="hljs-keyword">internal</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">DeviceInfoService</span>
{
    <span class="hljs-function"><span class="hljs-keyword">internal</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">string</span> <span class="hljs-title">Model</span>(<span class="hljs-params"></span>)</span> =&gt; <span class="hljs-keyword">new</span> EasClientDeviceInformation().SystemProductName;

    <span class="hljs-function"><span class="hljs-keyword">internal</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">string</span> <span class="hljs-title">Platform</span>(<span class="hljs-params"></span>)</span> =&gt; <span class="hljs-string">$"UWP <span class="hljs-subst">{GetVersionString()}</span>"</span>;

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">string</span> <span class="hljs-title">GetVersionString</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">var</span> version = AnalyticsInfo.VersionInfo.DeviceFamilyVersion;

        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">ulong</span>.TryParse(version, <span class="hljs-keyword">out</span> <span class="hljs-keyword">var</span> v))
        {
            <span class="hljs-keyword">var</span> v1 = (v &amp; <span class="hljs-number">0xFFFF000000000000</span>L) &gt;&gt; <span class="hljs-number">48</span>;
            <span class="hljs-keyword">var</span> v2 = (v &amp; <span class="hljs-number">0x0000FFFF00000000</span>L) &gt;&gt; <span class="hljs-number">32</span>;
            <span class="hljs-keyword">var</span> v3 = (v &amp; <span class="hljs-number">0x00000000FFFF0000</span>L) &gt;&gt; <span class="hljs-number">16</span>;
            <span class="hljs-keyword">var</span> v4 = v &amp; <span class="hljs-number">0x000000000000FFFF</span>L;
            <span class="hljs-keyword">return</span> <span class="hljs-string">$"<span class="hljs-subst">{v1}</span>.<span class="hljs-subst">{v2}</span>.<span class="hljs-subst">{v3}</span>.<span class="hljs-subst">{v4}</span>"</span>;
        }

        <span class="hljs-keyword">return</span> version;
    }
}
</code></pre>
<p>The Android and iOS implementations are the ones where we want more information and the Windows implementation is very similar to the way Xamarin / MAUI Essentials works.</p>
<h3 id="heading-maccatalyst-partial-class">MacCatalyst Partial Class</h3>
<p>The MacCatalyst implementation of our platform specific code goes in the <code>Platforms\MacCatalyst</code> folder hierarchy.</p>
<pre><code class="lang-c#"><span class="hljs-keyword">using</span> Microsoft.Maui.Essentials;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">MauiBeach.Services</span>;

<span class="hljs-keyword">internal</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">DeviceInfoService</span>
{
    <span class="hljs-function"><span class="hljs-keyword">internal</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">string</span> <span class="hljs-title">Model</span>(<span class="hljs-params"></span>)</span> =&gt; DeviceInfo.Model;

    <span class="hljs-function"><span class="hljs-keyword">internal</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">string</span> <span class="hljs-title">Platform</span>(<span class="hljs-params"></span>)</span> =&gt; <span class="hljs-string">$"<span class="hljs-subst">{DeviceInfo.Platform}</span> <span class="hljs-subst">{DeviceInfo.VersionString}</span>"</span>;
}
</code></pre>
<p>I have no experience with MacCatalyst but if a MAUI app targets a platform it must include an implementation of the cross platform partial class for that platform. In order to fulfil that condition we simply return values from MAUI Essentials' <code>DeviceInfo</code> class. If that proves to be insufficient in future it will be easy to expand this class to provide more information.</p>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>Using partial classes to write platform specific code in .NET MAUI is quick to learn and I think slightly easier than the old <code>DependencyService</code> in Xamarin.Forms. And, most of the code in this article is copied and pasted from my Xamarin code so it is also easy to change from the old way to this new way of writing platform specific code. 🎉</p>
<p> </p>
<p> </p>
<p>Cover image includes a vector created by brgfx from <a target="_blank" href="https://www.freepik.com/vectors/">www.freepik.com</a>.</p>
]]></content:encoded></item><item><title><![CDATA[GitHub Action - Edit Release v1.1.0]]></title><description><![CDATA[Edit Release is a GitHub Action for editing an existing release. Edit the Name, Draft status and Pre-release status of a release as well as adding text and the content of markdown files to the Body of a release.
Edit Release is built with C# and Dock...]]></description><link>https://blog.taranissoftware.com/github-action-edit-release-v110</link><guid isPermaLink="true">https://blog.taranissoftware.com/github-action-edit-release-v110</guid><category><![CDATA[github-actions]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[Devops]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[.NET]]></category><dc:creator><![CDATA[Dave Murray]]></dc:creator><pubDate>Sat, 15 Jan 2022 19:11:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1641765996522/04ZN-7ap5.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Edit Release is a GitHub Action for editing an existing release. Edit the Name, Draft status and Pre-release status of a release as well as adding text and the content of markdown files to the Body of a release.</p>
<p>Edit Release is built with C# and Docker and uses GitHub Container Registry.</p>
<h2 id="heading-edit-release-v110-release-notes">Edit Release v1.1.0 Release Notes</h2>
<ul>
<li>Updates Action to .NET 6</li>
<li>Reduces Docker image layers</li>
<li>Disables .NET diagnostics</li>
<li>Switch to file scoped namespaces</li>
</ul>
<p> </p>
<ul>
<li>🛍 <a target="_blank" href="https://github.com/marketplace/actions/edit-release">GitHub Marketplace</a></li>
<li>👨‍💻 <a target="_blank" href="https://github.com/irongut/EditRelease">Repository</a></li>
</ul>
<p> </p>
<p> </p>
<p>People vector created by pch.vector - <a target="_blank" href="https://www.freepik.com/vectors/people">www.freepik.com</a></p>
]]></content:encoded></item><item><title><![CDATA[Building .NET MAUI apps with GitHub Actions]]></title><description><![CDATA[Writing a GitHub Actions workflow to build a cross platform .NET MAUI app is more complicated than the average .NET app. First you have to build all the platforms you want to support, taking into consideration the runners they can be built on. Then, ...]]></description><link>https://blog.taranissoftware.com/building-net-maui-apps-with-github-actions</link><guid isPermaLink="true">https://blog.taranissoftware.com/building-net-maui-apps-with-github-actions</guid><category><![CDATA[dotnet]]></category><category><![CDATA[github-actions]]></category><category><![CDATA[.NET]]></category><category><![CDATA[Xamarin]]></category><category><![CDATA[#dotnet-maui]]></category><dc:creator><![CDATA[Dave Murray]]></dc:creator><pubDate>Mon, 03 Jan 2022 18:03:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1641179527140/Nvan4zLjC.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Writing a GitHub Actions workflow to build a cross platform .NET MAUI app is more complicated than the average .NET app. First you have to build all the platforms you want to support, taking into consideration the runners they can be built on. Then, you have the fact that .NET MAUI is still in preview to complicate things. In this article I demonstrate a workflow that builds a cross platform .NET MAUI app for Android, iOS, MacCatalyst and Windows on GitHub, explaining each step in the process.</p>
<dl>
<dt>⚠ <strong>Warning! Out of Date Content Ahead!</strong> ⚠</dt>
<dd>Since the GA release of .NET MAUI the build process has changed and the workarounds required below are no longer needed. This article has been superceeded by my new article <a target="_blank" href="https://blog.taranissoftware.com/build-net-maui-apps-with-github-actions">Build .NET MAUI apps with GitHub Actions</a>.</dd>
</dl>


<h2 id="heading-preamble">Preamble</h2>
<p>We need a .NET MAUI app for our workflow to build so I will be using my demo app <a target="_blank" href="https://github.com/irongut/MauiBeach">Maui Beach</a>. Because .NET MAUI is in preview we will be using preview versions of .NET and MSBuild so our app must be compatible with the latest preview version of .NET MAUI. Currently this is Preview 11, I explain how to install Preview 11 and update your app in my article <a target="_blank" href="https://blog.taranissoftware.com/upgrading-to-net-maui-preview-11">Upgrading to .NET MAUI Preview 11</a>.</p>
<p>This is a CI build workflow without any testing, signing or deployment steps and will build our .NET MAUI app for Android, iOS, MacCatalyst and Windows in separate jobs that will run in parallel. Each build job will take 4 - 7 minutes to run and we will be using Windows and MacOS runners which are charged at an increased rate so be aware this workflow can really eat into your Actions minutes.</p>
<h2 id="heading-set-up-the-workflow">Set up the Workflow</h2>
<p>The first part of our CI build is standard stuff for GitHub Actions - set the action name, what events it responds to and any environment variables.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">CI</span> <span class="hljs-string">Build</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span> [ <span class="hljs-string">master</span> ]
    <span class="hljs-attr">paths-ignore:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'**/*.md'</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'**/*.gitignore'</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'**/*.gitattributes'</span>
  <span class="hljs-attr">pull_request:</span>
    <span class="hljs-attr">branches:</span> [ <span class="hljs-string">master</span> ]
  <span class="hljs-attr">workflow_dispatch:</span>
<span class="hljs-attr">permissions:</span>
  <span class="hljs-attr">contents:</span> <span class="hljs-string">read</span>

<span class="hljs-attr">env:</span>
  <span class="hljs-attr">DOTNET_NOLOGO:</span> <span class="hljs-literal">true</span>                     <span class="hljs-comment"># Disable the .NET logo</span>
  <span class="hljs-attr">DOTNET_SKIP_FIRST_TIME_EXPERIENCE:</span> <span class="hljs-literal">true</span> <span class="hljs-comment"># Disable the .NET first time experience</span>
  <span class="hljs-attr">DOTNET_CLI_TELEMETRY_OPTOUT:</span> <span class="hljs-literal">true</span>       <span class="hljs-comment"># Disable sending .NET CLI telemetry</span>
</code></pre>
<p>As you can see our workflow will respond to push and pull request events on the <code>master</code> branch and has a manual <code>workflow_dispatch</code> trigger. I've also included a little trick I learned recently, to prevent unnecessary builds I've excluded the <code>push</code> event running when the only changes involve markdown files, <code>.gitignore</code> or <code>.gitattributes</code>.</p>
<p>The environment variables tell the .NET CLI not to show the logo, to skip the first time experience and disable telemetry. This should help improve build times.</p>
<h2 id="heading-android-build-job">Android Build Job</h2>
<pre><code class="lang-yaml">  <span class="hljs-attr">build-android:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">windows-2022</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">Android</span> <span class="hljs-string">Build</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">.NET</span> <span class="hljs-number">6</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-dotnet@v1</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">dotnet-version:</span> <span class="hljs-number">6.0</span><span class="hljs-string">.x</span>
          <span class="hljs-attr">include-prerelease:</span> <span class="hljs-literal">true</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-java@v2</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">distribution:</span> <span class="hljs-string">'microsoft'</span>
          <span class="hljs-attr">java-version:</span> <span class="hljs-string">'11'</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">MAUI</span> <span class="hljs-string">Workloads</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          dotnet workload install android --ignore-failed-sources
          dotnet workload install maui --ignore-failed-sources
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Restore</span> <span class="hljs-string">Dependencies</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">restore</span> <span class="hljs-string">src/MauiBeach/MauiBeach.csproj</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">MAUI</span> <span class="hljs-string">Android</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">build</span> <span class="hljs-string">src/MauiBeach/MauiBeach.csproj</span> <span class="hljs-string">-c</span> <span class="hljs-string">Release</span> <span class="hljs-string">-f</span> <span class="hljs-string">net6.0-android</span> <span class="hljs-string">--no-restore</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Upload</span> <span class="hljs-string">Android</span> <span class="hljs-string">Artifact</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/upload-artifact@v2.3.1</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">name:</span> <span class="hljs-string">android-ci-build</span>
          <span class="hljs-attr">path:</span> <span class="hljs-string">src/MauiBeach/bin/Release/net6.0-android/*Signed.a*</span>
</code></pre>
<p>Building a .NET MAUI app for Android can be done on MacOS or Windows so our build will run on Windows since that uses less minutes. Building .NET 6 apps requires Visual Studio 2022 so we must use a <code>windows-2022</code> runner.</p>
<p>We start by checking out the code as normal and then install .NET 6. Because MAUI is still in preview we need to install the latest pre-release version of .NET 6.</p>
<p>Building a .NET MAUI app for Android requires JDK 11 so that is our next step. I've picked the Microsoft distribution of JDK 11.</p>
<p>The .NET MAUI workloads are not included in GitHub's <code>windows-2022</code> runner yet so we need to install them too. To build an Android app we need the Android and MAUI workloads.</p>
<p>Now we can get to the build part of the workflow - restore dependencies and build the app. We can use a standard <code>dotnet build</code> command to build our app for Android. The important option here is <code>-f net6.0-android</code> to set the framework we want to build. I like my CI builds to be release builds so I've set the option <code>-c Release</code> as well as <code>--no-restore</code> so we only restore our dependencies once.</p>
<p>Finally we upload our build artifacts, I've chosen to upload both AAB and APK packages.</p>
<h2 id="heading-windows-build-job">Windows Build Job</h2>
<pre><code class="lang-yaml">  <span class="hljs-attr">build-windows:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">windows-2022</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">Windows</span> <span class="hljs-string">Build</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">.NET</span> <span class="hljs-number">6</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-dotnet@v1</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">dotnet-version:</span> <span class="hljs-number">6.0</span><span class="hljs-string">.x</span>
          <span class="hljs-attr">include-prerelease:</span> <span class="hljs-literal">true</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">MSBuild</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">microsoft/setup-msbuild@v1.1</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">vs-prerelease:</span> <span class="hljs-literal">true</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">MAUI</span> <span class="hljs-string">Workloads</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          dotnet workload install maui --ignore-failed-sources
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Restore</span> <span class="hljs-string">Dependencies</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">restore</span> <span class="hljs-string">src/MauiBeach/MauiBeach.csproj</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">MAUI</span> <span class="hljs-string">Windows</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">msbuild</span> <span class="hljs-string">src/MauiBeach/MauiBeach.csproj</span> <span class="hljs-string">-r</span> <span class="hljs-string">-p:Configuration=Release</span> <span class="hljs-string">-p:RestorePackages=false</span> <span class="hljs-string">-p:TargetFramework=net6.0-windows10.0.19041</span> <span class="hljs-string">/p:GenerateAppxPackageOnBuild=true</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Upload</span> <span class="hljs-string">Windows</span> <span class="hljs-string">Artifact</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/upload-artifact@v2.3.1</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">name:</span> <span class="hljs-string">windows-ci-build</span>
          <span class="hljs-attr">path:</span> <span class="hljs-string">src/MauiBeach/bin/Release/net6.0-windows*/**/MauiBeach*.msix</span>
</code></pre>
<p>Building a .NET app for Windows requires Windows and .NET 6 requires Visual Studio 2022 so we will use a <code>windows-2022</code> runner for our Windows build job as well.</p>
<p>Again we start by checking out the code and install the latest pre-release version of .NET 6.</p>
<p>Using <code>dotnet build</code> to build a .NET MAUI app for Windows doesn't work with the current previews of .NET MAUI so for this job we will need to use MSBuild. We need the latest pre-release version of MSBuild so that is our next step.</p>
<p>The .NET MAUI workloads are not included in GitHub's <code>windows-2022</code> runner yet so we need to install them too. To build a Windows app we only need the MAUI workload.</p>
<p>With our build environment set up we can now restore our dependencies and build our app. I want MSBuild to create a release build and not to restore dependencies itself. The important options in the MSBuild command are the framework <code>-p:TargetFramework=net6.0-windows10.0.19041</code> and the instruction to create an app package <code>/p:GenerateAppxPackageOnBuild=true</code>.</p>
<p>Finally we upload the app package as a build artifact.</p>
<h2 id="heading-ios-build-job">iOS Build Job</h2>
<pre><code class="lang-yaml">  <span class="hljs-attr">build-ios:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">macos-11</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">iOS</span> <span class="hljs-string">Build</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">.NET</span> <span class="hljs-number">6</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-dotnet@v1</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">dotnet-version:</span> <span class="hljs-number">6.0</span><span class="hljs-string">.x</span>
          <span class="hljs-attr">include-prerelease:</span> <span class="hljs-literal">true</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">MAUI</span> <span class="hljs-string">Workloads</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          dotnet workload install ios --ignore-failed-sources
          dotnet workload install maui --ignore-failed-sources
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Restore</span> <span class="hljs-string">Dependencies</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">restore</span> <span class="hljs-string">src/MauiBeach/MauiBeach.csproj</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">MAUI</span> <span class="hljs-string">iOS</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">build</span> <span class="hljs-string">src/MauiBeach/MauiBeach.csproj</span> <span class="hljs-string">-c</span> <span class="hljs-string">Release</span> <span class="hljs-string">-f</span> <span class="hljs-string">net6.0-ios</span> <span class="hljs-string">--no-restore</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Upload</span> <span class="hljs-string">iOS</span> <span class="hljs-string">Artifact</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/upload-artifact@v2.3.1</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">name:</span> <span class="hljs-string">ios-ci-build</span>
          <span class="hljs-attr">path:</span> <span class="hljs-string">src/MauiBeach/bin/Release/net6.0-ios/**/*.app/</span>
</code></pre>
<p>Apple requires that all iOS apps are built on a Mac and .NET 6 requires Visual Studio 2022 so we need to use a <code>macos-11</code> runner for our iOS build job.</p>
<p>Again we start by checking out the code and install the latest pre-release version of .NET 6.</p>
<p>The .NET MAUI workloads are not included in GitHub's <code>macos-11</code> runner yet so we need to install them too. To build an iOS app we need the iOS and MAUI workloads.</p>
<p>Now we can restore our dependencies and build our app. Like the Android build job we can use a standard <code>dotnet build</code> command to build our app for iOS. Again I want <code>dotnet build</code> to create a release build and not to restore dependencies. The important option here is <code>-f net6.0-ios</code> to set the framework we want to build.</p>
<p>Finally we upload the <code>*.app</code> folder as a build artifact.</p>
<h2 id="heading-maccatalyst-build-job">MacCatalyst Build Job</h2>
<pre><code class="lang-yaml">  <span class="hljs-attr">build-mac:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">macos-11</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">MacCatalyst</span> <span class="hljs-string">Build</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">.NET</span> <span class="hljs-number">6</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-dotnet@v1</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">dotnet-version:</span> <span class="hljs-number">6.0</span><span class="hljs-string">.x</span>
          <span class="hljs-attr">include-prerelease:</span> <span class="hljs-literal">true</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">MAUI</span> <span class="hljs-string">Workloads</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">|
          dotnet workload install maccatalyst --ignore-failed-sources
          dotnet workload install maui --ignore-failed-sources
</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Restore</span> <span class="hljs-string">Dependencies</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">restore</span> <span class="hljs-string">src/MauiBeach/MauiBeach.csproj</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">MAUI</span> <span class="hljs-string">MacCatalyst</span>
        <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">build</span> <span class="hljs-string">src/MauiBeach/MauiBeach.csproj</span> <span class="hljs-string">-c</span> <span class="hljs-string">Release</span> <span class="hljs-string">-f</span> <span class="hljs-string">net6.0-maccatalyst</span> <span class="hljs-string">--no-restore</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Upload</span> <span class="hljs-string">MacCatalyst</span> <span class="hljs-string">Artifact</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/upload-artifact@v2.3.1</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">name:</span> <span class="hljs-string">macos-ci-build</span>
          <span class="hljs-attr">path:</span> <span class="hljs-string">src/MauiBeach/bin/Release/net6.0-maccatalyst/**/*.app/</span>
</code></pre>
<p>Our MacCatalyst build job is very similar to our iOS build job. Using a <code>macos-11</code> runner we check out our code and install the latest pre-release version of .NET 6. To build a MacCatalyst app we need the MacCatalyst and MAUI workloads so they are installed next.</p>
<p>Again we can use a standard <code>dotnet build</code> command to build our app for MacCatalyst. For this job the framework option is <code>-f net6.0-maccatalyst</code>.</p>
<p>The last step is to upload the <code>*.app</code> folder as a build artifact.</p>
<h2 id="heading-and-finally">And Finally...</h2>
<p>The complete workflow can be found in my Maui Beach repo:<br />
👩‍💻 <a target="_blank" href="https://github.com/irongut/MauiBeach/blob/master/.github/workflows/ci-build.yml">.github/workflows/ci-build.yml</a></p>
<p>And, you can see the workflow run <a target="_blank" href="https://github.com/irongut/MauiBeach/actions/workflows/ci-build.yml">here</a>. 🥳🎉</p>
<p> </p>
<p> </p>
<p>Cover image includes a vector created by brgfx from <a target="_blank" href="https://www.freepik.com/vectors/">www.freepik.com</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Upgrading to .NET MAUI Preview 11]]></title><description><![CDATA[.NET MAUI Preview 11 was released a little over a week ago but it was something of a stealth release due to a late bug in Visual Studio 2022 17.1 Preview 2. We can still install Preview 11 using the CLI but there are some caveats. In this article I e...]]></description><link>https://blog.taranissoftware.com/upgrading-to-net-maui-preview-11</link><guid isPermaLink="true">https://blog.taranissoftware.com/upgrading-to-net-maui-preview-11</guid><category><![CDATA[dotnet]]></category><category><![CDATA[.NET]]></category><category><![CDATA[Xamarin]]></category><category><![CDATA[#dotnet-maui]]></category><dc:creator><![CDATA[Dave Murray]]></dc:creator><pubDate>Tue, 28 Dec 2021 00:14:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1640649736712/0TNB0ttlt.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://github.com/dotnet/maui/releases/tag/6.0.101-preview.11.3">.NET MAUI Preview 11</a> was released a little over a week ago but it was something of a stealth release due to a late bug in Visual Studio 2022 17.1 Preview 2. We can still install Preview 11 using the CLI but there are some caveats. In this article I explain how to install .NET MAUI Preview 11, what issues you might encounter and how to upgrade an app from Preview 10 to Preview 11.</p>
<h2 id="heading-installing-net-maui-preview-11">Installing .NET MAUI Preview 11</h2>
<p><strong>Update: Visual Studio 2022 17.1 Preview 2 is now available and is the recommended method to install <a target="_blank" href="https://devblogs.microsoft.com/dotnet/announcing-dotnet-maui-preview-11">.NET MAUI Preview 11</a>. CLI install is no longer needed, see below for how to <a class="post-section-overview" href="#heading-removing-cli-workloads">remove the CLI workloads</a> and install with VS2022.</strong></p>
<p>.NET MAUI Preview 11 can be installed using the command line but will break XAML Hot Reload in Visual Studio 2022 17.1 Preview 1 and risks conflicts once Visual Studio 2022 17.1 Preview 2 is released. If you're willing to accept those issues the process is fairly simple:</p>
<ol>
<li>Close all Visual Studio + Visual Studio Installer windows</li>
<li>Install <a target="_blank" href="https://dotnet.microsoft.com/en-us/download/dotnet/6.0">.NET v6.0.1</a> (aka v6.0.101)</li>
<li><code>dotnet --version</code> (check for 6.0.101)</li>
<li><code>dotnet workload update</code></li>
<li><code>dotnet workload install maui</code></li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640646246647/1qZ1TtmXJ.png" alt="Install MAUI Preview 11" /></p>
<p>At this point it is recommended to create a new MAUI project using the command line <code>dotnet new maui</code> to check for templates that need to be installed or updated. This command will install or update all MAUI templates:</p>
<pre><code class="lang-txt">dotnet new --install  Microsoft.Maui.Templates
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640646323114/oC5mwnfiO.png" alt="Install MAUI Templates" /></p>
<p>Another useful command is <code>dotnet workload list</code> which will show all the workloads installed from the command line - note it does not list workloads installed using the Visual Studio Installer. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640646332643/qJqjdheW4.png" alt="dotnet workload list" /></p>
<h3 id="heading-disable-xaml-hot-reload">Disable XAML Hot Reload</h3>
<p>As mentioned Preview 11 breaks XAML Hot Reload in Visual Studio 2022 17.1 Preview 1. I didn't find any problems running a MAUI app on Windows with XAML Hot Reload still enabled but when I tried Android the app wouldn't run. To disable XAML Hot Reload in Visual Studio:</p>
<ol>
<li>Open the Options window (<strong>Tools</strong> | <strong>Options</strong>)</li>
<li>Expand the <strong>Debugging</strong> section</li>
<li>Untick <strong>Enable XAML Hot Reload</strong> at the top of the window</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640648640157/I4Urd5VnI.png" alt="Disable XAML Hot Reload" /></p>
<h3 id="heading-removing-cli-workloads">Removing CLI Workloads</h3>
<p>Now that Visual Studio 2022 17.1 Preview 2 is available we should remove the CLI workloads before updating Visual Studio. We can do this with the <code>dotnet workload uninstall</code> command:</p>
<pre><code class="lang-txt">dotnet workload uninstall maui
dotnet workload uninstall android
dotnet workload uninstall ios
dotnet workload uninstall maccatalyst
</code></pre>
<p>Check that you have uninstalled all the workloads with <code>dotnet workload list</code> and you can update to Visual Studio 2022 17.1 Preview 2. Remember to re-enable Hot Reload after the update.</p>
<h2 id="heading-updating-an-app-for-net-maui-preview-11">Updating an app for .NET MAUI Preview 11</h2>
<p>In order to update <a target="_blank" href="https://github.com/irongut/MauiBeach">MAUI Beach</a> from Preview 10 to Preview 11 I made the following changes. You can also see them on GitHub <a target="_blank" href="https://github.com/irongut/MauiBeach/commit/422f2f6ceaeac1aec81b9d809c1bdf96e719a708">here</a> and <a target="_blank" href="https://github.com/irongut/MauiBeach/commit/63a1710327b26047f876c58e5a49b9691841e276">here</a>.</p>
<h3 id="heading-required-changes-for-windows">Required Changes for Windows</h3>
<p>In your project file <code>*.csproj</code> change the <code>RuntimeIdentifier</code> to <code>win10-x64</code> in this section:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">PropertyGroup</span> <span class="hljs-attr">Condition</span>=<span class="hljs-string">"$(TargetFramework.Contains('-windows'))"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">OutputType</span>&gt;</span>WinExe<span class="hljs-tag">&lt;/<span class="hljs-name">OutputType</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">RuntimeIdentifier</span>&gt;</span>win10-x64<span class="hljs-tag">&lt;/<span class="hljs-name">RuntimeIdentifier</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">PropertyGroup</span>&gt;</span>
</code></pre>
<p>You will also need to update the <code>Microsoft.WindowsAppSDK</code> and <code>Microsoft.Graphics.Win2D</code> dependencies. Using the Visual Studio package manager didn't work for me because those packages are in their own conditional <code>ItemGroup</code> so I would recommend updating them by editing your <code>*.csproj</code> manually:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">ItemGroup</span> <span class="hljs-attr">Condition</span>=<span class="hljs-string">"$(TargetFramework.Contains('-windows'))"</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- Required - WinUI does not yet have buildTransitive for everything --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Microsoft.Graphics.Win2D"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"1.0.0.30"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Microsoft.WindowsAppSDK"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"1.0.0"</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ItemGroup</span>&gt;</span>
</code></pre>
<p>When I tried to run my app on Windows I ran into this error related to a splash screen:</p>
<pre><code class="lang-txt">DEP0700: Registration of the app failed. [0x80073CF6] 
AppxManifest.xml(33,27): error 0x80070002: Cannot install or update package 
because the splash screen image [appiconfgSplashScreen.png] cannot be located.
</code></pre>
<p>I'm not sure if this was because of Preview 11 but I was able to fix it by updating the <code>uap:SplashScreen</code> property in <code>Platforms/Windows/Package.appxmanifest</code>:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">uap:SplashScreen</span> <span class="hljs-attr">Image</span>=<span class="hljs-string">"Assets\wave_splashSplashScreen.png"</span> /&gt;</span>
</code></pre>
<p>Amusingly I still don't actually see a splash screen on Windows but it compiles and runs. 🤷‍♂️</p>
<h3 id="heading-required-changes-for-android">Required Changes for Android</h3>
<p>In <code>Platforms/Android/MainActivity.cs</code> add the <code>OnCreate</code> and <code>OnRequestPermissionsResult</code> methods from the new template:</p>
<pre><code class="lang-c#"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">MainActivity</span> : <span class="hljs-title">MauiAppCompatActivity</span>
{
    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnCreate</span>(<span class="hljs-params">Bundle savedInstanceState</span>)</span>
    {
        <span class="hljs-keyword">base</span>.OnCreate(savedInstanceState);
        Platform.Init(<span class="hljs-keyword">this</span>, savedInstanceState);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnRequestPermissionsResult</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> requestCode, <span class="hljs-keyword">string</span>[] permissions, Permission[] grantResults</span>)</span>
    {
        Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
        <span class="hljs-keyword">base</span>.OnRequestPermissionsResult(requestCode, permissions, grantResults);
    }
}
</code></pre>
<h3 id="heading-required-changes-for-ios-andamp-maccatalyst">Required Changes for iOS &amp; MacCatalyst</h3>
<p>I didn't find any changes that were necessary for iOS or MacCatalyst. 🎉</p>
<h3 id="heading-optional-changes">Optional Changes</h3>
<p>The .NET MAUI Preview 11 app template uses C# 10 implicit <a target="_blank" href="https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-10#global-using-directives">global usings</a> and <a target="_blank" href="https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-10#file-scoped-namespace-declaration">file-scoped namespaces</a>. If you want to opt-in to implicit global usings add the following property to the first <code>PropertyGroup</code> in your <code>*.csproj</code>:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">ImplicitUsings</span>&gt;</span>enable<span class="hljs-tag">&lt;/<span class="hljs-name">ImplicitUsings</span>&gt;</span>
</code></pre>
<p>You can then remove most of the <code>using</code> statements in your code! </p>
<p>File-scoped namespaces are even easier to use. Just open a <code>*.cs</code> file and place your cursor on the namespace line, a Quick Action icon will appear, click the <strong>Convert to file-scoped namespace</strong> action and you're done.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640646581263/8G8WYlM8F.png" alt="Convert to file scoped namespace" /></p>
<h2 id="heading-useful-links">Useful Links</h2>
<ul>
<li><a target="_blank" href="https://devblogs.microsoft.com/dotnet/december-2021-updates/">.NET December Updates</a></li>
<li><a target="_blank" href="https://dotnet.microsoft.com/en-us/download/dotnet/6.0">.NET 6 Downloads</a></li>
<li><a target="_blank" href="https://github.com/dotnet/maui/releases/tag/6.0.101-preview.11.3">MAUI Preview 11 Release</a></li>
</ul>
<p> </p>
<p> </p>
<p>Cover image includes a vector created by brgfx from <a target="_blank" href="https://www.freepik.com/vectors/tree">www.freepik.com</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Document your .NET code with DocFX and GitHub Actions]]></title><description><![CDATA[In this article I show how to use XML doc comments, DocFX and GitHub Actions to automatically build and publish documentation for your .NET code. Along the way I'll explain how to install DocFX and set up a documentation project, add XML doc comments...]]></description><link>https://blog.taranissoftware.com/document-your-net-code-with-docfx-and-github-actions</link><guid isPermaLink="true">https://blog.taranissoftware.com/document-your-net-code-with-docfx-and-github-actions</guid><category><![CDATA[.NET]]></category><category><![CDATA[documentation]]></category><category><![CDATA[github-actions]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[dotnet]]></category><dc:creator><![CDATA[Dave Murray]]></dc:creator><pubDate>Sun, 19 Dec 2021 23:38:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1639938966765/4du-0AJ5B.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this article I show how to use XML doc comments, DocFX and GitHub Actions to automatically build and publish documentation for your .NET code. Along the way I'll explain how to install DocFX and set up a documentation project, add XML doc comments to your code, generate Intellisense tooltips and use GitHub Actions to build and publish your documentation to GitHub Pages. The code for this article is available on GitHub: <a target="_blank" href="https://github.com/irongut/DocFXExample">irongut/DocFXExample</a></p>
<h2 id="heading-example-code">Example Code</h2>
<p>The example solution in the <code>src</code> folder contains a .NET Standard 2.1 class library <code>DocFXExample</code> and a test project. <code>DocFXExample</code> has one class <code>Example</code> which contains a single method <code>Divide()</code>. The code itself is not important, we just need something we can document.</p>
<pre><code class="lang-c#"><span class="hljs-keyword">namespace</span> <span class="hljs-title">DocFXExample</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Example</span>
    {
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">static</span> (<span class="hljs-params"><span class="hljs-keyword">int</span> quotient, <span class="hljs-keyword">int</span> remainder</span>) <span class="hljs-title">Divide</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> dividend, <span class="hljs-keyword">int</span> divisor</span>)</span>
        {
            <span class="hljs-keyword">return</span> (dividend / divisor, dividend % divisor);
        }
    }
}
</code></pre>
<h2 id="heading-installing-docfx">Installing DocFX</h2>
<p><a target="_blank" href="https://github.com/dotnet/docfx">DocFX</a> is a documentation generator for .NET which supports C#, Visual Basic and F#. Using XML doc comments in your code along with Markdown and YAML files, DocFX builds a static HTML website which can be hosted on GitHub Pages or any web server.</p>
<p>DocFX can be integrated with Visual Studio but I prefer using the command line version which can be installed using Chocolatey, Homebrew or manually from GitHub.</p>
<ul>
<li>Chocolatey: <code>choco install docfx -y</code></li>
<li>Homebrew: <code>brew install docfx</code></li>
<li>GitHub: <a target="_blank" href="https://github.com/dotnet/docfx/releases">Download DocFX</a>, extract to a folder and add it to your PATH</li>
</ul>
<h2 id="heading-create-a-documentation-project">Create a Documentation Project</h2>
<p>First we need to initialise a new DocFX project. In the root of the repo type the command:</p>
<p><code>docfx init -q</code></p>
<p>This generates a documentation project named <code>docfx_project</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639879810609/8MwUQB6Cj.png" alt="DocFX Project" /></p>
<p>The documentation project contains several files and folders we can use to customise the generated documentation:</p>
<ul>
<li><code>docfx.json</code> - configuration file</li>
<li><code>index.md</code> - home page for the generated site</li>
<li><code>toc.yml</code> - main navigation menu</li>
<li><code>articles</code> - folder for custom articles</li>
<li><code>api/index.md</code> - index page for API documentation</li>
</ul>
<p>We need to tell DocFX where to find our code by updating the <code>src</code> property in the <code>metadata</code> section of the configuration file <code>docfx.json</code>. We also need to change the output folder <code>dest</code> from <code>_site</code> to <code>../docs</code> to make publishing to GitHub Pages easier:</p>
<pre><code class="lang-yml">{
  <span class="hljs-attr">"metadata":</span> [
    {
      <span class="hljs-attr">"src":</span> [
        {
          <span class="hljs-attr">"src":</span> <span class="hljs-string">"../"</span>,
          <span class="hljs-attr">"files":</span> [
            <span class="hljs-string">"src/DocFXExample/**.csproj"</span>
          ]
        }
      ],
      <span class="hljs-attr">"dest":</span> <span class="hljs-string">"api"</span>,
      <span class="hljs-attr">"disableGitFeatures":</span> <span class="hljs-literal">false</span>,
      <span class="hljs-attr">"disableDefaultFilter":</span> <span class="hljs-literal">false</span>
    }
  ],
  <span class="hljs-attr">"build":</span> {
    <span class="hljs-attr">"content":</span> [
      {
        <span class="hljs-attr">"files":</span> [
          <span class="hljs-string">"api/**.yml"</span>,
          <span class="hljs-string">"api/index.md"</span>
        ]
      },
      {
        <span class="hljs-attr">"files":</span> [
          <span class="hljs-string">"articles/**.md"</span>,
          <span class="hljs-string">"articles/**/toc.yml"</span>,
          <span class="hljs-string">"toc.yml"</span>,
          <span class="hljs-string">"*.md"</span>
        ]
      }
    ],
    <span class="hljs-attr">"resource":</span> [
      {
        <span class="hljs-attr">"files":</span> [
          <span class="hljs-string">"images/**"</span>
        ]
      }
    ],
    <span class="hljs-attr">"overwrite":</span> [
      {
        <span class="hljs-attr">"files":</span> [
          <span class="hljs-string">"apidoc/**.md"</span>
        ],
        <span class="hljs-attr">"exclude":</span> [
          <span class="hljs-string">"obj/**"</span>,
          <span class="hljs-string">"_site/**"</span>
        ]
      }
    ],
    <span class="hljs-attr">"dest":</span> <span class="hljs-string">"../docs"</span>,
    <span class="hljs-attr">"globalMetadataFiles":</span> [],
    <span class="hljs-attr">"fileMetadataFiles":</span> [],
    <span class="hljs-attr">"template":</span> [
      <span class="hljs-string">"default"</span>
    ],
    <span class="hljs-attr">"postProcessors":</span> [],
    <span class="hljs-attr">"markdownEngineName":</span> <span class="hljs-string">"markdig"</span>,
    <span class="hljs-attr">"noLangKeyword":</span> <span class="hljs-literal">false</span>,
    <span class="hljs-attr">"keepFileLink":</span> <span class="hljs-literal">false</span>,
    <span class="hljs-attr">"cleanupCacheHistory":</span> <span class="hljs-literal">false</span>,
    <span class="hljs-attr">"disableGitFeatures":</span> <span class="hljs-literal">false</span>
  }
}
</code></pre>
<p>DocFX includes its own <code>.gitignore</code> files in the <code>docfx_project</code> and <code>docfx_project/api</code> folders but I like to keep my ignores central so I delete those files and add the following to the root <code>.gitignore</code> file:</p>
<pre><code class="lang-txt"># DocFX
docfx_project/api/.manifest
docfx_project/api/*.yml
docs
</code></pre>
<p>We can now build and test our documentation project locally with the following command:</p>
<p><code>docfx docfx_project\docfx.json --serve</code></p>
<p>You can view the generated website at: <code>http://localhost:8080/</code></p>
<h2 id="heading-add-xml-doc-comments">Add XML Doc Comments</h2>
<p><a target="_blank" href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/xmldoc/">XML documentation comments</a> are special comment fields indicated by triple slashes which can be converted into documentation by tools like <a target="_blank" href="https://github.com/dotnet/docfx">DocFX</a>, <a target="_blank" href="https://github.com/EWSoftware/SHFB">Sandcastle</a> and <a target="_blank" href="https://github.com/doxygen/doxygen">Doxygen</a>.</p>
<pre><code class="lang-c#"><span class="hljs-keyword">namespace</span> <span class="hljs-title">DocFXExample</span>
{
    <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;summary&gt;</span>A simple example class with one method which divides two numbers.<span class="hljs-doctag">&lt;/summary&gt;</span></span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Example</span>
    {
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;summary&gt;</span>Divides the specified dividend by the divisor, returning the quotient and the remainder as a Tuple.<span class="hljs-doctag">&lt;/summary&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;param name="dividend"&gt;</span>The dividend - the number which will be divided by the divisor.<span class="hljs-doctag">&lt;/param&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;param name="divisor"&gt;</span>The divisor - the number by which the dividend will be divided.<span class="hljs-doctag">&lt;/param&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;returns&gt;</span>(quotient, remainder)<span class="hljs-doctag">&lt;/returns&gt;</span></span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">static</span> (<span class="hljs-params"><span class="hljs-keyword">int</span> quotient, <span class="hljs-keyword">int</span> remainder</span>) <span class="hljs-title">Divide</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> dividend, <span class="hljs-keyword">int</span> divisor</span>)</span>
        {
            <span class="hljs-keyword">return</span> (dividend / divisor, dividend % divisor);
        }
    }
}
</code></pre>
<h3 id="heading-intellisense-tooltips">Intellisense Tooltips</h3>
<p>The C# compiler can also process XML doc comments and generate IntelliSense tooltips in Visual Studio. To enable Intellisense we need to tell the compiler to generate an XML file by adding a <code>PropertyGroup</code> to the <code>csproj</code> project file:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">PropertyGroup</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">DocumentationFile</span>&gt;</span>DocFXExample\Taranis.DocFXExample.xml<span class="hljs-tag">&lt;/<span class="hljs-name">DocumentationFile</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">PropertyGroup</span>&gt;</span>
</code></pre>
<p>After building the project the contents of the <code>summary</code> and <code>returns</code> tags will now appear in Visual Studio:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639879858870/LncWsD1dV.png" alt="Intellisense Tooltip" /></p>
<h2 id="heading-github-actions-workflows">GitHub Actions Workflows</h2>
<p>In the example repo I've created three GitHub Actions workflows:</p>
<ul>
<li><code>ci-build.yml</code></li>
<li><code>docs-only-build.yml</code></li>
<li><code>release-build.yml</code></li>
</ul>
<p>All three workflows include a manual <code>workflow_dispatch</code> trigger for testing purposes and use Ubuntu runners. Why Ubuntu? Windows runners are charged at 2x the cost of Linux runners, MacOS runners are charged at 10x the cost of Linux runners and only Linux runners are compatible with Docker based Actions so unless you need Windows or MacOS to build your solution it is better to use an Ubuntu runner.</p>
<h3 id="heading-ci-build">CI Build</h3>
<p><code>ci-build.yml</code> is a typical .NET Continuous Integration workflow with a single job that runs on any push or pull request to the master branch. It checks out the repo, sets up .NET, builds and tests the solution before uploading the Nuget library and test coverage report as build artifacts.</p>
<pre><code class="lang-yml"><span class="hljs-attr">name:</span> <span class="hljs-string">CI</span> <span class="hljs-string">Build</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span> [ <span class="hljs-string">master</span> ]
  <span class="hljs-attr">pull_request:</span>
    <span class="hljs-attr">branches:</span> [ <span class="hljs-string">master</span> ]
  <span class="hljs-attr">workflow_dispatch:</span>
<span class="hljs-attr">permissions:</span>
  <span class="hljs-attr">contents:</span> <span class="hljs-string">read</span>  
  <span class="hljs-attr">pull-requests:</span> <span class="hljs-string">write</span>

<span class="hljs-attr">env:</span>
  <span class="hljs-attr">DOTNET_NOLOGO:</span> <span class="hljs-literal">true</span>                     <span class="hljs-comment"># Disable the .NET logo in the console output</span>
  <span class="hljs-attr">DOTNET_SKIP_FIRST_TIME_EXPERIENCE:</span> <span class="hljs-literal">true</span> <span class="hljs-comment"># Disable the .NET first time experience to skip caching NuGet packages and speed up the build</span>
  <span class="hljs-attr">DOTNET_CLI_TELEMETRY_OPTOUT:</span> <span class="hljs-literal">true</span>       <span class="hljs-comment"># Disable sending .NET CLI telemetry to Microsoft</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">build:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">CI</span> <span class="hljs-string">Build</span>
    <span class="hljs-attr">steps:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">.NET</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-dotnet@v1</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">dotnet-version:</span> <span class="hljs-number">5.0</span><span class="hljs-string">.x</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Restore</span> <span class="hljs-string">Dependencies</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">restore</span> <span class="hljs-string">src/DocFXExample.sln</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">build</span> <span class="hljs-string">src/DocFXExample.sln</span> <span class="hljs-string">--configuration</span> <span class="hljs-string">Release</span> <span class="hljs-string">--no-restore</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Test</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">test</span> <span class="hljs-string">src/DocFXExample.sln</span> <span class="hljs-string">--configuration</span> <span class="hljs-string">Release</span> <span class="hljs-string">--no-build</span> <span class="hljs-string">--verbosity</span> <span class="hljs-string">normal</span> <span class="hljs-string">--collect:"XPlat</span> <span class="hljs-string">Code</span> <span class="hljs-string">Coverage"</span> <span class="hljs-string">--results-directory</span> <span class="hljs-string">./coverage</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Copy</span> <span class="hljs-string">Coverage</span> <span class="hljs-string">To</span> <span class="hljs-string">Predictable</span> <span class="hljs-string">Location</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">cp</span> <span class="hljs-string">coverage/**/coverage.cobertura.xml</span> <span class="hljs-string">coverage.cobertura.xml</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Coverage</span> <span class="hljs-string">Summary</span> <span class="hljs-string">Report</span>
      <span class="hljs-attr">uses:</span>  <span class="hljs-string">irongut/CodeCoverageSummary@v1.2.0</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">filename:</span> <span class="hljs-string">coverage.cobertura.xml</span>
        <span class="hljs-attr">badge:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">format:</span> <span class="hljs-string">'md'</span>
        <span class="hljs-attr">output:</span> <span class="hljs-string">'both'</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Add</span> <span class="hljs-string">Coverage</span> <span class="hljs-string">PR</span> <span class="hljs-string">Comment</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">marocchino/sticky-pull-request-comment@v2</span>
      <span class="hljs-attr">if:</span> <span class="hljs-string">github.event_name</span> <span class="hljs-string">==</span> <span class="hljs-string">'pull_request'</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">recreate:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">path:</span> <span class="hljs-string">code-coverage-results.md</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Upload</span> <span class="hljs-string">Coverage</span> <span class="hljs-string">Artifact</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/upload-artifact@v2.3.0</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">test-coverage-report</span>
        <span class="hljs-attr">path:</span> <span class="hljs-string">|
          coverage.cobertura.xml
          code-coverage-results.md
</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Upload</span> <span class="hljs-string">Nuget</span> <span class="hljs-string">Artifact</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/upload-artifact@v2.3.0</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">ci-nugets</span>
        <span class="hljs-attr">path:</span> <span class="hljs-string">src/DocFXExample/bin/Release/Taranis.DocFXExample*.nupkg</span>
</code></pre>
<h3 id="heading-documentation-build">Documentation Build</h3>
<p><code>docs-only-build.yml</code> is a manually triggered workflow that uses DocFX to generate and publish our documentation. It consists of a single job which checks out the repo, builds the documentation using the <a target="_blank" href="https://github.com/nikeee/docfx-action">nikeee/docfx-action</a> action and publishes it to GitHub Pages using the <a target="_blank" href="https://github.com/peaceiris/actions-gh-pages">peaceiris/actions-gh-pages</a> action. This workflow is useful for testing the documentation job and updating the site with new articles or customisation changes.</p>
<pre><code class="lang-yml"><span class="hljs-attr">name:</span> <span class="hljs-string">Docs</span> <span class="hljs-string">Only</span> <span class="hljs-string">Build</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">workflow_dispatch:</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">build-docs:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">Docs</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">Documentation</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">nikeee/docfx-action@v1.0.0</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">args:</span> <span class="hljs-string">docfx_project/docfx.json</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">to</span> <span class="hljs-string">GitHub</span> <span class="hljs-string">Pages</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">peaceiris/actions-gh-pages@v3</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">github_token:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.GITHUB_TOKEN</span> <span class="hljs-string">}}</span>
        <span class="hljs-attr">publish_dir:</span> <span class="hljs-string">./docs</span>
</code></pre>
<h3 id="heading-release-build">Release Build</h3>
<p><code>release-build.yml</code> is where we put everything together. Triggered whenever a pre-release or full release is published it consists of three jobs that run sequentially.</p>
<p>The first job <code>build</code> is a slightly simplified version of our CI Build workflow. It checks out the repo, builds and tests the code and uploads the Nuget and test coverage report as an artifact.</p>
<p>The <code>deploy-nuget</code> job only runs if the <code>build</code> job was successful. It gets the artifact from the previous step, publishes the Nuget package and adds the test coverage report to the release notes. Or it would in a real project, since this is just an example I've left out the steps to deploy the package to nuget.org, GitHub Packages or another package feed.</p>
<p>The final job <code>deploy-docs</code> is a copy of our Documentation Build workflow. It will only run if both the <code>build</code> and <code>deploy-nuget</code> jobs are successful. Like the previous workflow it checks out the repo, builds the documentation and publishes it to GitHub Pages.</p>
<pre><code class="lang-yml"><span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">+</span> <span class="hljs-string">Deploy</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">release:</span>
    <span class="hljs-attr">types:</span> [<span class="hljs-string">published</span>]
    <span class="hljs-attr">branches:</span> [<span class="hljs-string">master</span>]
  <span class="hljs-attr">workflow_dispatch:</span>

<span class="hljs-attr">env:</span>
  <span class="hljs-attr">DOTNET_NOLOGO:</span> <span class="hljs-literal">true</span>                     <span class="hljs-comment"># Disable the .NET logo</span>
  <span class="hljs-attr">DOTNET_SKIP_FIRST_TIME_EXPERIENCE:</span> <span class="hljs-literal">true</span> <span class="hljs-comment"># Disable the .NET first time experience to speed up the build</span>
  <span class="hljs-attr">DOTNET_CLI_TELEMETRY_OPTOUT:</span> <span class="hljs-literal">true</span>       <span class="hljs-comment"># Disable sending .NET CLI telemetry</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">build:</span>
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">Release</span> <span class="hljs-string">Build</span>
    <span class="hljs-attr">steps:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Setup</span> <span class="hljs-string">.NET</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/setup-dotnet@v1</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">dotnet-version:</span> <span class="hljs-number">5.0</span><span class="hljs-string">.x</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Restore</span> <span class="hljs-string">Dependencies</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">restore</span> <span class="hljs-string">src/DocFXExample.sln</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">build</span> <span class="hljs-string">src/DocFXExample.sln</span> <span class="hljs-string">--configuration</span> <span class="hljs-string">Release</span> <span class="hljs-string">--no-restore</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Test</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">dotnet</span> <span class="hljs-string">test</span> <span class="hljs-string">src/DocFXExample.sln</span> <span class="hljs-string">--configuration</span> <span class="hljs-string">Release</span> <span class="hljs-string">--no-build</span> <span class="hljs-string">--verbosity</span> <span class="hljs-string">normal</span> <span class="hljs-string">--collect:"XPlat</span> <span class="hljs-string">Code</span> <span class="hljs-string">Coverage"</span> <span class="hljs-string">--results-directory</span> <span class="hljs-string">./coverage</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Copy</span> <span class="hljs-string">Coverage</span> <span class="hljs-string">To</span> <span class="hljs-string">Predictable</span> <span class="hljs-string">Location</span>
      <span class="hljs-attr">run:</span> <span class="hljs-string">cp</span> <span class="hljs-string">coverage/**/coverage.cobertura.xml</span> <span class="hljs-string">coverage.cobertura.xml</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Coverage</span> <span class="hljs-string">Summary</span> <span class="hljs-string">Report</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">irongut/CodeCoverageSummary@v1.2.0</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">filename:</span> <span class="hljs-string">coverage.cobertura.xml</span>
        <span class="hljs-attr">badge:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">format:</span> <span class="hljs-string">'md'</span>
        <span class="hljs-attr">output:</span> <span class="hljs-string">'both'</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Upload</span> <span class="hljs-string">Coverage</span> <span class="hljs-string">Artifact</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/upload-artifact@v2.3.0</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">release-nugets</span>
        <span class="hljs-attr">path:</span> <span class="hljs-string">code-coverage-results.md</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Upload</span> <span class="hljs-string">Nuget</span> <span class="hljs-string">Artifact</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/upload-artifact@v2.3.0</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">release-nugets</span>
        <span class="hljs-attr">path:</span> <span class="hljs-string">src/DocFXExample/bin/Release/Taranis.DocFXExample*.nupkg</span>

  <span class="hljs-attr">deploy-nuget:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">Nuget</span>
    <span class="hljs-attr">needs:</span> [<span class="hljs-string">build</span>]
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Download</span> <span class="hljs-string">Artifacts</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/download-artifact@v2</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">release-nugets</span>

    <span class="hljs-comment"># Here you can deploy your Nuget package to </span>
    <span class="hljs-comment"># nuget.org, GitHub Packages or a private package feed</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Add</span> <span class="hljs-string">Coverage</span> <span class="hljs-string">to</span> <span class="hljs-string">Release</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">irongut/EditRelease@v1.0.0</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">token:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.GITHUB_TOKEN</span> <span class="hljs-string">}}</span>
        <span class="hljs-attr">id:</span> <span class="hljs-string">${{</span> <span class="hljs-string">github.event.release.id</span> <span class="hljs-string">}}</span>
        <span class="hljs-attr">files:</span> <span class="hljs-string">code-coverage-results.md</span>

  <span class="hljs-attr">deploy-docs:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">Docs</span>
    <span class="hljs-attr">needs:</span> [<span class="hljs-string">build</span>, <span class="hljs-string">deploy-nuget</span>]
    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">Documentation</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">nikeee/docfx-action@v1.0.0</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">args:</span> <span class="hljs-string">docfx_project/docfx.json</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">to</span> <span class="hljs-string">GitHub</span> <span class="hljs-string">Pages</span>
      <span class="hljs-attr">uses:</span> <span class="hljs-string">peaceiris/actions-gh-pages@v3</span>
      <span class="hljs-attr">with:</span>
        <span class="hljs-attr">github_token:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.GITHUB_TOKEN</span> <span class="hljs-string">}}</span>
        <span class="hljs-attr">publish_dir:</span> <span class="hljs-string">./docs</span>
</code></pre>
<h2 id="heading-configure-github-pages">Configure GitHub Pages</h2>
<p>After we publish our documentation for the first time, using either the docs only or the release workflow, we need to configure GitHub Pages to show it. On the Settings tab for the repo click Pages in the left menu. Our workflows publish the documentation to a branch called <code>gh-pages</code>. Select the <code>gh-pages</code> branch in the first drop down, <code>/ (root)</code> in the second drop down and click Save. Our documentation site is now available on the url shown, in this case: <a target="_blank" href="https://irongut.github.io/DocFXExample/">https://irongut.github.io/DocFXExample/</a> 🎉</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1639950784437/RTqZ5sE-Y.webp" alt="configure-github-pages.webp" /></p>
<h2 id="heading-links">Links</h2>
<ul>
<li><a target="_blank" href="https://github.com/dotnet/docfx">DocFX</a></li>
<li><a target="_blank" href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/xmldoc/">XML Documentation Comments</a></li>
<li><a target="_blank" href="https://github.com/nikeee/docfx-action">DocFX Action</a></li>
<li><a target="_blank" href="https://github.com/peaceiris/actions-gh-pages">GitHub Pages Action</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[First Steps on MAUI Beach]]></title><description><![CDATA[With the release of Visual Studio 2022 and .NET 6 in early November I decided it was time to set sail for .NET MAUI. This article documents my experiences starting a new cross platform app using .NET MAUI Preview 10. The app is a playground for exper...]]></description><link>https://blog.taranissoftware.com/first-steps-on-maui-beach</link><guid isPermaLink="true">https://blog.taranissoftware.com/first-steps-on-maui-beach</guid><category><![CDATA[dotnet]]></category><category><![CDATA[Xamarin]]></category><category><![CDATA[#dotnet-maui]]></category><category><![CDATA[.NET]]></category><dc:creator><![CDATA[Dave Murray]]></dc:creator><pubDate>Fri, 03 Dec 2021 23:26:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1640920123611/6w2mpfHBB.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>With the release of Visual Studio 2022 and .NET 6 in early November I decided it was time to set sail for .NET MAUI. This article documents my experiences starting a new cross platform app using <a target="_blank" href="https://devblogs.microsoft.com/dotnet/announcing-net-maui-preview-10/">.NET MAUI Preview 10</a>. The app is a playground for experiments with .NET MAUI, called MAUI Beach, and will also appear in future articles. All code is available on GitHub: <a target="_blank" href="https://github.com/irongut/MauiBeach">irongut/MauiBeach</a>.</p>
<h2 id="heading-start-me-up">Start Me Up</h2>
<p>To get the best experience with .NET MAUI Preview 10 requires the preview version of <a target="_blank" href="https://aka.ms/vs2022preview">Visual Studio 2022 (17.1)</a> which can be installed alongside the stable release of Visual Studio 2022 (17.0) or earlier releases. During installation select the <strong>Mobile development with .NET</strong> workload and you should see <strong>.NET MAUI (Preview)</strong> added to the optional mobile components on the right. Click install and you're ready to get started with .NET MAUI!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1638493460533/-qN7ff9bS.png" alt="Visual Studio 2022 Installer" /></p>
<h2 id="heading-first-time">First Time</h2>
<p>Creating a new .NET MAUI app in Visual Studio 2022 is a simple process. Select <strong>MAUI</strong> from the Project Types drop down and you will be presented with three options - <strong>.NET MAUI App</strong>, <strong>.NET MAUI Blazor App</strong> and <strong>.NET MAUI Class Library</strong>. Select <strong>.NET MAUI App</strong> and click <strong>Next</strong>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1638493742977/gP1gK835B.png" alt="VS2022 MAUI New Project" /></p>
<p>You will then be asked for a project name, location and solution name like any other .NET project. Click <strong>Create</strong> and a new cross platform app will be created from the .NET MAUI template.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1638490403321/JfIpb8uUt.png" alt="VS2022 MAUI Project Details" /></p>
<p>Time to build the template app for the first time and check the emulators, etc are working. At this point I ran into a problem, my first build failed with three errors in the build output:</p>
<pre><code class="lang-txt">CS0246 The type or namespace name 'IStartup' could not be found
CS0246 The type or namespace name 'IAppHostBuilder' could not be found
CS0308 The non-generic type 'MauiApplication' cannot be used with type arguments
</code></pre>
<p>A quick search lead me to the cause of the problem. I had previously tried an early preview of Visual Studio 2022 and .NET MAUI which left old templates on my system and the new install hadn't replaced them. I was able to update the templates by running the following command line:</p>
<pre><code class="lang-txt">dotnet new --install  Microsoft.Maui.Templates
</code></pre>
<p>After deleting and recreating my project with the updated template I was able to build.</p>
<h2 id="heading-run-through-the-jungle">Run Through The Jungle</h2>
<p>Having achieved my first build I ran the app on Windows, since that doesn't require an emulator or simulator. Immediately I was greeted by .NET Bot waving happily with a button connected to a counter, success! 🎉</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1638493495482/Rysph8C7D0.png" alt="MAUI First Run on Windows" /></p>
<p>Similarly, running the app on Android went smoothly. Visual Studio 2022 had recognised the Android emulators I use with Visual Studio 2019 so I was able to pick one from the drop down, click run and a few moments later was greeted again by .NET Bot but now running on Android. 👏</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1638493506191/8x0C_F2t5.png" alt="MAUI First Run on Android" /></p>
<p>The iOS build process and the Remote Simulator have been temperamental for me in recent months so I was unsurpried when my first iOS run failed with a deployment error:</p>
<pre><code><span class="hljs-quote">&gt; Need recipe file</span>
</code></pre><p>Falling back on old Xamarin habits, I double checked the Start drop down had the Framework set to <code>net6.0-ios</code> and an iOS Simulator correctly selected then Cleaned and Rebuilt the solution.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1638492402142/liGCfRxwn.png" alt="VS2022 Start Framework Menu" /></p>
<p>That got me past the recipe file error and the Remote Simulator started up... but then failed with the message <code>A fatal error occurred while trying to start the server.</code> 🙁</p>
<p>I closed the simulator and tried again... disconnected and reconnected to my build Mac and tried again... but to no avail. For some reason Visual Studio 2022 couldn't start the simulator. Eventually I used a Xamarin.iOS project in Visual Studio 2019 to start the simulator, switched back to Visual Studio 2022 and ran my MAUI app on the same simulator...</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1638493521220/lvDEeXAcR.png" alt="MAUI First Run on iOS" /></p>
<p>Success! 😁</p>
<p>The recipe file error appears whenever I switch from testing on Windows or Android to testing on iOS. A clean and rebuild usually solves it, sometimes if done twice. 🤨 Fortunately the simulator and Visual Studio 2022 have been behaving themselves since the first time and I haven't seen that problem again. </p>
<h2 id="heading-paint-it-black">Paint It Black</h2>
<p>With everything working on three platforms it was time to make some changes. In <code>Resources/Styles/DefaultTheme.xaml</code> I created a dark theme with a few colours and default styles for <code>Label</code> and <code>Button</code> based on existing styles in <code>App.xaml</code>:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">ResourceDictionary</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://schemas.microsoft.com/dotnet/2021/maui"</span>
                    <span class="hljs-attr">xmlns:x</span>=<span class="hljs-string">"http://schemas.microsoft.com/winfx/2009/xaml"</span> 
                    <span class="hljs-attr">x:Class</span>=<span class="hljs-string">"MauiBeach.Resources.Styles.DefaultTheme"</span>&gt;</span>

    <span class="hljs-comment">&lt;!-- Colour Palette --&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">Color</span> <span class="hljs-attr">x:Key</span>=<span class="hljs-string">"pageBackgroundColor"</span>&gt;</span>#2E3440<span class="hljs-tag">&lt;/<span class="hljs-name">Color</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Color</span> <span class="hljs-attr">x:Key</span>=<span class="hljs-string">"pageForegroundColor"</span>&gt;</span>#434C5E<span class="hljs-tag">&lt;/<span class="hljs-name">Color</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">Color</span> <span class="hljs-attr">x:Key</span>=<span class="hljs-string">"textColor"</span>&gt;</span>#D8DEE9<span class="hljs-tag">&lt;/<span class="hljs-name">Color</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Color</span> <span class="hljs-attr">x:Key</span>=<span class="hljs-string">"strongTextColor"</span>&gt;</span>#ECEFF4<span class="hljs-tag">&lt;/<span class="hljs-name">Color</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Color</span> <span class="hljs-attr">x:Key</span>=<span class="hljs-string">"altTextColor"</span>&gt;</span>White<span class="hljs-tag">&lt;/<span class="hljs-name">Color</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Color</span> <span class="hljs-attr">x:Key</span>=<span class="hljs-string">"darkTextColor"</span>&gt;</span>#2E3440<span class="hljs-tag">&lt;/<span class="hljs-name">Color</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Color</span> <span class="hljs-attr">x:Key</span>=<span class="hljs-string">"lightTextColor"</span>&gt;</span>#D8DEE9<span class="hljs-tag">&lt;/<span class="hljs-name">Color</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Color</span> <span class="hljs-attr">x:Key</span>=<span class="hljs-string">"disabledTextColor"</span>&gt;</span>DarkGray<span class="hljs-tag">&lt;/<span class="hljs-name">Color</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">Color</span> <span class="hljs-attr">x:Key</span>=<span class="hljs-string">"primaryColor"</span>&gt;</span>#BF616A<span class="hljs-tag">&lt;/<span class="hljs-name">Color</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Color</span> <span class="hljs-attr">x:Key</span>=<span class="hljs-string">"secondaryColor"</span>&gt;</span>#D08770<span class="hljs-tag">&lt;/<span class="hljs-name">Color</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Color</span> <span class="hljs-attr">x:Key</span>=<span class="hljs-string">"tertiaryColor"</span>&gt;</span>#A3BE8C<span class="hljs-tag">&lt;/<span class="hljs-name">Color</span>&gt;</span>

    <span class="hljs-comment">&lt;!-- Styles --&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">Style</span> <span class="hljs-attr">TargetType</span>=<span class="hljs-string">"Label"</span>&gt;</span><span class="xml">
        <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"TextColor"</span>
                <span class="hljs-attr">Value</span>=<span class="hljs-string">"{StaticResource textColor}"</span>/&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"FontFamily"</span>
                <span class="hljs-attr">Value</span>=<span class="hljs-string">"OpenSansRegular"</span>/&gt;</span>
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">Style</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">Style</span> <span class="hljs-attr">TargetType</span>=<span class="hljs-string">"Button"</span>&gt;</span><span class="xml">
        <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"TextColor"</span>
                <span class="hljs-attr">Value</span>=<span class="hljs-string">"{StaticResource altTextColor}"</span>/&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"FontFamily"</span>
                <span class="hljs-attr">Value</span>=<span class="hljs-string">"OpenSansRegular"</span>/&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"BackgroundColor"</span>
                <span class="hljs-attr">Value</span>=<span class="hljs-string">"{StaticResource primaryColor}"</span>/&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"CornerRadius"</span>
                <span class="hljs-attr">Value</span>=<span class="hljs-string">"10"</span>/&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"Padding"</span>
                <span class="hljs-attr">Value</span>=<span class="hljs-string">"15, 10"</span>/&gt;</span>
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">Style</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">ResourceDictionary</span>&gt;</span>
</code></pre>
<p>I created <code>DefaultTheme.xaml</code> by creating a new <code>ContentPage</code> and changing the type. This initially gave me an error because the <code>ContentPage</code> template was from Xamarin.Forms. Updating its <code>using</code> statements and xml namespace to MAUI instead of Forms, changing its <strong>Build Action</strong> property to <code>MauiXaml</code> and deleting the value of its <strong>Custom Tool</strong> property fixed the problem.</p>
<p>The existing styles in <code>App.xaml</code> I replaced by loading my theme:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Application.Resources</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ResourceDictionary</span> <span class="hljs-attr">Source</span>=<span class="hljs-string">"Resources/Styles/DefaultTheme.xaml"</span>/&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Application.Resources</span>&gt;</span>
</code></pre>
<p>I also added colours to <code>Platforms/Android/Resources/values/colors.xml</code> and updated the <code>BackgroundColor</code> of <code>MainPage.xaml</code>. I'll eventually expand on this theme, add more themes and a way to switch between them but for this article a simple dark theme will do. Running the app we can see that the theme works:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1638493554334/2KKQX8Lx4.png" alt="MAUI app with dark theme" /></p>
<h2 id="heading-splish-splash">Splish Splash</h2>
<p>Setting up a theme in .NET MAUI works exactly like Xamarin.Forms so now I wanted to try something completely new - a cross platform splash screen. In Xamarin.Forms you need to add a splash screen for each platform in their own project and learn the different ways they all handle splash screens natively. With .NET MAUI you create one splash screen which the build system will magically make work on every platform!</p>
<p>There isn't much documentation for .NET MAUI at the moment but David Ortinau's awesome <a target="_blank" href="https://github.com/davidortinau/WeatherTwentyOne">WeatherTwentyOne</a> app is a great resource to learn from. You've may have seen it demonstrated at <a target="_blank" href="https://www.dotnetconf.net/">.Net Conf 2021</a> or in the <a target="_blank" href="https://devblogs.microsoft.com/dotnet/category/maui/">official .Net MAUI blogs</a>.</p>
<p>The project file <code>*.csproj</code> contains a <code>MauiSplashScreen</code> item which allows you to specify an image and a colour to use for the background of the splash screen. You can even use an <abbr title="Scalable Vector Graphic">SVG</abbr> image so your splash screen will look good at all resolutions and screen sizes. I changed the default image to something suitable and the colour to the primary colour in my theme.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">MauiSplashScreen</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Resources\wave_splash.svg"</span> <span class="hljs-attr">Color</span>=<span class="hljs-string">"#BF616A"</span>/&gt;</span>
</code></pre>
<p>My splash screen worked perfectly on both Android and iOS. (Windows does not show a splash screen)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1638493077416/ZfjZ0oVo2.png" alt="MAUI Beach Splash Screens on Android and iOS" /></p>
<p>The project file also contains a <code>MauiIcon</code> item which allows you to specify a background image, a foreground image and a colour. Again you can use SVG images and the build system will magically create icons for all platforms.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">MauiIcon</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Resources\appicon.svg"</span> <span class="hljs-attr">ForegroundFile</span>=<span class="hljs-string">"Resources\wave_icon.svg"</span> <span class="hljs-attr">Color</span>=<span class="hljs-string">"#BF616A"</span>/&gt;</span>
</code></pre>
<p>This worked quite well on Windows and iOS but unfortunately on Android the icon didn't look the same.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1638572072713/m3AFA5yPs.png" alt="MAUI Beach Icons" /></p>
<p>I spent some time editing the image with Inkscape and a text editor (to remove meta tags the build process doesn't like) but I couldn't get an icon I liked on Android or even one that looked the same as the other platforms. The build process that creates the Android icon has some limitations at the moment and rejects valid SVG tags. I can create a better icon with Android Studio so I could fall back on using a platform specific icon for Android but hopefully the process will be improved in a future preview.</p>
<h2 id="heading-shell-shocked">Shell Shocked</h2>
<p>I want the UI for MAUI Beach to be based around a flyout menu built using Shell. Adding an <code>Application.MainPage</code> element containing a <code>Shell</code> element to <code>App.xaml</code> I defined a flyout header, an item template and three flyout items:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Application.MainPage</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Shell</span> <span class="hljs-attr">FlyoutBehavior</span>=<span class="hljs-string">"Flyout"</span>
           <span class="hljs-attr">FlyoutHeaderBehavior</span>=<span class="hljs-string">"Fixed"</span>
           <span class="hljs-attr">FlyoutVerticalScrollMode</span>=<span class="hljs-string">"Auto"</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">Shell.FlyoutHeader</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Grid</span> <span class="hljs-attr">BackgroundColor</span>=<span class="hljs-string">"{StaticResource primaryColor}"</span>
                  <span class="hljs-attr">HeightRequest</span>=<span class="hljs-string">"220"</span>
                  <span class="hljs-attr">Padding</span>=<span class="hljs-string">"0, 10"</span>&gt;</span>

                <span class="hljs-tag">&lt;<span class="hljs-name">Image</span> <span class="hljs-attr">Source</span>=<span class="hljs-string">"wave_icon.png"</span>
                       <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Center"</span>
                       <span class="hljs-attr">VerticalOptions</span>=<span class="hljs-string">"Center"</span>
                       <span class="hljs-attr">HeightRequest</span>=<span class="hljs-string">"200"</span>
                       <span class="hljs-attr">WidthRequest</span>=<span class="hljs-string">"200"</span>/&gt;</span>

                <span class="hljs-tag">&lt;<span class="hljs-name">Label</span> <span class="hljs-attr">Text</span>=<span class="hljs-string">"MAUI Beach"</span>
                       <span class="hljs-attr">TextColor</span>=<span class="hljs-string">"{StaticResource darkTextColor}"</span>
                       <span class="hljs-attr">FontSize</span>=<span class="hljs-string">"42"</span>
                       <span class="hljs-attr">FontAttributes</span>=<span class="hljs-string">"Bold"</span>
                       <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Center"</span>
                       <span class="hljs-attr">VerticalOptions</span>=<span class="hljs-string">"End"</span>/&gt;</span>

            <span class="hljs-tag">&lt;/<span class="hljs-name">Grid</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Shell.FlyoutHeader</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">Shell.ItemTemplate</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">DataTemplate</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Grid</span> <span class="hljs-attr">ColumnDefinitions</span>=<span class="hljs-string">"0.25*,0.75*"</span>
                      <span class="hljs-attr">Padding</span>=<span class="hljs-string">"0, 10"</span>&gt;</span>

                    <span class="hljs-tag">&lt;<span class="hljs-name">Image</span> <span class="hljs-attr">Source</span>=<span class="hljs-string">"{Binding FlyoutIcon}"</span>
                           <span class="hljs-attr">HeightRequest</span>=<span class="hljs-string">"45"</span>
                           <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Center"</span>/&gt;</span>

                    <span class="hljs-tag">&lt;<span class="hljs-name">Label</span> <span class="hljs-attr">Grid.Column</span>=<span class="hljs-string">"1"</span>
                           <span class="hljs-attr">Text</span>=<span class="hljs-string">"{Binding Title}"</span>
                           <span class="hljs-attr">FontSize</span>=<span class="hljs-string">"Large"</span>
                           <span class="hljs-attr">FontAttributes</span>=<span class="hljs-string">"Bold"</span>
                           <span class="hljs-attr">VerticalOptions</span>=<span class="hljs-string">"Center"</span>/&gt;</span>

                <span class="hljs-tag">&lt;/<span class="hljs-name">Grid</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">DataTemplate</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Shell.ItemTemplate</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">FlyoutItem</span> <span class="hljs-attr">Title</span>=<span class="hljs-string">"Home"</span>
                    <span class="hljs-attr">Icon</span>=<span class="hljs-string">"home.png"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">ShellContent</span> <span class="hljs-attr">ContentTemplate</span>=<span class="hljs-string">"{DataTemplate p:HomePage}"</span>/&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">FlyoutItem</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">FlyoutItem</span> <span class="hljs-attr">Title</span>=<span class="hljs-string">"Settings"</span>
                    <span class="hljs-attr">Icon</span>=<span class="hljs-string">"settings.png"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">ShellContent</span> <span class="hljs-attr">ContentTemplate</span>=<span class="hljs-string">"{DataTemplate p:SettingsPage}"</span>/&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">FlyoutItem</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">FlyoutItem</span> <span class="hljs-attr">Title</span>=<span class="hljs-string">"About"</span>
                    <span class="hljs-attr">Icon</span>=<span class="hljs-string">"about.png"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">ShellContent</span> <span class="hljs-attr">ContentTemplate</span>=<span class="hljs-string">"{DataTemplate p:AboutPage}"</span>/&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">FlyoutItem</span>&gt;</span>

    <span class="hljs-tag">&lt;/<span class="hljs-name">Shell</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Application.MainPage</span>&gt;</span>
</code></pre>
<p>One thing to note here is the names of the images used in my flyout items - <code>wave_icon.png</code>, <code>home.png</code>, <code>settings.png</code> and <code>about.png</code>. You will not find those images in my project but in the <code>Resources/Images</code> folder you will find <code>wave_icon.svg</code>, <code>home.svg</code>, <code>settings.svg</code> and <code>about.svg</code> - so what is going on? .NET MAUI incorporates a tool called <a target="_blank" href="https://github.com/Redth/ResizetizerNT">ResizetizerNT</a> which allows you to add a single SVG or PNG image to your project and at build time it creates all the different sizes of image required by your app in the native resources folders. So <code>about.svg</code> in the <code>Resources/Images</code> folder becomes <code>about.png</code> in my XAML. ResizetizerNT is also invloved in the creation of app icons during the build process.</p>
<p>In <code>App.xaml.cs</code> I removed the line that created the default MainPage and deleted the files. At this point I needed to create my three pages and when creating a new <code>ContentPage</code> I ran into the same Xamarin.Forms template issue as earlier with the <code>ResourceDictionary</code>. Again updating their <code>using</code> statements and xml namespaces, changing <strong>Build Action</strong> to <code>MauiXaml</code> and deleting <strong>Custom Tool</strong> fixed the problem.</p>
<p>My initial pages are almost identical and very basic for now:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">ContentPage</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://schemas.microsoft.com/dotnet/2021/maui"</span>
             <span class="hljs-attr">xmlns:x</span>=<span class="hljs-string">"http://schemas.microsoft.com/winfx/2009/xaml"</span>
             <span class="hljs-attr">x:Class</span>=<span class="hljs-string">"MauiBeach.Pages.HomePage"</span>
             <span class="hljs-attr">Title</span>=<span class="hljs-string">"Home"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ContentPage.Content</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">StackLayout</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Label</span> <span class="hljs-attr">Text</span>=<span class="hljs-string">"Home Page"</span>
                   <span class="hljs-attr">FontSize</span>=<span class="hljs-string">"Large"</span>
                   <span class="hljs-attr">VerticalOptions</span>=<span class="hljs-string">"CenterAndExpand"</span> 
                   <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"CenterAndExpand"</span>/&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">StackLayout</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">ContentPage.Content</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ContentPage</span>&gt;</span>
</code></pre>
<p>After checking that worked I could start to customise my UI more. To my theme in <code>Resources/Styles/DefaultTheme.xaml</code> I added default styles for <code>Page</code> and <code>NavigationPage</code> and styles for the Shell flyout menu and menu items.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Style</span> <span class="hljs-attr">TargetType</span>=<span class="hljs-string">"Page"</span>
       <span class="hljs-attr">ApplyToDerivedTypes</span>=<span class="hljs-string">"True"</span>&gt;</span><span class="xml">
    <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"BackgroundColor"</span>
            <span class="hljs-attr">Value</span>=<span class="hljs-string">"{StaticResource pageBackgroundColor}"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"Padding"</span>
            <span class="hljs-attr">Value</span>=<span class="hljs-string">"20, 10"</span>/&gt;</span>
</span><span class="hljs-tag">&lt;/<span class="hljs-name">Style</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">Style</span> <span class="hljs-attr">TargetType</span>=<span class="hljs-string">"NavigationPage"</span>
        <span class="hljs-attr">ApplyToDerivedTypes</span>=<span class="hljs-string">"True"</span>&gt;</span><span class="xml">
    <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"BackgroundColor"</span>
            <span class="hljs-attr">Value</span>=<span class="hljs-string">"{StaticResource pageBackgroundColor}"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"BarBackgroundColor"</span>
            <span class="hljs-attr">Value</span>=<span class="hljs-string">"{StaticResource primaryColor}"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"BarTextColor"</span>
            <span class="hljs-attr">Value</span>=<span class="hljs-string">"{StaticResource altTextColor}"</span>/&gt;</span>
</span><span class="hljs-tag">&lt;/<span class="hljs-name">Style</span>&gt;</span>

<span class="hljs-comment">&lt;!-- Shell --&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">Style</span> <span class="hljs-attr">x:Key</span>=<span class="hljs-string">"BaseStyle"</span>
       <span class="hljs-attr">TargetType</span>=<span class="hljs-string">"Element"</span>&gt;</span><span class="xml">
    <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"Shell.FlyoutBackground"</span>
            <span class="hljs-attr">Value</span>=<span class="hljs-string">"{StaticResource pageBackgroundColor}"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"Shell.FlyoutBackgroundColor"</span>
            <span class="hljs-attr">Value</span>=<span class="hljs-string">"{StaticResource pageBackgroundColor}"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"Shell.BackgroundColor"</span>
            <span class="hljs-attr">Value</span>=<span class="hljs-string">"{StaticResource primaryColor}"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"Shell.ForegroundColor"</span>
            <span class="hljs-attr">Value</span>=<span class="hljs-string">"{StaticResource altTextColor}"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"Shell.TitleColor"</span>
            <span class="hljs-attr">Value</span>=<span class="hljs-string">"{StaticResource altTextColor}"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"Shell.DisabledColor"</span>
            <span class="hljs-attr">Value</span>=<span class="hljs-string">"{StaticResource disabledTextColor}"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"Shell.UnselectedColor"</span>
            <span class="hljs-attr">Value</span>=<span class="hljs-string">"{StaticResource disabledTextColor}"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"Shell.TabBarBackgroundColor"</span>
            <span class="hljs-attr">Value</span>=<span class="hljs-string">"{StaticResource primaryColor}"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"Shell.TabBarTitleColor"</span>
            <span class="hljs-attr">Value</span>=<span class="hljs-string">"{StaticResource altTextColor}"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"Shell.TabBarUnselectedColor"</span>
            <span class="hljs-attr">Value</span>=<span class="hljs-string">"{StaticResource disabledTextColor}"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"Shell.NavBarHasShadow"</span>
            <span class="hljs-attr">Value</span>=<span class="hljs-string">"True"</span>/&gt;</span>
</span><span class="hljs-tag">&lt;/<span class="hljs-name">Style</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">Style</span> <span class="hljs-attr">BasedOn</span>=<span class="hljs-string">"{StaticResource BaseStyle}"</span>
       <span class="hljs-attr">ApplyToDerivedTypes</span>=<span class="hljs-string">"True"</span>
       <span class="hljs-attr">TargetType</span>=<span class="hljs-string">"FlyoutItem"</span>/&gt;</span><span class="xml">

<span class="hljs-tag">&lt;<span class="hljs-name">Style</span> <span class="hljs-attr">x:Key</span>=<span class="hljs-string">"shellItem"</span>
       <span class="hljs-attr">TargetType</span>=<span class="hljs-string">"Grid"</span>&gt;</span><span class="xml">
    <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"VisualStateManager.VisualStateGroups"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">VisualStateGroupList</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">VisualStateGroup</span> <span class="hljs-attr">x:Name</span>=<span class="hljs-string">"CommonStates"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">VisualState</span> <span class="hljs-attr">x:Name</span>=<span class="hljs-string">"Normal"</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">VisualState.Setters</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"BackgroundColor"</span>
                                <span class="hljs-attr">Value</span>=<span class="hljs-string">"{StaticResource pageBackgroundColor}"</span>/&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">VisualState.Setters</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">VisualState</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">VisualState</span> <span class="hljs-attr">x:Name</span>=<span class="hljs-string">"Selected"</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">VisualState.Setters</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">Setter</span> <span class="hljs-attr">Property</span>=<span class="hljs-string">"BackgroundColor"</span>
                                <span class="hljs-attr">Value</span>=<span class="hljs-string">"{StaticResource secondaryColor}"</span>/&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">VisualState.Setters</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">VisualState</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">VisualStateGroup</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">VisualStateGroupList</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Setter</span>&gt;</span>
</span></span><span class="hljs-tag">&lt;/<span class="hljs-name">Style</span>&gt;</span>
</code></pre>
<p>I also expanded on my Shell definition in <code>App.xaml</code> by adding a flyout footer and a gradient backdrop:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Shell.FlyoutFooter</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Grid</span> <span class="hljs-attr">BackgroundColor</span>=<span class="hljs-string">"{StaticResource primaryColor}"</span>
          <span class="hljs-attr">HeightRequest</span>=<span class="hljs-string">"32"</span>
          <span class="hljs-attr">Padding</span>=<span class="hljs-string">"0, 5, 0, 5"</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">Label</span> <span class="hljs-attr">Text</span>=<span class="hljs-string">"Copyright © 2021 Taranis Software"</span>
               <span class="hljs-attr">TextColor</span>=<span class="hljs-string">"{StaticResource altTextColor}"</span>
               <span class="hljs-attr">FontSize</span>=<span class="hljs-string">"Small"</span>
               <span class="hljs-attr">HorizontalOptions</span>=<span class="hljs-string">"Center"</span>/&gt;</span>

    <span class="hljs-tag">&lt;/<span class="hljs-name">Grid</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Shell.FlyoutFooter</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">Shell.FlyoutBackdrop</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">LinearGradientBrush</span> <span class="hljs-attr">StartPoint</span>=<span class="hljs-string">"0,0"</span>
                         <span class="hljs-attr">EndPoint</span>=<span class="hljs-string">"0,1"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">GradientStop</span> <span class="hljs-attr">Color</span>=<span class="hljs-string">"{StaticResource primaryColor}"</span>
                      <span class="hljs-attr">Offset</span>=<span class="hljs-string">"0.3"</span>/&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">GradientStop</span> <span class="hljs-attr">Color</span>=<span class="hljs-string">"{StaticResource secondaryColor}"</span>
                      <span class="hljs-attr">Offset</span>=<span class="hljs-string">"1.0"</span>/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">LinearGradientBrush</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Shell.FlyoutBackdrop</span>&gt;</span>
</code></pre>
<p>As you can see this mostly worked but the <code>FlyoutBackground</code> / <code>FlyoutBackgroundColor</code> properties are not correctly set on any platform, instead these areas are white or black depending on the device's current system theme. Looking at the .NET MAUI repo this has been fixed already for Android and iOS and work is in progress for Windows so a future preview version will fix my flyout menu.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1638495664862/U7Y79glXG.png" alt="MAUI Beach Shell Flyout Menu" /></p>
<h2 id="heading-the-end">The End</h2>
<p>I now have the basic framework set up for <a target="_blank" href="https://github.com/irongut/MauiBeach">MAUI Beach</a> - a cross platform app built using .NET MAUI. My app has a theme, a splash screen, an icon and a flyout menu with three empty pages.</p>
<p>I have run into a few issues, which should be expected when working with pre-release code, but none of them were insurmountable and most things worked in a similar way to Xamarin.Forms. .NET MAUI Preview 11 is due soon and should fix some of my problems. Overall I think .NET MAUI is shaping up nicely and I'm looking forward to a great experience when it goes GA in Q2 2022.</p>
<p> </p>
<p> </p>
<p>Cover image includes a background vector created by freepik from <a target="_blank" href="https://www.freepik.com/vectors/background">www.freepik.com</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Code Coverage Summary v1.2.0 Released!]]></title><description><![CDATA[I'm pleased to announce another update to my GitHub Action Code Coverage Summary. CodeCoverageSummary v1.2.0 brings two new features:

🗃 supports multiple coverage files
🧭 supports hiding the Branch Rate and Complexity metrics in the output

Perfor...]]></description><link>https://blog.taranissoftware.com/code-coverage-summary-v120-released</link><guid isPermaLink="true">https://blog.taranissoftware.com/code-coverage-summary-v120-released</guid><category><![CDATA[GitHub]]></category><category><![CDATA[github-actions]]></category><category><![CDATA[Testing]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[Dave Murray]]></dc:creator><pubDate>Thu, 25 Nov 2021 19:21:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1640979136031/t7n6rOGHh.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I'm pleased to announce another update to my GitHub Action <a target="_blank" href="https://github.com/marketplace/actions/code-coverage-summary">Code Coverage Summary</a>. CodeCoverageSummary v1.2.0 brings two new features:</p>
<ul>
<li>🗃 supports multiple coverage files</li>
<li>🧭 supports hiding the Branch Rate and Complexity metrics in the output</li>
</ul>
<p>Performance has also been improved thanks to an upgrade to .Net 6 and improvements to the Docker image.</p>
<p>Code Coverage Summary reads Cobertura format coverage files from your test suite and outputs a text or markdown summary that can be posted as a Pull Request comment or included in Release Notes to give you an immediate insight into the health of your code without using a third-party site.</p>
<p>Designed for use with <a target="_blank" href="https://github.com/coverlet-coverage/coverlet">Coverlet</a> and <a target="_blank" href="https://github.com/gcovr/gcovr">gcovr</a>, Code Coverage Summary should work with any test framework that outputs coverage in Cobertura format.</p>
<ul>
<li>🛍 <a target="_blank" href="https://github.com/marketplace/actions/code-coverage-summary">GitHub Marketplace</a></li>
<li>👨‍💻 <a target="_blank" href="https://github.com/irongut/CodeCoverageSummary">Repository</a></li>
</ul>
<p> </p>
<p>Blue vector created by <a target="_blank" href="https://www.freepik.com/vectors/blue">vectorjuice - www.freepik.com</a></p>
]]></content:encoded></item></channel></rss>