I’ve been working with Blazor
this past year. In case you haven’t heard, Blazor
is a web development framework, from Microsoft, that promises to use C# for most of the things that we’re all forced to use javascript for, today. Blazor
is open source and it runs on the server, or the client, making SPA
applications much easier to construct.
Here is a link to the WIKI for Blazor
, if you’d like to know more.
My opinion of Blazor
, so far, is good. To be fair, there are still some rough edges, but overall, I really like what’s there. One of the things I do miss though, is framework level support for either model view controller (MVC)
, or model view, view-model (MVVM)
design patterns. Since Blazor
doesn’t support those things by itself, that usually means I have to roll my own, on each project, or pull in some 3rd party library. Neither of those approaches typically make me happy, so I added a basic (I stress basic) implementation of MVVM
to my CG.Blazor
NUGET package.
Before I dive into my MVVM
code, let’s spend a second or two and discuss what life is like, in Blazor
, without MVVM
. The two most common alternatives are a variation of code-behind where all the code is mixed with the markup, like this:
@page "/" <!-- markup goes here --> @code { // code goes here }
or, some variation of coding in a base class, then deriving your view from that, like this:
public class MyPage : BlazorComponent { // page code goes here. }
@page "/" @inherits MyPage <!-- markup goes here --> @code { // more code goes here }
My biggest problems with these two approaches are that they: (1) mix markup and implementation far more than I am comfortable with, and (2), they are harder to unit test. That last part is a deal breaker, for me, because GUI’s are difficult enough to test properly, as it is. I don’t see any reason to make the process harder than it needs to be.
At this point you might be asking yourself, why doesn’t Blazor
include MVVM
already? Well, it’s actually not an oversight on Microsoft’s part. Instead, the designers who crafted Blazor
purposefully made the framework agnostic towards certain design patterns, like MVVM.
That’s alright though. I can add basic MVVM
support with a surprisingly small amount of code. Let’s go look at that.
I started by creating an interface called IViewModel
. Here is what that looks like:
public interface IViewModel : INotifyPropertyChanged { }
From there, I created a corresponding base class to complete the abstraction. Here is what that class looks like:
public abstract class ViewModelBase : IViewModel { public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged( [CallerMemberName] string propertyName = "" ) { PropertyChanged?.Invoke( this, new PropertyChangedEventArgs(propertyName) ); } protected void SetValue<T>( ref T backingField, T value, [CallerMemberName] string propertyName = null ) { if (EqualityComparer<T>.Default.Equals(backingField, value)) { return; } backingField = value; OnPropertyChanged( propertyName ); } }
This is probably fairly straightforward for anyone with MVVM
experience but I’ll go through the code anyway. The class implements the INotifyPropertyChanged
interface, so the view-model can signal whenever the value of a property changes by raising the PropertyChanged
event.
The PropertyChanged
event is raised through the OnPropertyChanged
method. Notice the use of the CallerMemberNameAttribute
decoration, for the propertyName
argument. By using that feature of C#, we allow the compiler to fill in the property name for us, whenever we raise the PropertyChanged
event. It’s a trick (not mine) that just makes life a little easier.
The next method we’ll look at is the SetValue<T>
method, which is responsible for updating the backing field for a property. It also takes care of raising the PropertyChanged
event for us, as well.
Now that I have a base view-model abstraction, I’ll need to look into the view portion of MVVM
. I’ll do that by creating a base class for my Blazor
pages to use. Here is what my class looks like:
public abstract class ViewBase<T> : ComponentBase, IDisposable where T : class, IViewModel { [Inject] protected T ViewModel { get; set; } public void Dispose() { #pragma warning disable CS1998 ViewModel.PropertyChanged -= async (sender, e) => { }; #pragma warning restore CS1998 } protected override async Task OnInitializedAsync() { if (null != ViewModel) { ViewModel.PropertyChanged += async (sender, e) => { await InvokeAsync(() => StateHasChanged()); }; } await base.OnInitializedAsync(); } }
When I first started trying to use MVVM
, in Blazor,
I discovered that Blazor
doesn’t understand anything about the INotifyPropertyChanged
interface. That’s a problem because one of the core precepts of MVVM
is that the view-model tells the view whenever something important changes. In C#, that notification mechanism is typically handled through the INofityPropertyChanged
interface. I kept that in mind as I pondered the design of a simple Blazor
view class.
For my view class, I started with a type argument T
for the class, which specifies an associated view-model type for the view. I suppose some purists won’t like that I’ve linked the view and view-model through the type parameter, but it works for me. Decorating the ViewModel
property with the InjectAttribute
tells the DI container to inject an instance of my view-model type, at runtime. That means I don’t have to worry about where my view-model instance comes from, it will just be there when it’s needed. It also means my ViewBase
class won’t need a constructor. It also means I will have forced fewer assumptions on whatever code derives from my class. That’s generally a good thing, as well.
Now that I have a view-model reference in my ViewModel
property, I can use it to wire up a handler for the PropertyChanged
event. I do that in my OnInitializedAsync
method, where I wire up an asynchronous handler that responds to PropertyChanged
events by calling StateHasChanged
on the view. StateHasChanged
is the standard Blazor
way of saying “something important changed in my view and you should respond by redrawing the UI”. So, by handling the PropertyChanged
event and forwarding it to the StateHasChanged
method, in Blazor
, I get Blazor
to respond to my view-model changes with no other effort. Nice, right?
The only other thing I need, now, to have a working implementation of MVVM
that I can import and use in my Blazor
projects, is an implementation of ICommand
. It just so happens that the System.ObjectModel
assembly already contains a definition of ICommand
, so I don’t have to worry about that part. I do, however, need to worry about coming up with a working implementation of ICommand
.
There are a couple of ways I could handle that situation: (1) I could reference any one of the 20 bazillion existing implementations of ICommand
that are out on the Interwebz, or (2), I could write my own version. Normally, I would argue against creating yet another command class, but, if I don’t write my own then I’ll have to live with a reference to some other assembly that probably won’t have much else to do with my little CG.Blazor
NUGET package. And, of course, some people might not care to use ICommand
at all, and, in their case, they would have to live with that extra assembly reference for something that does them no good. For that reason alone, I decided to write a quick command class. This is what that looks like:
public sealed class DelegateCommand : ICommand { private DelegateEventHandler _handler; private bool _isEnabled = true; public event EventHandler CanExecuteChanged; public bool IsEnabled { get { return _isEnabled; } } public DelegateCommand( DelegateEventHandler handler ) { Guard.Instance().ThrowIfNull(handler, nameof(handler)); _handler = handler; } void ICommand.Execute(object arg) { _handler(); } bool ICommand.CanExecute(object arg) { return IsEnabled; } private void OnCanExecuteChanged() { if (CanExecuteChanged != null) { CanExecuteChanged(this, EventArgs.Empty); } } }
Not much going on, really, in this little class. It implements the ICommand
interface and wraps a delegate and a flag for indicating whether the command is enabled or disabled. By passing in a delegate to my constructor, I can execute any view-model handler whenever the command is executed. The Execute method demonstrates how I’ll call the handler at runtime. The CanExecute
method allows callers to have some idea whether the command is enabled, or not. The CanExecute
method simply returns the value of the IsEnabled
property, on the command object. Whenever the enabled state of the command changes, it raises the CanExecuteChanged
event. The OnCanExecuteChanged
method takes care of that.
Now that I have a view-model base, a view base, and a working command mechanism, the only other thing I really need is a convenient way of registering my concrete view-model types with the DI container. For smaller projects, that part can easily be handled manually. For larger projects though, those Startup
classes get crazy complicated, really quickly! For that reason alone, I generally try to factor startup logic out into a separate extension method whenever it makes sense.
Let’s look at how I wrote a flexible extension method, for handling the view-model registration:
public static partial class ServiceCollectionExtensions { public static IServiceCollection AddViewModels( this IServiceCollection serviceCollection, string assemblyWhiteList = "", string assemblyBlackList = "Microsoft.*,System.*,netstandard" ) { Guard.Instance().ThrowIfNull(serviceCollection, nameof(serviceCollection)); var impTypes = typeof(ViewModelBase).DerivedTypes( assemblyWhiteList, assemblyBlackList ); foreach (var impType in impTypes) { var serviceType = impType.FindInterfaces((x, y) => { foreach (var z in y as Type[]) { if (z == x) { continue; } if (z.IsAssignableFrom(x)) { return true; } } return false; }, new[] { typeof(IViewModel) } ).FirstOrDefault(); if (null != serviceType) { serviceCollection.AddScoped( serviceType, impType ); } else { serviceCollection.AddScoped( impType ); } } return serviceCollection; } }
Based on my own experience, concrete view-model types tend to live fairly close to wherever their corresponding views are. That observation implies that I probably won’t find a concrete view-model type in any of the ‘standard’ .NET assemblies, such as System.Whatever
, or Microsoft.Whatever
, right? So, if I’m not likely to find view-model types in those assemblies then there’s no need to scan them at runtime, right? That’s the idea behind the two parameters on my AddViewModels
method: assemblyWhiteList
, and assemblyBlackList
. They allow me to specify assemblies that I know need to be scanned for view-model types, or, to filter out assemblies that I know will probably never contain a view-model type. That allows me to increase the efficiency of my assembly scan, thereby improving startup times. Both of the list parameters are optional but the black listing of the ‘standard’ .NET assemblies is so commonplace that I’ve included those assembly names, with wildcards, as the default value for the black list.
Those lists parameters are fed into an extension method I wrote called DerivedTypes
, which scans through all the currently loaded assemblies, looking for any types that derive from a specified type. In this case, I’m looking for any types that derive from my ViewModelBase
class. The result is a list of all concrete view-model types (assuming the view-model types were public non-static and non-abstract).
Once I have that list of view-model types I need to make a decision about how best to register that type with the DI container. That means I’ll need to know if the type implements a service interface, or not. For instance, here are two possibilities that I encounter often, for view-models:
public class MyVmA : ViewModelBase { } public interface IMyVmB : IViewModel {} public class MyVmB : ViewModelBase, IMyVmB { }
View-model type MyVmA
should be registered without a service type because it doesn’t directly define one. Oh sure, it indirectly implements IViewModel
through the ViewModelBase
, but then again, so does every other view-model class – right? So, yeah, we probably want to register MyVmA
like this:
serviceCollection.AddScoped<MyVmA>(); // or service.Collection.AddScoped(typeof(MyVmA));
On the other hand, view-model type MyVmB
does directly define a service interface, IMyVmB
, which derives from the IViewModel
interface. In this situation, we probably want to honor that service type when we register. So, we’ll register MyVmB
like this:
serviceCollection.AddScoped<IMyVmB, MyVmB>(); // or serviceCollection.AddScoped(typeof(IMyVmB), typeof(MyVmB));
The practical difference is the way the two types are used in a project. For instance, if you’ll never use or need a service type, and are comfortable leaving references to MyVmA
all over your code, then the first approach makes sense. I usually take this approach for smaller projects, or those where I know I’ll never be asked to write extensive unit test fixtures for view-models.
The second scenario is more for larger project types, or those where testing is more of an ingrained part of the development culture. In that situation, you’ll probably leave references to IMyVmB
in your code and only deal with the concrete type, MyVmB
, in one or two places.
Back to the AddViewModels
method – having explained the need for two different methods of registering view-model types, I’ll move ahead by describing how I go about determining if any of those view-model types have a corresponding service interface, or not.
Iterating over the collection of view-model types, I call FindInterfaces
on each type, to see if that type implements an interface that derives from the IViewModel
interface. If it does, I assume the interface type is the service type for that view-model. It’s possible my assumption might be fallacious, especially if there are several levels of inheritance involved, but, I think, for a majority of cases, it will work just fine.
After I know whether a view-model type has an associated service interface, I have enough information to register the type with the DI container. That’s the next thing I do, and that’s the entire AddViewModels
method, in a nutshell.
Using the AddViewModels
method is easy, just call it from the Startup.ConfigureServices
method, like this:
public void ConfigureServices(IServiceCollection serbives) { services.AddViewModels(); }
Using the other parts mf my MVVM
extension are just as easy. Here is a quick example of a concrete view-model type:
public interface IMyViewModel : IViewModel {} public class MyViewModel : ViewModelBase, IMyViewModel { }
Here is an example of a view to correspond with that view-model:
@page "/myview" @inherits ViewBase<IMyViewModel> @using CG.Blazor.Views <!-- markup goes here --> @code { // could still put code here, if you needed to. }
That’s about it. Obviously my example doesn’t do anything useful, but it does lay out the syntax for getting started, in your project.
So there you have it. If, like me, you enjoy working in Blazor
but you also enjoy using MVVM
in your projects, now there is a very simple, very lightweight solution for that – namely, the CG.Blazor
NUGET package.
Note that the CG.Blazor
NUGET project includes a complete quick start sample that demonstrates everything in this blog post, plus some additional features of the CG.Blazor
library that are outside the scope of this blog post.
As always, all the source code for CG.Blazor
can be found on the CODEGATOR GitHub page, HERE.
The NUGET package itself can be downloaded directly from the CODEGATOR NUGET page, HERE.
Photo by Ayesha Firdaus on Unsplash