Jon Rumsey

An online markdown blog and knowledge repository.


Project maintained by nojronatron Hosted on GitHub Pages — Theme by mattgraham

DotNET MAUI Learnings Log

This markdown file exists to capture additional learnings about .NET MAUI.

For starters, notes will be made while following MSFT Learn modules.

Table of Contents

Build Mobile and Desktop Apps Training Notes

MAUI assists with architecture between platforms:

MAUI supports creating consistent interfaces on varying hardware platforms, all within a unified project.

MAUI Tech Stack

A common layer pulls all the API differences together, called a Base Common Library, starting with .NET 6.

New Dot Nets

These come with .NET MAUI:

Note: WinUI3 is provided by the Mono project.

DotNET BCL 6

The new Dot Net Libraries sit on top of BCL.

BCL sits on top of Mono Runtime and WinRT runtime.

Mono and WinRT

These parallel APIs are layered on top of Android, macOS, iOS, and Windows platform APIs.

DotNET MAUI ties these components together in the stack to get cross-platform build-and-run capability.

XAML

Use XAML to describe the UI and Controls, just like in WPF.

Semantic Properties are used to support Accessibility throughout the UI.

Note: UI can be assembled using pure code to enable dynamic responsiveness on the fly.

Native Code

MAUI always generates native code for the target platform.

Optimizations are performed during the build.

DotNET MAUI UI Provisions

Installing MAUI

Requirements, App creation, App structure, and Startup.

MAUI Install Requirements

Visual Studio 2022 v17.3 or newer and '.NET MAUI Multi-platform App UI Development' workload.

Note: There is a preview version of Visual Studio that supports MAUI development on Mac.

Creating a MAUI App

Three Project Templates are available with .NET MAUI:

Note: Create projects as close to a drive's root folder as possible because directory structure lengths can get very long especially for Android projects.

MAUI App Structure and Startup

Note: SemanticScreenReader is a MAUI class with a static member Announce(string) that tells a screen reader what to say. Apply this to event handlers in code-behind.

App Class:

Shell Class:

Pages Class:

Views Class:

Note: Create a Screen using the Page Class.

Project File Noteworthy Elements

Initial PropertyGroup specifies platform frameworks to target, app title, AppID, version, display, and supported OSes. These can be ammended as needed.

ItemGroup following that allows specifying image and color for splash screen (app loading visual). Set default locations for fonts, images, and other assets used by the app. See Resources Folder for storing the actual items referenced. These should be REGISTERED using MauiApp.CreateBuilder() in MauiProgram.cs.

MAUI UI

Controls and Layouts, and modifying Control properties.

Controls and Layouts

Views contain a single Control (button, label, etc).

Layouts define rules for how Controls are arranged in a View.

StackLayouts:

Other Layouts:

Modifying Control Properties

Can do this in C# code:

CounterBtn.Text = $"Clicked {count} times";

A View can be definied purely in C# if wanted:

  1. Create a partial class and inherit from ContentPage.
  2. Add fields as needed. Controls can be declared a Class member!!
  3. In the constructor, initialize the Views and Layouts, and assign initial values to Fields (like a Label, Button text, etc).
  4. Add Event Handler members to execute when Controls are clicked, etc.

Note: It will be necessary to take the instantiated Controls and add them to View instances, updating layout options such as LayoutOptions.Center and so-on.

Margin and Padding are properties of Controls that the various Layout classes will respect.

VerticalStackLayout and HorizontalStackLayout also have a Spacing property that affects the Margin of the child items within the layout.

Debug Mode

Setup Debugging for Android. Other modes are possible but are not yet documented here.

Debug Android MAUI App

Tools -> Android -> Android Device Manager: Create a new phone (emulator) and API Level (Google API implementation version).

Debug (Green Arrow) drop-down -> Andoid Emulator -> Pick the correct emulator.

Note: Enable Hyper-V on the workstation to improve emulator performance.

Note2: A dedicated graphics processor and a Mid- to High-end processor will be necessary to run the Android Emulator without crashing or severe lagging.

Android Manifest Updates

Make updates to the Android Manifest to allow using native implementation such as dialing the phone.

MAUI abstracts these away into namespaces like Microsoft.Maui.ApplicationModel.Communication.

Drill-in to the Platforms folder to get to the manifest file for each platform.

If the default Manifest view does not show the needed item(s), use Open With dialog to use Automatic Editor Selector (XML) to make the edits.

For example, to use the Phone, create an intent element with action and data:

<...>
  <queries>
    <intent>
      <action android:name="android.intent.action.DIAL" />
      <data android:scheme="tel" />
    </intent>
  </queries>
</...>

Create a UI in a DotNET MAUI App By Using XAML

XAML over Code

Most importantly: Separate the definition of the UI from the logic of the app.

Types and Properties

XAML Parser takes XAML and creates objects given the elements with set properties from the XAML definitions.

Most MAUI Controls are located in Microsoft.Maui.Controls namespace.

Most Common Types are located in Microsoft.Maui.Dependencies and Microsoft.Maui.Extensions packages (NuGet).

Utility Types are defined in Microsoft.Maui namespace. Example: Thickness Utility Type.

Graphics Types are defined in Microsoft.Maui.Graphics namespace. Example: Color.

XAML Elements, .NET Types, and Custom Types can all be rolled into a custom App with XAML.

Instantiating types in XAML and C# are done differently, but the result is the same:

// instantiate a Label object
<Label TextColor="Azure" />
// instantiate a Label object
var label = new Label {
  TextColor = Color.FromArgb(255, 0, 127, 255)
};

Bringing MAUI Types and other Types into scope can be done in both XAML and C#:

<!-- import namespaces -->
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" />
<!-- Namespaces are aliases for the assemblies that bring all the things into scope
     first one is .NET MAUI Controls
     second one is intrinsic types (strings, numerics, properties, etc) -->
// import a namespace
using Microsoft.Maui;
// same result!

The result of x:Class="..." is a reference to the named class within quotes.

<!-- bring in a custom type called Utils -->
<ContentPage ...
             xmlns:mycode="clr-namespace:Utils"
             ... />

Type Converters

Use Type Converters to convert XML Attributes from string to the correct Type.

MAUI has Type Converters for most built-in classes, and they are used automatically.

Custom Type Converters can be written to handle custom cases! Once implemented, associate the conversion to your custom Type to make it usable in XAML.

Complex Type Assignment

Some Types are "complex" and need specific setup within XAML.

The example MSFT provides shows a Label with is necessary Attributes (Text, TextColor, etc) and an inner <Label.GestureRecognizers> (a complex Type) and an instantiation Property Element form of <TapGestureRecognizer NumberOfTapsRequired="2" />.

So using this format Element.Property is "Property Element Form", and adds the complex Type to the parent element.

Default Content

A property that is sometimes set in MAUI Types.

The example given shows a <VerticalStackLayout> parent element with a <Label Text="..." /> child element that does NOT require <VerticalStackLayout.Children> Property Element form.

Event Handling in XAML

Event Signatures

Required event handler signature components by .NET convention:

Separation of Concerns

Some issues:

Wiring Event Handlers in Code:

XAML Markup Extensions

Most XAML definitions will be settled at compile time.

Run-time determined variables are sorted using XAML Markup Extensions.

Create a XAML Markup Extension - Implement the ProvideValue method as defined by IServiceProvider interface:

Drawbacks:

Benefits:

Apply the Markup Extension to a Control:

Note: MAUI XAML recognizes the naming convention with the suffix Extension as a XAML Markup Extension, so the namespace import and alias does not need to include Extension during assignment in the XAML header.

StaticExtension Class

StaticExtension (aka 'Static') is a short-hand way of creating and using a XAML Markup Extension.

[ContentProperty("Member")]
public class StaticExtension : IMarkupExtension
{
  public string Member {get; set;}
  public object ProvideValue (IServiceProvider serviceProvider)
  {
    // code returns a type that inherits from Object
  }
}

Utilize this extension the same way as before:

Platform Specific Values in XAML

Fine-tune your UI for each platform.

Layout management is provided in MAUI.

DevinceInfo Class:

For situations where applying an attribute (like Padding) to solve a layout problem on one device won't solve the problem on all devices, necessarily.

Use a turnary clause to provide the correct values depending on the device type return by DeviceInfo.Platform.

Example provided by [MSFT Learn]:

MyStackLayout.Padding =
  DeviceInfo.Platform == DevicePlatform.iOS // struct iOS returns a string
    ? new Thickness(30, 60, 30, 30) // shift content down for iOS only
    : new Thickness(30); // default margin for non-iOS devices

Note: Since the tweak is for the UI, it is usually preferred to make these types of changes in the UI layer, i.e. XAML. The next sub-section addresses this.

OnPlatform Markup Extension

Enables detecting the runtime platform within XAML!

Example provided by [MSFT Learn]:

<VerticalStackLayout>
  <VerticalStackLayout.Padding>
    <OnPlatform x:TypeArguments="Thickness">
      <On Platform="iOS" Value="30,60,30,30" />
      <On Platform="Android" Value="30" />
      <On Platform="WinUI" Value="30" />
    </OnPlatform>
  </VerticalStackLayout.Padding>
  <!-- XAML for other controls go here -->
</VerticalStackLayout>

Use this extension to define other attribute values such as x:TypeArguments="Color", etc.

Key Takeaway: Customize the UI for each platform using OnPlatform.

OnPlatform Reduced Syntax

Simplify by using a Default Value for any non-match [MSFT Learn example]:

<VerticalStackLayout Padding="{OnPlatform iOS='30,60,30,30', Default='30'}">
  <!-- XAML for other controls go here -->
</VerticalStackLayout>

Customize XAML Pages Layout

Specify View Size:

Views, Pages, and Layouts

UI Controls are the parent class to Pages, Layouts, and Views.

Pages:

Layouts:

Views:

Note the common parent at VisualElement:

Layout Panel

Note: RelativeLayout is similar to FlexLayout, but FlexLayout should be used due to better performance.

View Size Configuration

Affects how the parent element sizes itself around its content.

The Request portion means at run time the App will make a final decision on size based on available space for the element and its contents.

Platform Sizing Considerations

Size units are different on some platforms:

MAUI does not use device-specific values at all:

Rendered Size of a View:

Positioning Considerations

Left, right, or center of the screen?

Left, right, or center of the parent element?

View base class has VerticalOptions and HorizontalOptions properties:

Layout Properties

Note: Recall VerticalStackLayout and HorizontalStackLayout handle StackLayout Orientation property automatically.

Expands deprecated property replaced by StartAndExpand, CenterAndExpand, EndAndExpand, or FillAndExpand:

Optimized StackLayouts:

Grid Layout Panel

GridUnitTypeStar, same effect, both ways:

Note: The default size for any Row or Column is 1*.

XAML definitions can be shortened like this:

<Grid RowDefinitions="*, *, *" ColumnDefinitions="*, *">
  <!-- row and grid controls -->
</Grid>

Grid Layout Attached Properties

Attached Properties are defined in one class but set on objects of other Types.

Grid Spanning

Span BoxView across 2 columns (cols must be previously defined): <BoxView Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" ... />

Static and Dynamic Resources

Define style information in one place and have MAUI "look them up" so they are applied consistently across your APp.

XAML Resources

Symbolic constants are defined in one place and referenced everywhere it is needed.

Style and OnPlatform instances can be set as Resources!

Define a XAML Resource [MSFT Learn example]:

<Color x:Key="PageControlTextColor">Blue</Color>

Store Resources in a Resource Dictionary

XAML Resource Dictionary

.NET MAUI has a specific class for this: ResourceDictionary.

<ContentPage.Resources> <!-- this is REQUIRED -->
  <ResourceDictionary> <!-- this is actually OPTIONAL -->
    <Color x:Key="PageControlTextColor">Azure</Color>
    <!-- more Resources defined here -->
  </ReseourceDictionary>
</ContentPage.Resources>

Ways to use Resource Dictionaries:

Note: The order of entries within a Resource Dictionary are important, especially in cases where Style BasedOn and Style overrides are used.

Apply a Static Resource

Markup Extension looks up resources in a resource dictionary, by Key.

Static Resources are only looked up once. Changing the resource during Run Time will not have an effect.

Code courtesy of [MSFT Learn]:

<Label TextColor="{StaticResource PageControlTextColor}" ... />
<!-- Key is 'PageControlTextColor' and the value returned will be placed in the TextColor attribute at Run Time -->

Note: Static Resources will throw a Runtime Exception if the Key is not found.

Intrinsic Types

Remember these in C#? Well, they exist in XAML, too!

For example, initializing a Double variable in XAML is this simple: <x:Double x:Key="MyDouble">12.3</x:Double>.

This applies to FONTs as Strings, Font Size as Double, and other intrinsic Types like Boolean, Int64, Byte, etc.

Platform-Specific Values for a Resource

Use OnPlatform to get slightly different UI Control Styles and settings based on the Platform at build time.

Code courtesy of [MSFT Learn]:

<ContentPage.Resources>
  <OnPlatform x:Key="textColor" x:TypeArguments="Color">
    <On Platform="iOS" Value="Silver" />
    <On Platform="Android" Value="Green" />
    <On Platform="WinUI" Value="Azure" />
    <On Platform="MacCatalyst" Value="Pink" />
  </OnPlatform>
</ContentPage.Resources>

Dynamic Resources

Another Markup Extension!

Places to use Dynamic Resources instead of Static:

Dynamic Resources are scanned dynamically at RunTime, whereas Static Resources are scanned just once, and that scanned-in value is used for the lifetime of the App.

Imposes a small resource penalty on the Application, vs Static Resources.

Update Dynamic Resources at Run Time

Add new, Remove or Update existing Resources!

Code-behind allows making these changes [MSFT Learn example]:

this.Resources["PanelBackgroundColor"] = Colors.Green;

Reference Dynamic Resources similarly to Static Resources in XAML [MSFT Learn example]:

<StackLayout BackgroundColor="{DynamicResource PanelBackgroundColor}">

Style Setters

Using StaticResource and DynamicResource can start to clutter XAML.

Defining new, or editing existing Controls takes a fair amount of XAML code and can make a Page XAML code hard to read and maintain.

Use a Setter to help solve this problem:

Note: All Properties on Controls in .NET MAUI whose name suffix is Property is a Bindable Property and is necessary for use in Setters. For example, TextColorProperty is a BindableProperty and can be used directly in a Setter. Other Properties of Controls that are not already bindable properties must be handled differently. Thankfully, most of the time MAUI's built-in Control Properties already have a bindable property definition.

Styles are a collection of Setters targeting a specific Control Type.

Define Styles as resources within a Resource Dictionary object.

Example from [MSFT Learn documentation]

<ContentPage.Resources>
  <Style x:Key="MyButtonStyle" TargetType="Button">
    <!-- Setter collection here -->
  </Style>
</ContentPage.Resources>

Applying a Style

Attach a Style through the Style property of a Control ([MSFT Learn example code]):

<Button Text="OK" Style="{StaticResource MyButtonStyle}" />
<Button Text="Cancel" Style="{DynamicResource CancelButtonStyle}" />

Implicit Styles for Multiple Controls

Apply the same Style to all Controls on a page (or App):

Limitations:

Override a Style

Use this technique when a closely-matched Control exists, and just override a few of the Setters.

Explicitly set Style settings are applied after the base Control styles, so they will override.

Steps:

  1. All buttons except one need to have the same Style applied.
  2. Create the Style using Setters and assign an x:Key KVP name and TargetType so the styles will be applied.
  3. For the button that needs Style overrides, only update the properties that need to be changed.

For example, all buttons are configured to have a Green background and Grray foreground, except the 'Exit' button which needs to be Black background and White foreground:

<!-- Buttons will have Green background, Gray text, and a medium-thin border with curved corners -->
<Style x:Key="MyButtonStyle" TargetType="Button">
  <Setter Property="BackgroundColor" Value="Green" />
  <Setter Property="BorderRadius" Value="12" />
  <Setter Property="BorderWidth" Value="4" />
  <Setter Property="TextColor" Value="Gray" />
</Style>

<!-- set Exit button overrides TextColor and BackgroundColor just for itself -->
<Button Text="Exit"
        Style="{StaticResource MyButtonStyle}"
        TextColor="White"
        BackgroundColor="Black" />

Target an Ancestor

Use TargetType set to 'VisualElement', a base Class for several Controls (e.g. Button and Label).

BasedOn Style Inheritance

Many Style definitions will repeat lots of the same Setters, such as BackgroundColor.

Example [MSFT Learn] code:

<!-- base Style to base another off of -->
<Style x:Key="MyVisualElementStyle" TargetType="VisualElement">
  <Setter Property="BackgroundColor" Value="Blue" />
</Style>

<!-- altered Style based on MyVisualElementStyle, simplifying the code -->
<Style x:Key="MyButtonStyle" TargetType="Button" BasedOn="{StaticResource MyVisualElementStyle}">
  <Setter Property="BorderColor" Value="Navy" />
  <Setter Property="BorderWidth" Value="5" />
</Style>

Application-Wide Resources

When defining Resources within a specific Page of an App, those Resources are only available to that same page.

Avoiding repeated code throughout your application, especially as it gets bigger and more complex, will require an App-Wide Resource Dictionary - a single place to set UI Control Styles.

About VisualElement:

Diagram of MAUI Application Organization:

=================================================
|           Application Layer                   |
- - - - - - - - - - - - - - - - - - - - - - - - -
|     Page            |   |   Page              |
- - - - - - - - - - - -   - - - - - - - - - - - -
|     Layout          |   |   Layout            |
- - - - - - - - - - - -   - - - - - - - - - - - -
| View | View | View  |   | View | View | View  |
=======================   =======================

Therefore, define Application-level Resources at the Application layer through the Application Class like this example from [MSFT Learn]:

<Application.Resources>
  <Color x:Key="MyTextcolor">Azure</Color>
</Application.Resources>

How MAUI Searches for a Resource

Searches the tree for the first instance of the key and then uses that value.

  1. Search the VisualElement instance ResourceDictionary.
  2. Search the parent Control ResourceDictionary e.g. Layout.
  3. Search the parent Control ResourceDictionary e.g. Page.
  4. Search the Application Class ResourceDictionary.
  5. If Key is not found, App will use default values for Styling.

x:Key definitions can be duplicated across layers without issue (technically).

Jon's Advice: Push Style setters to the highest possible Resource Dictionary.

App Navigation in MAUI

Flyout:

Tab:

Stack:

Flyout Navigation

Composed of:

Usually used to allow navigation between different pages of an App.

Class: FlyoutItem

Implicit Conversion simplifies implementation:

Example code snippets from [MSFT Learn]:

<Shell xmlns="..."
  >
  <ShellContent Title="Cats"
                Icon="cat.png"
                ContentTemplate="{DataTemplate views:CatsPage}" />
  <!-- additional ShellContent instances here -->
</Shell>

Flyout Menu Items

MenuItem object:

Content that optionally appears at top (or bottom) of Flyout.

Appearance defined by Shell.FlyoutHeader (or Shell.FlyoutFooter) bindable property.

Code snippet from [MSFT Learn]:

<Shell ...>
  <Shell.FlyoutHeader>
    <Grid>
      <Image Source="..." />
    </Grid>
  </Shell.FlyoutHeader>
</Shell>

Note: <Shell.FlyoutFooter> uses similar code.

Tab Navigation

Navigate a user on a permanent tab strip at the top or bottom of their device screen.

Note: If more than 4-5 items in a tab strip, consider using a different navigation scheme.

TabBar Object

Implement Tab navigation using TabBar:

TabBar must be instantiated as a child to the Shell class.

Add Tab objects to the TabBar.

Within a Tab object, a ShellContent object should be set to a ContentPage object.

Create a Tabbed Page:

  1. Define an xmlns path to the folder that will contain the Tab Pages.
  2. Define <TabBar> within <Shell>.
  3. Instantiate a <Tab> with a Title and perhaps an Icon property.
  4. Set a <ShellContent> object with a ContentTemplate property pointing to a DataTemplate property referencing the Page that should be displayed.

Stack Navigation

Shell class defines navigation properties:

This is a good choice when there are any number of target content pages to navigate to:

Stack Nav Routes

Navigation requires a URI to navigate TO:

Note: Shell is a component of Navigation, including Page and Query Params.

Register Routes

Routes can be defined on these Types by using their Route properties:

Example Route property from [MSFT Learn]:

<Shell ...>
  <FlyoutItem ...
    Route="astronomy">
      <ShellContent ...
        Route="moonphase" />
      <!-- etc -->
  </FlyoutItem>
</Shell>

Absolute route URI to moonphase is //astronomy/moonphase.

Register Detail Routes

For any content that is not in the Visual Hierarchy, register the route then navigate to it using the registered name value:

This includes navigating between pages within tabbed pages.

Backward Nav:

Passing Data:

Retreiving Data:

// to move to antoher page using Navigation:
Navigation.PushAsync(target);

// to move to another page using Shell:
Shell.Current.GoToAsync(target);

Consuming REST Web Services

Handling Network Connectivity Overview

Connections could be WiFi or Cellular so your App needs to not fail when connectivity changes:

Responding to network-related issues:

Detecting Network Connectivity

Use Connectivity class.

NetworkAccess enumeration:

Example code from [MSFT Learn]:

if (Connectivity.Current.NetowrkAccess == NetworkAccess.None)
{
  ...
}

Event ConnectivityChanged is triggerred automatically when network connectivity changes.

Portions of this code are from [MSFT Learn]:

// register the event handler
Connectivity.Current.ConnectivityChanged += Connectivity_ConnectivityChanged;

// event handler
void Connectivity_ConnectivityChanged(object sender, ConnectivityChangedEventArgs e)
{
  // leverage the EventArgs inheritor built-in property to get connectivity status
  bool stillConnected = e.IsConnected;
}

Consume a REST Service

REST architecture uses well-defined verbs representing operations in requests.

REST verbs enable CRUD operations (Create, Read, Update, and Delete).

REST service respond to requests in a standardized way.

The HttpClient class:

CRUD to REST translation:

CREATE <==> POST READ <==> GET UPDATE <==> PUT DELETE <==> DELETE

Create a Resource

Create a new Resource with code from [MSFT Learn]:

// this is an async method code block
HttpClient client = new HttpClient();
// request contains http verb 'Post' and url 'url'
HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Post, url);
// serialize the 'parp' variable into JSON for sending to REST service
message.Content = JsonContent.Create<Part>(part);
HttpResponseMessage response = await client.SendAsync(message);

Read a Resource

Convenience methods are included with HttpClient that shorten code in an HTTP Request:

Example code portions from [MSFT Learn]:

HttpClient client = new HttpClient();
// response will be returned as a string, which could be XML, JSON, or some other formatting
string text = await client.GetStringAsync(url);

// if response is going to be JSON then use the following instead
client.DefaultRequestHeaders.Accept.Add(new TypeWithQualityHeaderValue("application/json"));
// only response message body is returned within an HttpResponsemessage object instance

Update a Resource

Use HttpRequestMessage initialized with PUT verb (code snippets from [MSFT Learn]):

HttpClient client = new HttpClient();
HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Put, url);
message.Content = JsonContent.Create<Part>(part);
HttpResponseMessage response = await client.SendAsync(message);

Idempotency: The same operation will always have the same result. Submitting multiple PUT requests in a row with the same data will perform the exact same action as the 1st request. Submitting multiple POST requests with the same data will continue to create new items at each request. Server-response codes and messages might be different in subsequent identical requests regardless of the idempotency of the request type.

Handle Response from Request

Always expect a response message.

Redirection codes:

Verify the status code in a response message, from [MSFT Learn]:

static readonly HttpClient client = new HttpClient();

...
// call asynchronous network methods in a try-catch block to handle exceptions
try
{
  // initiate the HttpRequest
  HttpResponseMessage response = await client.SendAsync(msg);
  response.EnsureSuccessStatusCode(); // check that code IS WITHIN the 2xx range otherwise throw HttpRequestException
  string responseBOdy = await response.Content.ReadAsStringAsync();
  // handle the response
  ...
}
catch(HttpRequestException hrex) {
  // handle status codes from `e.StatusCode` that indicate the error condition
}

Platform-Specific Features

MAUI Templates map HttpClient to a layer that handles native networking stacks of each platform.

Varying platforms have different security layer implementations, but MAUI manages that for you -- to a point.

App Transport Security on iOS

ATS requires Http Apps to use TLS 1.2 or above.

Apps that don't use ATS will be denied network access.

How to work with ATS:

  1. Change endpoint to adhere to ATS policy, or
  2. Opt-out of using ATS altogether.

Opt-out:

  1. Add new key to Info.plist file (a Dictionary) called NSAppTransportSecurity.
  2. Add a new key to Info.plist called NSExceptionDomains.
  3. Add children to it for each endpoint your App will taget.

Editing Info.plist can be done using Visual Studio's generic PList Editor, or by editing the XML directly.

Targeting Keys look like this example from [MS Learn]:

<key>NSAppTransportSecurity</key>
<dict>
   <key>NSExceptionDomains</key>
      <dict>
      <key>dotnet.microsoft.com</key>
      <dict>
        <key>NSExceptionMinimumTLSVersion</key>
        <string>TLSv1.0</string>
        <key>NSExceptionAllowsInsecureHTTPLoads</key>
        <true/>
      </dict>
   </dict>
</dict>

It is helpful to use the OPT-OUT method for locally debugging a service on the dev machine like this example from [MS Learn]:

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsLocalNetworking</key>
    <true/>
</dict>

Optionally, ATS can be disabled completely using code like this example from [MS Learn]:

<key>NSAppTransportSecurity</key>
<dict>
   <key>NSAllowsArbitraryLoads</key>
   <true/>
</dict>

Configure Android Network Security

API Level 28 (Android 9) introduced a policy that disables non-HTTP clear text traffic.

This policy blocks downloading images or files from servers that are not configured for HTTPS, or there are no development certificates installed during dev or debug time.

To permit clear text traffic:

  1. Create a new AML file in Resources/xml and name it network_security_config.xml.
  2. Add element network-security-config with a domain-config child element.

Permitting clear text traffic look like this example from [MS Learn]:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
  <domain-config cleartextTrafficPermitted="true">
    <domain includeSubdomains="true">10.0.2.2</domain> <!-- Debug port -->
    <domain includeSubdomains="true">microsoft.com</domain>
  </domain-config>
</network-security-config>

Securing all traffic on Android device regardless of target API level:

  1. Set domain-config child element cleartextTrafficPermitted to false in network_security_config.xml.
  2. Enable Android to use the xml by setting the application element according to the examle code from [MSF Learn], below.
<?xml version="1.0" encoding="utf-8"?>
<manifest>
    <application android:networkSecurityConfig="@xml/network_security_config" ...></application>
</manifest>

Debug Apps Locally

Use iOS Simulator and Android Emulator to consume ASP.NET Core web services running locally over HTTP.

For example the GET request would be sent to localhost by configuring http://10.0.2.2:/api/myEndpoint.

Detect the Operating System

Use DeviceInfo class to determine OS the App is running on.

Set the BaseAddress for calling the API Endpoint to a different value based on the detected OS value.

Example code snippet from [MSFT Learn]:

public static string BaseAddress = DeviceInfo.Platform == DevicePlatform.Android
  ? "http://10.0.2.2:5000"
  : "http://localhost:5000"
public static string ItemsUrl = $"{BaseAddress}/api/items/";

Storing Data Locally

Mobile apps often store data locally for performance reasons, same is true for .NET MAUI.

There are several storage options in .NET MAUI applications:

Preferences

Preferences can be used directly to set and get KVPs:

string dataToStore = "data to be stored locally.";
Preferences.Set("dataKeyAlpha", dataToStore);
var savedPreference = Preferences.Get("dataKeyAlpha", false);

Preferences also has methods:

File System

Appropriate for:

Use System.Text.Json and System.IO:

using System.Text.Json;
using System.IO;

List<Item> items = ...;

// serialize and save
string fileName = "data-store.json";
var serializedData = JsonSerializer.Serialize(Items);
File.WriteAllText(fileName, serializedData);

// read and deserialize
var rawData = File.ReadAllText(fileName);
items = JsonSerializer.Deserialize<List<Item>>(rawData);
...

App Sandbox

Private area within the MAUI App for writing and reading files.

Only the Platform OS and the current User can access the App Sandbox.

AppDataDirectory is a static property of FileSystem that returns a string representing the sandbox path.

Apple Sandbox Guidelines:

SQLite Database Storage

When to use a database:

Database is a file that must be stored.

A Caveat: Android and iOS native SQLite implementation support C/C++ but not .NET directly.

Note: There are other C# "wrappers" for SQLite.

SQLite-net Features

Requires:

See the SQLList Project Home and Wiki links in the Resources and References section.

Note: Skipping SQLitePCLRaw.provider.dynamic_cdecl package will ensure an initialization error in the SQLite Connection constructor. See Type Initializer for SQLite.SLiteConnection threw an exception on StackOverflow.

SQLite Connect

Use SQLiteConnection object.

using System.IO;
using SQLite;
string filename = Path.Combine(FileSystem.AppDataDirectory, "my-sqlite.db");
SQLiteConnection connection = new SQLiteConnection(filename);

SQLite Create a Table

Define a C Sharp Class and use CreateTable on the SQLiteConnection class to generate a table.

[Table("myData")]
public class MyData
{
  // PK is usually numeric
  [PrimaryKey, AutoIncrement, Column("_id")]
  public int Id { get; set; }

  [MaxLength(100), Unique]
  public int bibNumber { get; set; }

  // more class-code...
}

Table Attributes:

Create the table:

connection.CreateTable<MyData>();

Note: If table exists and schema is different, CreateTable<T>() attempts to update the schema to the properties of Type 'T'.

SQLite Read and Write Operations

Insert:

public int AddNewBib(Bib bib)
{
  // todo: verify bib is not null?
  int result = connection.Insert(bib);
  // returns the number of row(s) that were updated
  return result;
}

Read All Rows in a Table:

public List<Bibs> GetAllBibs()
{
  List<Bib> bibs = connection.Table<Bib>.ToList();
  return bibs;
}

SQLite Queries Using LINQ

Selecting data within a table can be done using LINQ queries. Supports:

Use extension method syntax to extend the LINQ query to a fully functional query:

public List<Bib> GetByAction(string action)
{
  var bibs = from b in connection.Table<Bib>()
             where b.Action == action
             select b;
  return bibs.ToList();
}

SQLite Update and Delete

Use the SQLiteConnection object.

The Update() method updates a single record in a Table:

public int UpdateItem(Item item)
{
  int result = 0;
  result = connection.Update(item);
  return result;
}

The Delete(key) method removes row(s) from a Table:

public int DeleteItem(int itemID)
{
  int result = 0;
  result = connection.Delete<Item>(itemID);
  return result;
}

SQLite Asynchronous Operation

Use Async operations to ensure the UI remains responsive to the user.

Asynchronous features execute queries on a separate thread, not the UI thread.

All operations are Task-based to support background usage:

Android Emulator

Requirements:

  1. Reboot, enter UEFI/BIOS and Enable Trusted Execution Environment, save and exit.
  2. Add the Hyper-V Windows Feature (Apps -> Add Feature), reboot the computer.
  3. Load the Android Device Manager and click Start on the Device to start.
  4. Once booted, click Debug (Android phone model and API Level) to load the app into the Emulator.
  5. Stuff works instead of crashing or opening message box warnings.

About Tizen

Tizen is an open-source Linux distribution that supports IoT, TV, Mobile, and Wearable device profiles.

Tizen supports headed and headless products.

Driven by The Linux Foundation

Tizen Org

Question: Does this mean .NET MAUI can target Linux devices like RPi or full x86/AMD architectures?

Resources and References

Learn.Microsoft.com.

Code segments copied from various Modules at Microsoft Learn.

File System Helpers.

SQLite project home.

SQLite Wiki.

Julian Ewers-Peters has a Blog that is occasionally updated with CommunityToolkit and DetNET MAUI technical details.

Return to Conted Index

Return to Root README