My First .Net MAUI App

Kevin Williams
5 min readMay 31, 2022

--

Original article published at kevinwilliams.dev.

Last week at Microsoft Build, they announced that .Net MAUI was now GA, although you still need the preview version of Visual Studio to create and build the projects. Regardless, I thought it would be a good time to try it out, and decided to do so with a simple encryption helper program.

The program will be able to encrypt or decrypt text with a given RSA key, as well as generate a public / private key pair. Future versions may have other capabilities, but I figured this would be a good place to start.

Install Visual Studio

I’ve been working with Visual Studio 2022, but in order to work with .Net MAUI apps, the preview version is needed. It can be downloaded here.

Create the Project

Once the preview of Visual Studio has been installed, start it up and create a new project. For this, I decided to use a .Net MAUI Blazor App. Prior to running the app in Visual Studio, we need to have Developer Mode turned on in Windows. You can find this by going to Settings -> For Developers -> Developer Mode

Once the project has been installed, testing it out on Windows is as simple as starting the debugger. Prior to running the Android emulator, though, you have to accept the user licenses and install the sdks.

After installation has completed, you can start the debugger, which opens the Android Device Manager and allows you to create an emulator.

Add MudBlazor

Since I’m building a Blazor Hybrid app and want to use material design components, I decided to utilize MudBlazor. Following the installation steps for a MAUI app is the same as a regular Blazor App. The only notable differences are that services are registered inside of MauiProgram.cs, and wwwroot/css/site.css changed to wwwroot/css/app.css

Inside of the Shared directory, I created a MainLayoutBase.cs file for the C# code, and updated the MainLayout.razor file as follows:

<!-- MainLayout.razor -->
@inherits MainLayoutBase
<MudThemeProvider />
<MudDialogProvider />
<MudSnackbarProvider />
<MudLayout>
<MudAppBar>
<MudIconButton
Icon="@Icons.Material.Filled.Menu"
Color="Color.Inherit"
Edge="Edge.Start"
OnClick="@((e) => ToggleDrawer())"
/>
</MudAppBar>
<MudDrawer @bind-Open="@DrawerOpen" ClipMode="DrawerClipMode.Always">
<MudNavMenu>
<MudNavLink Href="/" Match="NavLinkMatch.All">Key Generator</MudNavLink>
<MudNavLink Href="/encryptor" Match="NavLinkMatch.All"
>RSA Encryptor</MudNavLink
>
</MudNavMenu>
</MudDrawer>
<MudMainContent>
<div style="margin: 16px">@Body</div>
</MudMainContent>
</MudLayout>
// MainLayoutBase.cs
using Microsoft.AspNetCore.Components;
namespace EncryptionHelper.Shared
{
public class MainLayoutBase : LayoutComponentBase
{
protected bool DrawerOpen { get; set; } = false;
protected void ToggleDrawer()
{
DrawerOpen = !DrawerOpen;
}
}
}

The main takeaways are that there’s an AppBar at the top with an icon that, when clicked, will open or close a drawer component. The drawer has two links that will take you to the Key Generator or Encryptor pages.

Add Functionality

Now that the main layout has been setup, we can add the actual encryption functions.

Key Generator

There’s not much to implement for creating keys. We just need to create an RSACryptoServiceProvider and export the keys as XML so they can be displayed in the text boxes

// IndexBase.cs
using Microsoft.AspNetCore.Components;
using System.Security.Cryptography;
namespace EncryptionHelper.Pages.Index
{
public class IndexBase : ComponentBase
{
protected string PublicKey { get; set; } = "";
protected string PrivateKey { get; set; } = "";
protected void GenerateKeyPair()
{
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
PublicKey = rsa.ToXmlString(false);
PrivateKey = rsa.ToXmlString(true);
}
}
protected async void Copy(string value)
{
await Clipboard.SetTextAsync(value);
}
}
}

Encryption & Decryption

When it comes to encrypting and decrypting, there’s slightly more logic as we have to handle errors. The idea, though, is to take a text string and key, then display the encrypted/decrypted version in a separate “result” textfield.

// EncryptorBase.cs
using EncryptionHelper.Utilities;
using Microsoft.AspNetCore.Components;
using MudBlazor;
using System.Security.Cryptography;
using System.Text;
namespace EncryptionHelper.Pages.Encryptor
{
public class EncryptorBase : ComponentBase
{
[Inject]
private ISnackbar _snackbar { get; set; }
protected string Text { get; set; } = "";
protected string Key { get; set; } = "";
protected string Result { get; set; } = "";
protected async void Copy()
{
await Clipboard.SetTextAsync(Result);
}
protected void Decrypt()
{
try
{
if (string.IsNullOrEmpty(Text) || string.IsNullOrEmpty(Key))
throw new Exception("Text and Key are required!");
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
RSAUtility.FromXmlString(rsa, Key);
byte[] encryptedKeyBytes = Convert.FromBase64String(Text);
byte[] decryptedKeyBytes = rsa.Decrypt(encryptedKeyBytes, false);
Result = Encoding.ASCII.GetString(decryptedKeyBytes);
}
}
catch (Exception ex)
{
Result = "";
_snackbar.Add(ex.Message, Severity.Error);
}
}
protected void Encrypt()
{
try
{
if (string.IsNullOrEmpty(Text) || string.IsNullOrEmpty(Key))
throw new Exception("Text and Key are required!");
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
RSAUtility.FromXmlString(rsa, Key);
byte[] plainKeyBytes = Encoding.ASCII.GetBytes(Text);
byte[] encryptedKeyBytes = rsa.Encrypt(plainKeyBytes, false);
Result = Convert.ToBase64String(encryptedKeyBytes);
}
}
catch (Exception ex)
{
Result = "";
_snackbar.Add(ex.Message, Severity.Error);
}
}
}
}

Build the UI

Since this is a small app and we’re using MudBlazor, the UI is pretty simple to setup.

Index

First we have the index, which is our key generation page. There’s a button to generate a public / private key pair and two text fields that have a copy functionality

<!-- Index.razor -->
@inherits IndexBase @page "/"
<div style="display: flex; flex-direction: row-reverse">
<MudButton
Variant="Variant.Filled"
Color="Color.Primary"
OnClick="@GenerateKeyPair"
>
Generate KeyPair
</MudButton>
</div>
<MudTextField
T="string"
ReadOnly="true"
Label="Public Key"
Variant="Variant.Outlined"
Text="@PublicKey"
Margin="Margin.Normal"
Lines="5"
Adornment="Adornment.End"
AdornmentIcon="@Icons.Material.Filled.CopyAll"
OnAdornmentClick="@(() => Copy(PublicKey))"
AdornmentAriaLabel="Copy"
/>
<MudTextField
T="string"
ReadOnly="true"
Label="Private Key"
Variant="Variant.Outlined"
Text="@PrivateKey"
Margin="Margin.Normal"
Lines="5"
Adornment="Adornment.End"
AdornmentIcon="@Icons.Material.Filled.CopyAll"
OnAdornmentClick="@(() => Copy(PrivateKey))"
AdornmentAriaLabel="Copy"
/>

Encryptor

The actual encryption / decryption takes place in the encryptor component, which contains 3 text fields and an encrypt/decrypt button.

<!-- Encryptor.razor -->
@inherits EncryptorBase @page "/encryptor"
<MudTextField
T="string"
Label="Text"
Variant="Variant.Outlined"
Margin="Margin.Normal"
@bind-Value="@Text"
Lines="5"
/>
<MudTextField
T="string"
Label="Key"
HelperText="XML-Formatted Key"
Variant="Variant.Outlined"
Margin="Margin.Normal"
@bind-Value="@Key"
Lines="5"
/>
<MudTextField
T="string"
Label="Result"
Variant="Variant.Outlined"
Margin="Margin.Normal"
@bind-Value="@Result"
ReadOnly="true"
Adornment="Adornment.End"
AdornmentIcon="@Icons.Material.Filled.CopyAll"
OnAdornmentClick="@(() => Copy())"
AdornmentAriaLabel="Copy"
Lines="5"
/>
<div style="display: flex; justify-content: space-around; margin-top: 16px;">
<MudButton Variant="Variant.Filled" Color="Color.Success" OnClick="Encrypt">
Encrypt
</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Error" OnClick="Decrypt">
Decrypt
</MudButton>
</div>

Update the Assets

With MAUI apps, there is a shared Resources folder that contains all of our files. Updating the spash screen and icons is as simple as updating the files in the appropriate directory, and making a small change to the csproj.

Project
- Resources
- AppIcon
- Fonts
- Images
- Raw
- Splash

Splash Page

The splash page is stored in the Project/Resources/Splash directory. I placed a png in that directory, and updated the following line in my csproj

<ItemGroup>  <!-- Splash Screen -->
<MauiSplashScreen Include="Resources\Splash\logo.png" BaseSize="280,60" Color="#000000"/>
<ItemGroup>

App Icon

For the app icon, just update the file in Project/Resources/AppIcon and change the csproj again

<ItemGroup>        <!-- App Icon -->
<MauiIcon Include="Resources\AppIcon\lock.svg" Color="#ffffff" />
</ItemGroup>

Test

Now that the app’s been created, we can test it out.

Windows

Android

Publish

Unfortunately, publishing the app to the Windows store or Google Play store is outside the scope of what I was looking to do for this project & blog. That being said, feel free to check out the code on GitHub, and keep an eye out for a future article going in depth regarding the publishing process.

--

--

Kevin Williams

A software engineer with an interest in fitness, agriculture, and electronics.