Vetuviem is a Roslyn Source Generation toolkit on top of ReactiveUI aimed at empowering development teams by:
- Giving a mechanism to reduce the amount of boiler plate code being produced, by allowing some of the ReactiveUI specific logic to be hidden away.
- Allow the developer to think along the lines of standard behavior for controls by offering a way to produce re-usable binding logic through a class and\or function design pattern.
- Allow the developer to focus on what matters on the ViewModel
- Reduce the cognitive load by:
- Removing the risk of misusing 1 way or 2 way binding
- Remove the need for the user to think about having to cater for Bind vs BindCommand
- Offer a structure that allows for more work to be done potentially with Source Generators to reduce reflection and improve the build time developer experience.
Where Vetuviem fits
Vetuviem acts as glue between the View and ViewModel in the MVVM pattern when using Codebehind binding in your XAML, you can think of it as MV2VM. The name comes from "V to VM", and is become a play on Latin (ve tu vi iem) which if you put in a translation tool and split correctly comes out as "You've force that day on".
The View Binding Models have a tipping point for being useful in terms of scalable of sustainable development. This can equate to:
- How many controls you have that conform to a standard behaviour.
- How many properties you bind between the View and ViewModel on these controls.
One of the quick time savers \ code reductions it allows is to write the control binding expression (i.e. vw => vw.TextBoxName) once, instead of for every binding line, as the library handles the individual control property expressions (vw => vw.TextBoxName.Text, vw.TextBoxName.Foreground, etc.) in the background.
- Producing a library that generates ViewBindingModels for each control in a standard library for a platform.
- Detailing how to use the generation with other control libraries.
- Detail further potential improvements for source generation.
Producing the ViewBindingModels
This first phase is the crux of the library. There are certain facts that underpin ReactiveUI binding.
- Each UI platform that is supported has a base class and\or interface for controls.
- If a UI platform supports Commands, that platform has a base class or interface.
- The core of ReactiveUI is agnostic as much as possible from these platform specific parts.
- There are essentially 3 types of bindings.
- Command Bindings
- One-way Property Bindings (where one side of the relationship only supports reading - the read only part is typically a property on the control such as a "HasItems" indicator on a control that represents a collection of items)
- Two-way Property Bindings (where there can be a bi-directional flow)
Roslyn source generators are a powerful addition to NET5. While previous concepts have existed for years, this is the first iteration to offer natural support in the IDE and build chain for .NET. There are still some parts in the IDE support that need to mature but it will only be a matter of time with use and engagement of the community. The one snag is the lack of detailed documentation around Roslyn, luckily the Syntax Visualizer within Visual Studio is a handy tool to aid in making sense of the method calls you need to use, but it can be a bit of trial and error where progress goes through peaks and troughs.
The process put together for this library uses the following concepts
- A UI platform (or subsequent libraries) will have libraries that are available via one of the following sources:
- Native platform support via the SDK target.
- Package(s) available from NuGet
- The build and Visual Studio project system will be responsible for driving the package availability.
- The Roslyn Source Generation Context makes the reference library available to consumers. It is possible for us to use platform specific logic to check the libraries are available, and for us to drive loading of additional references.
- We will scan each library of interest for classes that implement\inherit from the platforms core UI class \ interface.
- For each class (UI element) we match, we scan for public properties and define the type of binding as described above under ReactiveUI concepts.
- We will build an inheritance model to reduce duplicate code.
- We will generate a "bound" and "unbound" ViewBindingModel for each class. The unbound is to allow the flexibility in the generic inheritance design. The bound model is type specific model that removes one of the generic arguments to ensure that developers can utilize the control specific ViewBindingModel without having to deal with generic argument for the control.
Making Vetuviem usable with other libraries.
The libraries Vetuviem has for ReactiveUI are based on generating the View Binding Model logic for the target platform (WinUI, Xamarin, WPF, etc.) and the ReactiveUI specific controls for that platform. It doesn't generate the code for other control libraries, but it has been produced with those in mind because otherwise it would defeat the purpose of trying to make view binding specific development easier with ReactiveUI.
There is always balance in open source development, we could add support for different control libraries into ReactiveUI, but it adds a lot of maintenance overhead to the ReactiveUI project and you end up with support headaches where people need to keep their versions aligned. The 2 alternatives are for users to generate their own relevant logic alongside the control libraries they consume, or for the producers of control libraries to facilitate the support themselves. This requires engagement with the producers of the libraries and for them to maintain alongside versions of the control libraries and absorb any future changes to ReactiveUI and/or Vetuviem. They should already be absorbing some level of change for the raw platform changes such as Xamarin updates. At this point Vetuviem is still very much a proof of concept so I'm going to take the middle ground and leave it with consuming developers. Down the road if it's felt value is proven there can be conversations.
There are 2 classes that drive the source generation:
- Binding Model Generator
- Platform Resolver
The platform resolver is the piece that needs altering for control libraries. It contains a list of assemblies that need to be scanned. These same assembles need to be included in the C# project system as NuGet package references. In most scenarios the Binding Model Generator doesn't need any significant change as the base control and command for a platform should remain consistent.
Future options for Vetuviem and source generation
Currently Vetuviem is a one step process where it generates binding logic for every property on a control. The apply binding logic does a null check on every property in the View Binding Model to then drive the actual binding. We could add a second step process that recognizes these bindings are fixed at compile time and removes the "not null then bind" check for properties that will never bind. In theory there is already enough knowledge from what exists in Vetuviem for the compiler \ IL merge tools to perform this on a build, but the source generator could also do this as a second step to make it completely clear during a build and the code would be visible. At this point this is an option but Vetuviem needs to prove its value first.
Another piece is co-existing with the event binding that is offered by Pharmacist. There is currently having other work done on a source generator level to make the event logic not be tied to specific ReactiveUI (and specific UI platform) versions. This interaction with Vetuviem was left out of scope to allow focused proof of value.
GitHub Roslyn Project: Source Generators Cookbook. Accessed 2021-05-20.