Blazor MVVM

Blazor MVVM

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 INotifyPropertyChangedinterface. 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