Thread about my work on #GeoBlazor, which brings interactive maps and geospatial data to #blazor.

1/

I haven't released a new version GeoBlazor, the #dotnet #blazor wrapper for #arcgis, to NuGet for the past 4 months, but there's a really good reason for that.

https://buff.ly/3ZK4OyL

2/

dymaptic.GeoBlazor.Core 3.1.1

GeoBlazor is a GIS Component Library and SDK for building interactive maps in Blazor, powered by ArcGIS. For more information, visit https://www.geoblazor.com or contact dymaptic at geoblazor@dymaptic.com

Up to now, we've had a lot of trial and error to identify the best way to translate ArcGIS JavaScript into #csharp and #razor components. During that time, our "coverage" of the ArcGIS JavaScript SDK has been a small fraction of their total features.

3/

Instead of trying to cover everything, we've focused on providing the most commonly used functionality. For example, we focused on 2D Maps with FeatureLayers over 3d Scenes (although we also have basic support for that).

https://buff.ly/3ZLB5pb

4/

There are several good reasons it takes a long time to wrap an ArcGIS JS feature. First is deciding what the API and user interaction should be in Blazor. Can the user add the layer/widget/renderer/graphic right into markup? Can they add it via code? What about supporting both?

5/

Blazor wasn't really intended to have this flexibility, but since we're not rendering html directly, we can support both component-based nested markup and parameter-based code construction on the same types.

6/

Second is the issue of serialization. Most of our types use System.Text.Json, which is supported by default in Blazor's JSRuntime. Some, though, require custom JsonConverters, especially when reading types back from ArcGIS JS.

7/

Next, once we've passed types to JS, we still have to instantiate the relevant ArcGIS class or type. Simply passing in all the properties from serialized GeoBlazor components doesn't work, as for example passing `null` to a property not expecting it will cause errors.

8/

Csharp doesn't have the concept of `undefined`, but it does have `[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]`, which helps trim out unwanted properties.

9/

Another conversion that takes careful handling is TypeScript unions and Csharp enums. These are often a "logical" mapping, and allow GeoBlazor to be more type-safe than ArcGIS. But custom converters are a must to get them to map.

10/

Then there are issues with Csharp keywords (Type, ref, object) or property names we're using (Id) conflicting with ArcGIS properties. When this happens, I have to rename the ArcGIS property in GeoBlazor and create a custom mapping.

11/

We also can't pass the GeoBlazor Id prop into an ArcGIS constructor, it wouldn't understand it. But we do want to send it to our JS layer to store a lookup table and be able to retrieve objects by Id later.

12/

This is all to map a single class or type, but most types in ArcGIS also have properties of other types. The dependency tree of just FeatureLayer is over 100 types, at least when you include interfaces and unions. And there are dozens of layer types, each with their own dependencies.

13/

So far, all of the work described above was being hand-crafted by myself or a few colleagues. This fall, we have begun creating a source generation utility that will codify and automate all of these rules for conversion and wrapping.

14/

Luckily, ArcGIS does provide an excellent TypeScript definitions file, which provides a clear list of types, properties, and methods. So, the first task was to parse this file apart.

15/

I looked at a few existing TypeScript parsers but ultimately decided to create my own. This let me immediately map to an intermediate state for each type, which I can then save as JSON to disk. That way, if the rules and code haven't changed, I can skip the first step on repeated runs.

16/

After storing this intermediate step, I need to read the existing GeoBlazor code and determine rules for when to rewrite and when to let custom implementations remain. This took a lot of tries before I settled on each class having a `.cs` partial class custom file and a `.gb.cs` generated file.

17/

Constructors and properties get removed and moved to the generated code unless they are flagged with an attribute to leave them alone. Methods are more complex, so I opted to leave custom by default, and if you want to use the generated implementation, you have to delete the custom one.

18/

TypeScript doesn't have partial classes, so I chose inheritance. `FeatureLayerWrapper` inherits `FeatureLayerGenerated` from a `.gb.ts` file. This lets me do a similar pattern of generating and overriding implementations.

/19

There are a lot of rules I can create to handle conversions of many different types at once. But there are also many one-off custom rules and implementations. Each time I run the generator, I find more edge cases. I then revert the changes, implement the custom rules, and run again.

/20

The upside is that I can now generate hundreds of classes and properties in a few minutes. I can also generate tests to validate the code. There will still be a lot of hand-fixes, but over time we should progressively increase our ability to match the ArcGIS API.

/21