An online markdown blog and knowledge repository.
This markdown file exists to capture additional learnings about .NET MAUI.
For starters, notes will be made while following MSFT Learn modules.
MAUI assists with architecture between platforms:
MAUI supports creating consistent interfaces on varying hardware platforms, all within a unified project.
A common layer pulls all the API differences together, called a Base Common Library, starting with .NET 6.
These come with .NET MAUI:
Note: WinUI3 is provided by the Mono project.
The new Dot Net Libraries sit on top of BCL.
BCL sits on top of Mono Runtime and WinRT runtime.
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.
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.
MAUI always generates native code for the target platform.
Optimizations are performed during the build.
Requirements, App creation, App structure, and Startup.
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.
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.
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:
Windows
for the App. Default is a single window but additional can be launched.Shell Class:
Flyout
: Parent object to FlyoutItem
s.TabBar
: Bottom bar used for bottom-of-screen navigation. Contains Tab
items representing each navigation point.ShellContent
: Represents the ContentPage objects for each Tab.Pages Class:
Shell
Class.ContentPage
displays contents (most common page type).TabbedPage
: Used for tab navigation.FlyoutPage
: Enables Master/Detail style presentation. Lists Items for display in a child view.Views Class:
ContentView
: The default View.ScrollView
: Adds a scroller to move contens in the View.CarouselView
: Scrollable view using 'swipe' to move through content.CollectionView
: Retreive data from named data source, presenting each using _format template_s.Note: Create a Screen
using the Page
Class.
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
.
Controls and Layouts, and modifying Control properties.
Views contain a single Control (button, label, etc).
Layouts define rules for how Controls are arranged in a View.
StackLayouts:
Other Layouts:
Can do this in C# code:
CounterBtn.Text = $"Clicked {count} times";
A View can be definied purely in C# if wanted:
ContentPage
.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.
Setup Debugging for Android. Other modes are possible but are not yet documented here.
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.
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>
</...>
InitializeComponent
is not necessary if XAML is not used to define a page layout and controls.Most importantly: Separate the definition of the UI from the logic of the app.
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"
... />
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.
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.
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.
x:Name
s.x:Name
represents a POCO private field for interacting with the element.x:Name
represents a path for other XAML elements to access the element.InitializeComponent()
must be executed in order for x:Name
fields to become available.Required event handler signature components by .NET convention:
object
representing what triggered the event, and an EventArgs
parameter containing args passed by the sender.private
.Some issues:
Wiring Event Handlers in Code:
Counter.Clicked += OnCounterClicked;
private void OnCounterClicked(object sender, EventArgs e) {...}
Counter.Clicked -= OnCounterClicked;
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:
object
type.IServiceProvider
interface.ProvideValue
.Drawbacks:
Double
value.Benefits:
Apply the Markup Extension to a Control:
ProvideValue
member are defined in."{mycode:NameOfExtensionMethod}"
.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 (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:
"{x:Static Member=mycode:MainPage.NameOfExtensionMethod}"
Fine-tune your UI for each platform.
Layout management is provided in MAUI.
DevinceInfo
Class:
DeviceInfo.Platform
returns a string such as "Android", "iOS", "WinUI", or "macOS".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.
Enables detecting the runtime platform within XAML!
<On Platform="..." Value="..." />
blocks.type
parameter.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
.
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>
Specify View Size:
UI Controls are the parent class to Pages
, Layouts
, and Views
.
Pages:
ContentPage
: A single View containing content.FlyoutPage
: Contain FlyoutItem
s and detail pages.NavigationPage
: A hierarchical navigation concept.TabbedPage
: A series of Pages, loaded by TabBar
instances, located at the top (default) or bottom of a Page.Layouts:
View
class.Microsoft.Maui.Controls
apps.Views:
Note the common parent at VisualElement
:
Note: RelativeLayout is similar to FlexLayout, but FlexLayout should be used due to better performance.
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.
Size units are different on some platforms:
MAUI does not use device-specific values at all:
double
.Rendered Size of a View:
Left, right, or center of the screen?
Left, right, or center of the parent element?
View base class has VerticalOptions and HorizontalOptions properties:
LayoutOptions
: A struct with properties <LayoutAlignment>Alignment
and <bool>Expands
.LayoutAlignmnet
Type is an enumeration
of Start, Center, End, and Fill.LayoutAlignmnet
controls how child view is positions within the box, given inheritance from it's parent Layout Panel.Expands
property allows requesting extra space if available (from Xamarin.Forms
). Is obsolete in MAUI, use Grid
instead!<StackLayout x:Name="stack" Orientation="Horizontal">
Note: Recall VerticalStackLayout and HorizontalStackLayout handle StackLayout Orientation
property automatically.
Expands
deprecated property replaced by StartAndExpand
, CenterAndExpand
, EndAndExpand
, or FillAndExpand
:
AndExpand
option should be replaced with the MAUI versions.Optimized StackLayouts:
VerticalStackLayout
and HorizontalStackLayout
.StackLayout
plus its options.Spacing
property setting.RowDefinition
and ColumnDefinition
to set the height of each row and the width of each column.GridLength
, with its own properties GridUnitType
and Value
.GridUnitTypeStar, same effect, both ways:
new GridLength(2, GridUnitType.Star);
<RowDefinition Height="2*" />
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>
Attached Properties are defined in one class but set on objects of other Types.
<BoxView Grid.Row="1" Grid.Column="2" ... />
to tell the BoxView to stay in Row 1, Column 2 of the Grid.Span BoxView across 2 columns (cols must be previously defined): <BoxView Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" ... />
Define style information in one place and have MAUI "look them up" so they are applied consistently across your APp.
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
.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.
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.
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.
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>
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.
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}">
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>
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}" />
Apply the same Style to all Controls on a page (or App):
x:Key
identifier.TargetType
is used instead, to apply Style to all Controls of the same Type.Limitations:
TargetType
must be an exact match.Style.ApplyToDerivedTypes
attribute and set to True
in the Style definition.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:
x:Key
KVP name and TargetType
so the styles will be applied.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" />
Use TargetType
set to 'VisualElement', a base Class for several Controls (e.g. Button and Label).
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>
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
:
Resources
property.Application
Class defines its own Resources property.ResourceDictionary
of Resources.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>
Searches the tree for the first instance of the key and then uses that value.
x:Key
definitions can be duplicated across layers without issue (technically).
Jon's Advice: Push Style setters to the highest possible Resource Dictionary.
Flyout:
Tab:
Stack:
Composed of:
Usually used to allow navigation between different pages of an App.
Class: FlyoutItem
Shell
class.TabBar
class.ShellContent
property defines the page that is displayed when the flyout... flies out.Shell
page.ShellItem
class instance (Note: ShellItem
implements IVisualTreeElement
).FlyoutItem
inherits from BaseShellItem
vs FlyoutPage
which inherits from VisualElement
(an important distinction).Implicit Conversion simplifies implementation:
Shell
can only contain FlyoutItem
or TabBar
objects, both of which can only contain Tab
objects, which can only contain ShellContent
objects.Shell
class can contain ShellContent
objects directly.Example code snippets from [MSFT Learn]:
<Shell xmlns="..."
>
<ShellContent Title="Cats"
Icon="cat.png"
ContentTemplate="{DataTemplate views:CatsPage}" />
<!-- additional ShellContent instances here -->
</Shell>
MenuItem
object:
Button
Class.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.
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.
Implement Tab navigation using TabBar
:
Page
.Tab
s with icons + short words for navigating the App.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:
<TabBar>
within <Shell>
.<Tab>
with a Title and perhaps an Icon property.<ShellContent>
object with a ContentTemplate
property pointing to a DataTemplate property referencing the Page
that should be displayed.Shell class defines navigation properties:
BackButtonBehavior
of type BackButtonBehavior
is an attached property. Defines behavior of the Back button.CurrentItem
of type ShellItem
: The currently selected Item.CurrentPage
of type Page
: The currently selected Page.CurrentState
of type ShellNavigationState
: Current navigation state of the Shell.Current
of type Shell
: Type-casted alias for Application.Current.MainPage
.This is a good choice when there are any number of target content pages to navigate to:
Navigation requires a URI to navigate TO:
Shell
visual hierarchy.//route/page?queryParameters
.Note: Shell
is a component of Navigation, including Page and Query Params.
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
.
For any content that is not in the Visual Hierarchy, register the route then navigate to it using the registered name value:
Routing.RegisterRoute
: Supply description and a typeof()
with the pagename as the type argument.await Shell.Current.GoToAsync("myCustomPageIdentifier");
: Navigates to the registered route.This includes navigating between pages within tabbed pages.
Backward Nav:
..
as the target definition.Passing Data:
string
-based query params.?
symbol and ID=value
syntax.Retreiving Data:
QueryPropertyAttribe
decorator when defining the body page class.// to move to antoher page using Navigation:
Navigation.PushAsync(target);
// to move to another page using Shell:
Shell.Current.GoToAsync(target);
HttpClient
.Connections could be WiFi or Cellular so your App needs to not fail when connectivity changes:
Responding to network-related issues:
Use Connectivity
class.
NetworkAccess
property, which has an enumeration called NetworkAccess
.ConnectivityChanged
is also exposedNetworkAcess
property is available via the Current
property.Current
property.NetworkAccess enumeration:
ConstrainedInternet
Internet
Local
None
: No access to the internet!Unknown
Example code from [MSFT Learn]:
if (Connectivity.Current.NetowrkAccess == NetworkAccess.None)
{
...
}
Event ConnectivityChanged
is triggerred automatically when network connectivity changes.
ConnectivityChangedEventArgs
: Passed to the event handler.IsConnected
: Boolean, member of ConnectivityChangedEventArgs
.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;
}
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:
System.Net.Http
.CRUD to REST translation:
CREATE <==> POST READ <==> GET UPDATE <==> PUT DELETE <==> DELETE
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);
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
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.
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
}
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.
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:
Opt-out:
Info.plist
file (a Dictionary) called NSAppTransportSecurity
.Info.plist
called NSExceptionDomains
.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>
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:
Resources/xml
and name it network_security_config.xml
.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:
domain-config
child element cleartextTrafficPermitted
to false in network_security_config.xml
.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>
Use iOS Simulator and Android Emulator to consume ASP.NET Core web services running locally over HTTP.
NSAllowsLocalNetworking
.10.0.2.2
, an alias for 127.0.0.1
, and set up the network security configuration for using the loopback address.For example the GET
request would be sent to localhost by configuring http://10.0.2.2:/api/myEndpoint
.
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/";
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
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:
ContainsKey
-> booleanRemove
-> removes a keyClear
-> all Preference data is removedAppropriate 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);
...
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:
AppDataDirectory
. Use to store App-generated data.Environment.SpecialFolder.MyDocuments
. Store user-generated data (stored in direct response to a user action).When to use a database:
Database is a file that must be stored.
AppDataDirectory
.SQLite-net
.A Caveat: Android and iOS native SQLite implementation support C/C++ but not .NET directly.
Note: There are other C# "wrappers" for SQLite.
Requires:
sqlite-net-pcl
SQLitePCLRaw.provider.dynamic_cdecl
packageSee 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.
Use SQLiteConnection
object.
using System.IO;
using SQLite;
string filename = Path.Combine(FileSystem.AppDataDirectory, "my-sqlite.db");
SQLiteConnection connection = new SQLiteConnection(filename);
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'.
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:
Table
method.public List<Bibs> GetAllBibs()
{
List<Bib> bibs = connection.Table<Bib>.ToList();
return bibs;
}
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();
}
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;
}
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:
SQLiteAsyncConnection
class: var connection = new SQLiteAsyncConnection(databasePath);
.await connection.CreateTableAsync<Item>();
.DropTableAsync(class)
: Drop Table, by correlated Class.GetAsync(primaryKey)
: Gets record in table based on class, matching PK.InsertAsync(newItem)
: Insert new record.UpdateAsync(updatedItem)
: Updates existing record.DeleteAsync(primaryKey)
: Remove record that matches table, PK.QueryAsync()
: Direct SQL query and returns an object.ExecuteAsync()
: Direct SQL query returns count of affected rows.ExecuteScalarAsync()
: Direct SQL query returns single result.ToListAsync()
: Converts Table Query result to a List<T>
type.Requirements:
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
Question: Does this mean .NET MAUI can target Linux devices like RPi or full x86/AMD architectures?
Code segments copied from various Modules at Microsoft Learn.
SQLite project home.
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