MAUI–Decoupled applications using MvpVm

GitHub Project: Dotnet-maui-workshop (section 7)

Note: For this fictional application everything can be reused between Monkey and Inventory with the exception of the Population in the detail view - inventory does not have a population.

MAUI and MvpVm

The Model-View-ViewModel (Mvvm) pattern is a great reusable widget pattern,  but when it comes to building a framework/application you’ll want to use a more extensible pattern such as the Model-View-Presenter-ViewModel (MvpVm) pattern.

To understand why MvpVm is preferred, you’ll need to know some history, you’ll find that Mvvm has already been a traveled path (with MVC) requiring a new pattern to meet its short comings; thus it evolved to MVP.

Model View Controller (MVC)

The Model View Controller pattern essentially became obsolete with the emergence of smart controls; the "Controller" in MVC was replaced with controls that automatically updated the model via data binding (reference Twisting the Triad articles below)

It wasn't only the lack of a "Controller" that sparked the need for a new pattern, it was also the issues that MVC presented.   These were documented in the Taligent paper and Twisting the Triad (below). 

Model View Presenter (MVP)

A key problem, that I trust all of us have encountered with the MVVM pattern, is the inability to easily reuse a ViewModel; components become tightly coupled.   This, along with other issues, are not new; in Martin Fowlers article on GUI Architectures he addresses the Presentation Model - which is essentially MVVM.  Per GUI Architectures, the Presentation Model and Application Model are evolved MVC patterns with access to the View being the distinguishing difference.  MVP solved the issues presented by these MVC patterns (read Twisting the Triad and the Taligent paper links above for more info).

Decoupled applications

Microsoft Practices and Patterns Architecture Guide 2.0 covers, in great detail, best practices and patterns for application design.   The emphasis is on MVP as it lends itself to decoupled applications, i.e., the views and view models can truly be reusable.  AppArchGuide2.0.pdf (3.09 mb)



MVP as a pattern is the tried and true pattern for applications, established by early architects and emphasized in Microsoft's best practices and patterns.  However, the emergence of WPF (and now MAUI) didn't quite fit as-is.  The pattern still applies, the only difference is there is now a ViewModel that the View observes.  As a result I coined the phrase MvpVm in my MSDN article (click image below).

In MvpVm the View declares the presenter, and the presenter is responsible for populating the view-model and managing the business logic layer.   I should emphasize that the presentation layer never accesses the data access layer directly; it only is aware of the business logic layer which in turn is only aware of data layer interfaces.  Because of these interfaces and IOC, the application can easily swap out implementation as business logic dictates.  For example, in my GitHub  Dotnet-maui-workshop (section 7) project, I swap out the offline and online implementation of IDataService; an event driven process that automatically updates if internet is disconnected.  This is demonstrated by the animated gif at the top of this article.

Decoupled applications can be difficult to follow if you are not familiar with the practices and patterns of the application framework in question.   A knowledge of Inversion of control (IOC) aka Dependency Injection (DI) is a prerequisite.   I will cover the basics of this applications pattern below.

Tip: you can look at the MvpVm pattern as a collection of puzzle pieces (reusable components) that the presenter is responsible for managing.  It has the primary responsibility for updating the view-model, you'll find that via the PresenterBase class, it invokes commands that can update the view-model as well.   This permits view-models and commands to remain decoupled (and reusable).

Note: currently I have not established a database.   When I do there will be entity business logic classes that will manage those entities.  


A picture says a thousand words

Below I show the two view models that are shared between the Monkey and Inventory views.   At a glance you can see why they can be easily reused.  For the DetailViewModel you'll note there is an IsPopulationVisible flag, the Inventory view does not require the population information; this flag drives its visibility (I point to this in the animated gif at the top of this article).

If we look at our MonkeyPresenter below, used by the MainPage, you'll see that the SetSupportedButtons() method defines the commands (buttons) that we want to display (pointed to below, order counts).  Ideally, we don't want to use "magic strings", however since I do not have a reference to the "GotoInventoryCommand" (which resides in a separate DLL) I have to use one.  The framework displays the buttons as shown in bottom left pane.

Each command represents a clear separation of concerns with the logic being fully encapsulated within it.  Where the buttons "can be" specified in the SetSupportedButtons() method, it is not required, just convenient. In the Inventory view you'll note (in animated gif above) that there is a button with a question mark - this button is set in XAML (there is no command for it). Above I show that the label is handling clicks and they are handled in the OnButtonClickedHandler (green arrow above).

The MainPage view shown below

Note: the flexLayout control handles the display of all commands that are supported by this presenter, e.g., Find Closest, Get data, and Inventory; they are handled by the framework in PresenterBase.

Note in the FindClosestCommand that the "ButtonText property is set to "Find Closest" which is displayed in the first button.

Pointed to above you’ll see the command has access to the view-model, it not only has access to it but also the presenter and view(s).   The ButtonEventArgs is set [by the framework] with the following:

This effectively gives the command access to everything the presenter has access to.  

With this basic understanding of the application's framework you should be able to quickly navigate its code by going to the presenter and/or command for the applicable process.  

The Monkey and Inventory apps share most, if not all, of the currently available components (commands and view-models).  We can easily swap out data access layers (offline and online) for both Monkey and Inventory using these reusable components.   

With only the presenters being tightly coupled to available components, view-models as well as views can be easily reused.

Apple M1 an "ARM-powered" device

After giving my IMac to my son I purchased the newer Mac Mini with an M1 chip so that I can deploy IPhone and Android applications.   I installed parallels and was surprised with the following message after "having" to install Windows 11 insider preview (for ARM).

So it looks like I won't be able to have my x64 / x86 Windows development environment within my Apple environment :(  This was a disappointment...

However, the power and performance of the M1 was impressive, not what I was accustomed to.  I'll be downloading Visual Studio 2022 for Mac as well as Visual Studio Code to see how far it will let me go in developing my full-stack application.     

The above link (Visual Studio on ARM-powered devices) follows:


ASP.NET Core - Unexpected character encountered while parsing number:

IdentityServer4.Startup: Information: Starting IdentityServer4 version 4.1.0+5a4433f83e8c6fca7d8979141fa5a92684ad56f6
Exception thrown: 'Newtonsoft.Json.JsonReaderException' in Newtonsoft.Json.dll
Unexpected character encountered while parsing number: �. Path '', line 1, position 1.

You’ll see this error if you are configured with an IdentityServer type=File (figure 1) and your environment is setup to run in “Development”; the fix is to set the ASPNETCORE_ENVIRONMENT=Production (figure 2).  Likewise you can leave the setting at “Development” but will have to copy the appsettings.json “IdentityServer” segment to the appsettings.Development.json file (removing the Key type = Development).

In figure 3 and 4 you can see that it gets confused thinking it is in Development expecting JsonConvert to deserialize the contents when in fact the path is our .pfx file.  By setting it to Production it ignores the appsettings.Development.json (more closely emulating a production deployed environment which can aid with debugging issues).

Figure 1.

Figure 2.

Figure 3.

Figure 4.

VS Code / Typescript: hide .js files

1.  Create a settings.json file under the .vscode folder if it does not exist.
2.  Add the following settings and save

"files.exclude": {
// Hide the node_modules folder
"node_modules/": true,
// Hide all files that end in .d.ts
"**/*.d.ts": true,
// Hide all files that end with .js if they have a .ts file
"**/*.js": {
"when": "$(basename).ts"


Mac: show hidden files in finder

  1. Open terminal
    • <command>+<space>
    • Type in “terminal”
    • Double-click terminal
  2. Type in the following to enable viewing of hidden files in the finder
    defaults write AppleShowAllFiles YES [NO = hidden files off]
  3. Relaunch the finder
    • Hold <options> key and right click on finder
    • Click on Relaunch

VS Code / Angular: launch in debug browser (1.91 kb)

Figure 1.  Launch debugger in Chrome

  1. Ensure Debugger for Chrome extension is installed (figure 2)
  2. In .vscode folder add launch (figure 3) and tasks (figure 4) json files
  3. From VS Code debug select Launch Chrome or Launch Chrome (Test)

Figure 2.  Visual Studio Code extension for Debugger for Chrome

"version": "0.2.0",
"configurations": [

"name": "Launch Chrome",
"type": "chrome",
"request": "launch",
"preLaunchTask": "npm start",
"url": "http://localhost:4200/",
"webRoot": "${workspaceFolder}",
"trace": true,
"name": "Launch Chrome (Test)",
"type": "chrome",
"request": "launch",
"preLaunchTask": "npm test",
"url": "http://localhost:9876/debug.html",
"webRoot": "${workspaceFolder}"
Figure 3. launch.json file
"version": "2.0.0",
"tasks": [
"label": "npm start",
"type": "npm",
"script": "start",
"isBackground": true,
"group": {
"kind": "build",
"isDefault": true
"problemMatcher": {
"pattern": "$tsc",
"background": {
"beginsPattern": {
"regexp": "(.*?)"
"endsPattern": {
"regexp": "Compiled |compile."
"label": "npm test",
"type": "npm",
"script": "test",
"problemMatcher": [],
"group": {
"kind": "test",
"isDefault": true
Figure 4. tasks.json file