Thursday 21 March 2024

Avalonia UI Test Drive

As the title implies, this blog post is about my experience test driving Avalonia UI.

So why am I doing this?

I've been getting growing questions lately about whether MapGuide Maestro works on Linux via Mono

Sadly I no longer emphasize anything regarding Mono compatibility because.

  • WinForms support (in legacy .net Framework 4.x) on Mono is pretty much a hack.
  • And since we've moved away from legacy .net Framework to .net 6.0, the combination of WinForms + .net 6.0 is probably an unsupported combination (you don't use Mono to run .net core/5.0+ applications, you use Microsoft's official SDK/runtime). I'm also not too keen to waste time and resources to test and find out.
The best and practical approach for a multi-platform MapGuide Maestro would be to at a minimum to rewrite the UI in a library/framework where support on non-Windows platforms is a first-class citizen.

And in terms of .net multi-platform UI frameworks, there's only one logical choice: Avalonia UI

While it is not in my immediate plans to rewrite MapGuide Maestro's UI in Avalonia, I wanted to at least explore the feasibility of building such a UI even if all the actual functionality is mocked up, just to see how easy or difficult the whole process is.

Hence the motivation for this post.

So why Avalonia?

Avalonia is effectively the "spiritual successor" to WPF, and adopts most of the same patterns and practices when building UIs for Avalonia.

I had first heard about Avalonia when it was formerly known as Perspex and at the time, from the screenshots of example Perspex applications on Windows and non-Windows platforms, it was clear at that point in time the range of possible applications one can build with Perspex was quite limited and building an application like Maestro on top of Perspex was not feasible.

Just recently, I had heard about the framework now known as Avalonia again and this time round there was a lot more positive buzz around it, so I gave it another look and was much more impressed at its capabilities and richer suite of UI controls to build applications with.

Our objective 

My objective with this Avalonia learning exercise was to build a minimal multi-document interface skeleton application, mimicking the primary functionality (UI-wise) of MapGuide Maestro.

  • Being able to present a login UI to connect to a MapGuide Server
  • Present MapGuide Server resources in a tree view
  • Open resources in a region of tabbed editor panels and being able to close them.
This is a crude wireframe of the kind of UI I wanted to build.
If this kind of UI looks familiar, yes it's basically the UI for tools like:
MapGuide Maestro is an IDE-like application (in terms of UI), so my first instinct is to look for a Visual Studio like docking library for Avalonia. However, I've been using Visual Studio Code more often than full Visual Studio as my daily driver for coding and the VSCode UI has become my default UI that I want to replicate for any IDE-like application with a multi-document interface.

The benefit of such a UI, is that it doesn't require an explicit VS-style window docking control like I have for the current WinForms-based MapGuide Maestro. I can already see the controls needed to build such a UI

Any UI toolkit worth its salt should to able to provide these basic controls.

This is a proof-of-concept, so we won't be using the existing Maestro API to talk to actual MapGuide Servers. Instead such functionality will be mocked up for this application. The main purpose of this exercise is to see if Avalonia provides enough of the base UI elements to build a hypothetical version of MapGuide Maestro on top of.

It turns out this exercise was fraught with several challenges

Challenge #1: Getting familiar with Avalonia concepts

The first challenge was simply getting familiar with Avalonia concepts. Being someone who did .net desktop app development primarily in Windows Forms, I skipped over WPF, and its XAML-based variants/successors like Silverlight, Xamarin Forms, UWP, and MAUI, and Avalonia being a "spiritual successor" to WPF meant that I didn't have a potential conceptual head start on Avalonia that one might have if they already had experience with WPF and its XAML-based derivatives.

However, I was familiar with the MVVM pattern and data-binding, which is used heavily in Avalonia. I already had experience in knockout.js building/maintaining some of our web apps in my day job, and these concepts learned from knockout.js mostly translate cleanly 1:1 to Avalonia.

The other Avalonia concepts I'll have to figure out as we go along.

Challenge #2: WebAssembly (WASM) support

Before I begin, I must take the MapGuide-knowledgable readers of this post back in time, way back to when MapGuide Open Source 1.0 was released.

Do you remember how this new (at the time), re-invented version of MapGuide was released, but the only authoring option at the time was to buy a license of Autodesk MapGuide Studio? Not a pretty look if you are offering a free and open source web map GIS server, but the authoring tools are not?

Some members of the MapGuide community were aware of this glaring discrepancy and created a web-based equivalent of MapGuide Studio called Web Studio built fully with HTML/JS/CSS. Unfortunately, Web Studio was really bare-bones in terms of authoring capabilities and the code being written in the pre-historic era of javascript (this was circa 2006-2007) which did not make it conductive to external contribution. React, TypeScript and friends weren't around then, so trying to enhance Web Studio with new features was extremely challenging. I once tried to add at least a generic XML text editor fallback for Web Studio so you had something to edit various resources where Web Studio did not provide a dedicated editor UI for, but alas this was just too challenging for me and I gave up on such an idea.

But eventually another user of the MapGuide community solved this problem more directly by developing and releasing a v1.0 of a windows .net desktop application that you all know as MapGuide Maestro. As an aside, I am not the original author of MapGuide Maestro in case you're wondering, I merely took over development and maintenance of Maestro from the 2.1 release onwards.

Anyways, back to the topic at hand, when I created the new Avalonia application with the provided project template, I was most surprised to see that the generated solution came with a project that compiled to WebAssembly (WASM) that was ready to run in your web browser!

This was a mind-blowing revelation for me from a conceptual standpoint. It means our hypothetical MapGuide Maestro built on Avalonia could not only exist as a regular desktop application, but the WASM build of this application could be dropped in the the wwwroot of a MapGuide Web tier installation and you would now have a modern version of Web Studio, but much more powerful and capable, because it is just MapGuide Maestro ... now in your web browser!

Since our starter project template includes a functioning WASM browser target. I now had a strong incentive to keep this target active and working, because the prospect of being able to run MapGuide Maestro in a web browser is a very tempting proposition. Therefore, the choice of libraries and APIs I use is constrained by my new requirement of being able to work in a browser/WASM environment.

For example, I originally wanted to use MVVMDialogs to simplify working with dialogs (Maestro has lots of dialogs, so I figure such a library could be useful), but I couldn't get this library to work in a WASM environment with some of my testing dialogs, so this was a no-go. Since this was just a proof-of-concept, there wasn't a need to have working dialog system, but it does mean if this were to go beyond a proof-of-concept and into an actual application where we will inevitably have to present a dialog of some sort, I'd have to come with a paradigm that can work in both desktop and browsers.

Another problem with this WASM target is that I can't seem to debug it in Visual Studio. You can launch the WASM target in the debugger and spawn a new browser window to launch your app, but any breakpoints you stick in your C# code are not being hit. I'm not sure if this a shortcoming or a broken feature, but it is somewhat concerning if we were to go full steam ahead with support WASM as a compilation/deployment target.

Challenge #3: "Large scale" MVVM

Although I already knew the MVVM pattern from knockout.js, my scope of usage was mostly limited to using knockout.js to building "islands" of interactive client-side content on primarily server-generated web pages. So I didn't really have an idea of how to apply such a pattern on a full blown Single Page Application (SPA), which is pretty much what we're trying to achieve (conceptually) in Avalonia. By the time I was building SPAs proper, I had moved on the popular stuff like React, which is how I gained the knowledge needed to build a modern replacement map viewer for MapGuide and my usage of knockout.js fell by the wayside as a result, so I never figured out the answer for how to do large scale MVVM.

The main problem was that in a large scale MVVM, how do view models communicate with each other without a tight parent-child coupling?

I deduced that for starters, we definitely need to use dependency injection. Various view models will need to access different services and if we had a root view model with explicit nested child view models (each with their own service requirements), it would be an absolute pain to have to setup these various view models. Using a DI container means we can offload this concern to it and we can focus on just asking the DI container for a particular view model and it will setup all the required services for it for us provided we register everything properly with the DI container.

For better WASM support, I wanted a dependency injection container that is not driven by reflection to make the code more friendlier to app trimming. We want to be able to app trim on publish so that we can eliminate unused code and reduce the final binary size. This is most desirable for the WASM target as app trimming means smaller binaries, which means smaller payloads to download in a web browser. StrongInject was chosen for this reason as it was a "compile-time" DI container that can verify all your dependencies are registered properly before running the application through the magic of source generators, generating all the necessary registration and validation code for you.

Finally, to be able to communicate between view models without necessary parent-child coupling, the messenger facility of the MVVM toolkit can be used. With this messenger facility, we simply:
  • Have select view models be recipients of certain messages
  • Have our application services (that various view model commands call) send these messages.
  • Relevant recipients get notified and update themselves (and their bound UIs) accordingly as a result.
I don't know if this is the proper usage of MVVM pattern in a "large scale" application, but it makes sense internally in my mind.

Challenge #4: TabControl binding of heterogenous/polymorphic content

Our proof-of-concept app has a main tab region where tabs of various document content are shown. I figured out easily that their TabControl component is supposed to be data-bound to an observable collection of view models, but what really stumped me was how bind this control to an observable collection of polymorphic editor view models?

The reason is because the tab content we want to show is not homogenous. One open editor tab could be for a layer, one for a map, one for a feature source, etc, etc. So we need to be able to show different tabs on the same tab control. 

Avalonia documentation is pretty scant on this topic. All examples I found assume homogenous series of tab content, which is not our case.

Just for laughs, since AI has been hyped for soon taking away everyone's jobs (even us devs), I figured I'd ask huggingface chat (as a guest), how would you solve this problem?


Unfortunately, the provided code sample does not work out of the box. It clearly assumed Avalonia = WPF and gave me a WPF-based solution. The giveaway was the Avalonia TabControl does not have an ItemTemplateSelector, but the WPF TabControl does.

But Avalonia sharing many conceptual similarities with WPF meant that although the answer provided wasn't correct, parts of the answer were applicable and did lead me down to further avenues of inquiry and eventually I found the solution: It was to define a data template for every possible derived tab view model class in the same UserControl where the TabControl was specified.

Final challenge: Avalonia VS designer

This wasn't so much a challenge, rather an annoyance. The Avalonia designer in Visual Studio has some teething issues
  • Intellisense/autocomplete is somewhat flaky when writing binding expressions and when you're doing a lot of data-binding, having the editor giving you and incomplete or outdated list of properties you can bind to becomes annoying. A full project build generally fixes this, but it is annoying having to do this every time I add new observable properties or commands to an existing view model class.
  • It's also not rename-aware, so observable property or command renames will result in stale binding expressions, causing havoc with the designer and have to be manually fixed up in the XAML. Choose your observable property names wisely I suppose, because renaming them afterwards is painful.

The end result and overall thoughts

A week later from that post, after addressing and/or navigating around these various challenges, we have a functional skeleton application!





And if these screenshots don't convince you, thanks to being able to deploy as a WASM target, I deployed a copy to GitHub pages, so you can see this app for yourself right in your WASM-enabled web browser!


The source code for this app can be found here. One day this may form the basis of a new (true) multi-platform version of MapGuide Maestro, but for now this lies as a potentially useful starting point for building a tabbed-multi-document editor application with Avalonia.

So what did I think of Avalonia from this little experiment? 
  • I like it mostly. My pre-existing experience on knockout.js helped greatly with picking up MVVM and data-binding. My initial prediction of a VSCode-style UI layout being buildable turned out to be true.
  • I like the default (project template provided) choice of MVVM Toolkit for applying the MVVM pattern. I like their heavy use of source generators to make adding new observable properties and commands to a view model being a simple case of tacking [ObservableProperty] on a field or tacking [RelayCommand] to a private method and having the source generator generate all of the boilerplate code for you (and it's a lot of boilerplate!).
  • The revelation that Avalonia has a WASM deployment target was both exciting and "cramping my style". It meant that certain libraries I wanted to use (eg. The MVVM dialogs library) could not be used and it wasn't clear what would work in a WASM browser environment and what wouldn't. Which leads to ...
  • Documentation is lacking in some areas. What really stumped me for a while was how do a TabControl bound to a collection of polymorphic or heterogenous tab view models. Their provided examples completely failed to tell me how to do this. I suppose if I came into this with existing WPF experience, this wouldn't have been so difficult as most of the concepts and patterns seem to be mostly transferable, but I happened to have skipped WPF and its bajillion XAML-based derivatives, so I didn't have this pre-existing knowledge to fall back on. Through perseverance and looking at the source code for many existing Avalonia applications on GitHub, I was finally able to determine that data templates was the solution.
So all-in-all, this was a fun and useful exercise and you get a useful starting point app from this effort!

Now I better get back onto this MapGuide/FDO work.

Monday 18 December 2023

Minor change of plans

There will be a slight change of plans in the MapGuide Open Source 4.0 release timeline.

Namely, the next release will not be the Release Candidate, but rather a 2nd beta release.

The main driver behind this decision is because of my intention to remove the recently introduced support for Mapbox Vector Tiles. While I initially had high hopes with this implementation, additional testing with data outside of the example Sheboygan dataset has revealed rendering issues I do not have the capability to address. Rather than ship a half-baked implementation that may never bake fully, I'd rather bow out while we still can, remove this immature implementation, and leave MVT tile generation to dedicated external tools.

A 2nd beta release will also mean that the other changes I want to get in will also have some time to bake before the Release Candidate stage.

I am hoping the 2nd beta release will be out late January in the new year.

Monday 4 December 2023

Announcing: mapguide-rest 1.0 RC6

6 years later, I have finally put out another release of mapguide-rest!

The reason for finally putting out a new release (besides being long overdue!), is that I needed a solid verification of the vanilla SWIG API binding work for MapGuide Open Source 4.0 and mapguide-rest was just the ideal project that touches almost every nook and cranny of the MapGuide API. So if mapguide-rest still works with the PHP binding in MapGuide Open Source 4.0, that is as good of an endorsement to the reliability and readiness of these bindings.

For this release of mapguide-rest, it is compatible with the version of PHP that comes with:

  • MapGuide Open Source 3.1.2 (PHP 5.6)
  • MapGuide Open Source 4.0 Beta 1 (PHP 8.1)
Besides being compatible with the upcoming MapGuide Open Source release (and the current stable one), this release also adds a new series of APIs to perform various geo-processing tasks. All of which are available to try out in the interactive swagger API reference.




Special thanks to Gordon Luckett and Scott Hamiester for assistance in internal testing of many internal builds of mapguide-rest that finally culminated in this long-overdue release.

Now that this is out of the way, it is back to MapGuide development proper and getting closer to the 4.0 release.

Wednesday 13 September 2023

Announcing: vscode-map-preview 0.6.0

This impromptu update fixes up Stamen tiles support to point to the new infrastructure managed by Stadia Maps.

Since we have to do this, we've taken this migration as an opportunity to add support for many more base layer types. 

The full list of base layers supported is now:

However, certain Bing and Stadia Maps layers will only be available in the base layer switcher if you provide respective API keys for these services through new configuration properties.

Don't like these base layers and want to bring your own? You can do that now too! If you have an XYZ tile set or WMTS service you want as a backdrop for your map previews, you can define such layers in your settings.json like so:

And they shall appear in the base layer switcher as layers you can switch to



This update also refactors the viewer HTML preparation by fetching the document (to be previewed) content on viewer init instead of embedding its content into the viewer HTML. This should improve responsiveness when previewing larger files, to the point that you can make out the new preview preparation message.

However, as I started to test this with bigger and bigger files, I eventually found a limit where the VSCode APIs will not cooperate with us. This limit is the point where VSCode will either not do syntax highlighting or tokenization or both and when we try to preview such a file, it will silently fail.

For this update, we will show a better error message on such files.

So for those who are hoping to use this extension to preview GeoJSON files whose size is in the range of gigabytes, sorry there's nothin I can do here 🤷‍♂️🤷‍♂️🤷‍♂️🤷‍♂️ I think at that point you are better served by using actual GIS tools/software for this purpose.

Now this this update is out the door, it's back to mapguide-rest development.

Tuesday 12 September 2023

Another temporary detour

While I was deep into reviving mapguide-rest to make sure it works with the PHP 8.1 bundled with MapGuide Open Source 4.0, I missed the memo that Stamen Maps is now under new management: Stadia Maps.

The most important aspect of this announcement was that come October 2023 (a month from now), current stamen tile URLs may stop redirecting/working and you should have migrated over to Stadia Maps by then.

Because Stamen tiles carried the same role as OpenStreetMap as a freely accessible base tiled layer for various open source projects of mine, this announcement has thrown a spanner in my works because come next month, Stamen tiles may not work and for continued support for these layers would require migration over to Stadia Maps.

So I have momentarily suspended my work on mapguide-rest to give focus to one of my other projects affected by this announcement: My VSCode map preview extension.

Because continued support for stamen map tiles requires migrating the extension over to use Stadia Maps, we might as well take this opportunity to add support in the extension for other base layers that Stadia Maps offers. And while we're at it, we might as well go all the way and add in Bing Maps, XYZ and WMTS base layers as well.

Or, just have a gander at this clip for a taste of what the next version of the VSCode map preview extension can do!


The next version of the extension will drop real soon and then it is back to mapguide-rest development.

Saturday 12 August 2023

MapGuide Maestro 6.0m12 nuget packages now available on nuget.org

nuget.org support finally provided a resolution on my account issue and I was able to regenerate my publishing keys.

As a result, the 6.0m12 release (6.0.0-pre500) nuget packages are now finally available on nuget.org

We now return to your regularly scheduled programming ...

Monday 7 August 2023

Where's the new Maestro API nuget packages?

There were a few things I left out of the previous announcement that I'll use this post to address.

Firstly, the 6.0m12 release of MapGuide Maestro formally drops all Fusion editor support for integration with Google Maps tiles and services. We no longer support Google Maps integration in Fusion and the editor in previous releases gave the false impression that this is still possible. That is no longer the case with this release.

Secondly, the more important thing (and the subject of this post) is that if you are using the Maestro API and consume this through nuget packages from nuget.org you may be wondering why there are no new versions?

The answer to that one is simply: My nuget package publishing keys have expired and something in the nuget.org website or something with my nuget.org account is preventing me from regenerating these keys or to generate a fresh set. As a result, I currently cannot upload any new nuget packages to nuget.org

But do not fret, because there is an alternative solution.

As part of the MapGuide Maestro release on GitHub, the nuget .nupkg files are also included


From here, you can set up a local directory-based nuget package source, drop the .nupkg files into it and the this version of the package is available to install in your Visual Studio solution.

If/when I get a resolution on this publishing key matter, I will upload the .nupkg files for this release and make another announcement. Until then, this local package source is a suitable workaround.