This article is the fourth entry in a series about an implementation of Model View ViewModel in ASP.Net client side Blazor. If you would like to start reading the series from the beginning the first article is located here:

The source code for this project is available at GitHub. The code from the starting point of this article is in the ArticleThree branch and the 4.3 release. The code from the end of this article is the ArticleFour branch and the 4.4 release.

In this article we are going to examine several ways to use the built in .Net Core DI container in our Blazor client. We have already seen in the previous articles how we can register a class as an interface and then inject it through the constructor of a class or as a property in a View using @inject. Today we are going to look at two scenarios that we have not discussed yet. The first scenario is registering the same class twice as two different interfaces. The second scenario is registering two different classes as the same interface. We will keep the implementations very simple to keep this article from growing too large. Afterwards we will discuss one way to automatically register services in Startup.cs.

New Business Requirements

Our imaginary employer has run a cost benefit analysis and has decided to only use the National Weather Service API going forward. We need to change the system to provide the Basic, Enhanced and Hourly forecasts using only this service. We are going to take this opportunity to refactor and improve our Model while putting this change into place.

If we go back and look at FetchDataModel.cs we have 3 methods that we expose publicly. Each one of those methods contains a hard-coded reference to an external API and two of them return a class and not an interface. This code will be hard to write proper tests for and could become painful to support down the road. We also can improve our performance by not calling the API every time the user navigates between the daily and hourly forecast.

Our approach today is going to make two children for the Model, one for the daily forecast and one for hourly, and let Dependency Injection help us use them to meet our needs. We are also going to add private variables to store the forecast after it his been retrieved to avoid redundant calls to the API.

First Child Model

Before we add the model we are going to prep our code a bit by declaring an interface where we are currently using a class throughout our code. In the Shared project go into WeatherDotGovForecast and extract an interface:

public interface IWeatherDotGovForecast

{

Geometry geometry { get; set; }

Properties properties { get; set; }

string type { get; set; }

}

We’ll start with our daily forecast so go into the Models folder and create a new class DailyForecastModel.cs. The first thing we are going to do is set up the new child Model to receive the HttpClient through constructor injection and save a reference to it. Next we will add a single method to the class to pull down our daily forecast:

With the data acquisition taken care of we’ll need methods to return our enhanced and basic forecasts. The enhanced forecast is a simple pass-through method:

public async Task<IWeatherDotGovForecast> RetrieveFullForecast()

{

var forecast = await RetrieveForecast();

return forecast;

}

The basic forecast takes a bit more work. We only need a subset of the retrieved information and we also need only want one forecast per day. We are going to use a little bit of Linq to pull only the odd numbered periods out of the forecast and then pull just the data points we are looking for from that result set. The method we will create to do that looks like this:

public async Task<IWeatherForecast[]> RetrieveBasicForecast()

{

var forecast = await RetrieveForecast();

var basicForecast = (from Period p in forecast.properties.periods

where p.number % 2 == 0 select new WeatherForecast

{

Date = p.startTime,

TemperatureC = (int)((p.temperature - 32) * 5 / 9),

Summary = p.shortForecast

}).ToArray();

return basicForecast;

}

We could register this class as an interface as is and have the parent Model make two different method calls to get the data it needs (and in a real world application we would do that). For today’s exercise we are going to register this class as two different interfaces and let the model not know it is looking at the same class to make the different method calls. In the real world this is much more likely to be used when a class needs to be seen differently in different parts of an application, not within one class.

Our first interface will expose the our full forecast:

public interface IFullForecastModel

{

Task<IWeatherDotGovForecast> RetrieveFullForecast();

}

The second interface will expose the basic forecast:

public interface IBasicForecastModel

{

Task<IWeatherForecast[]> RetrieveBasicForecast();

}

Now we can update our class with these interfaces after which it will look like this:

With our new class complete we can go into ConfigureServices() in Startup.cs and register our new class under both interfaces.

services.AddTransient<IFullForecastModel, DailyForecastModel>(); services.AddTransient<IBasicForecastModel, DailyForecastModel>();

Using the first Child model

With everything now in place we can start making use of our new child in the parent Model. Go into FetchDataModel.cs and make the following changes. Add two new private variables to hold references to instances of the child Model

private IFullForecastModel _dailyForecast; private IBasicForecastModel _basicForecast;

and update the constructor of the parent to receive both instances through injection and save references to them.

public FetchDataModel(HttpClient http, IFullForecastModel dailyForecast, IBasicForecastModel basicForecast)

{

_http = http;

_dailyForecast = dailyForecast;

_basicForecast = basicForecast;

}

Now lets update the methods that the ViewModel calls into to use these new references. While we are at it we will improve performance by only making the API call if we haven’t already done it yet. First we will update the method for getting our basic forecast

public async Task RetrieveForecastsAsync()

{

if (_weatherForecasts == null)

{

_weatherForecasts = await

_basicForecast.RetrieveBasicForecast();

}

}

Then we will do the same thing for our enhanced forecast.

public async Task RetrieveRealForecastAsync()

{

if (_realWeatherForecast == null)

{

_realWeatherForecast = await

_dailyForecast.RetrieveFullForecast();

}

}

After these updates are done we will have a few type mismatches where we need to change a class declaration to an interface declaration. The updated Model now looks like this:

With this complete we can launch the application and see that it still works as expected. We have now injected the same class into our application through two different interfaces.

Second Child Model

For the Model for the hourly forecast we will go back into our Models folder and create another new class HourlyForecastModel.cs . This class is going to have much of the same content as our the first child model except it is only going to implement the “Full” interface and will only have once method. The class will look like this to start out:

The class will also need to be registered`in Startup.cs`.

services.AddTransient<IFullForecastModel, HourlyForecastModel>();

Everything is pretty standard up to this point. However, we have now registered two different implementations of IFullForecastModel so what happens when we run our application? The short answer is .Net Core’s DI container will return the last registered instance of an interface. This means as our code is now it will always inject the HourlyForecastModel . That is not what we want so how do we get around that? We are going to have the registered services tell us what they can do so we can pick which one we want to receive.

To start off we are going to make an enum that allows us to distinguish between the different registered services and expose it as a public property. Add it somewhere in the BlazorMVVMSample.Client.Models namespace.

public enum supports { daily, hourly}

Now add the same property to both child Models

public supports Supports { get; private set; }

and pull it up to the IFullForecastModel interface.

supports Supports { get; }

All that is left it to assign the needed support type in the constructors of the child Models

Supports = supports.daily; or Supports = supports.hourly;

Injecting the right Model

Now that we’ve made this change we can update the Parent to take advantage of this and pick the correct Model. First we need to update our constructor definition to take an IEnumerable<IFullForceastModel> to get every registered instance instead of just the last one.

public FetchDataModel(HttpClient http, IEnumerable<IFullForecastModel> fullForecasts, IBasicForecastModel basicForecast)

Once we have access to all of them we can use Linq to assign the one we want in our constructor like this:

_dailyForecast = fullForecasts

.Where( f => f.Supports == supports.daily )

.First();

The premise here is that we are looking through our registered services to find one that matches our needs. We only have two classes registered and have very simple criteria. This can just as easily be applied to a long list of services and have multiple criteria matched when searching for an appropriate service. Now that we have injection worked out we can make use of our second child model. We need to declare a private variable in FetchDataModel

private IFullForecastModel _hourlyForecast;

populate it inside of the constructor

_hourlyForecast = fullForecasts.Where(f => f.Supports == supports.hourly).First();

and update the method that retrieves the hourly forecast.

public async Task RetrieveHourlyForecastAsync()

{

if (_hourlyWeatherForecast == null)

{

_hourlyWeatherForecast = await

_hourlyForecast.RetrieveFullForecast();

}

}

After that is in place we will have a few more type mismatches where we will need to replace a class declaration with the interface and we will be all set. The FetchDataModel now looks like this:

We can now launch the application once again and it is fully functional. We are no longer calling out to the old API , we injected one class using two different interfaces and we injected two different classes using the same interface.

Automatic registration of dependencies

If we look at the contents of our ConfigureServices() method it now looks like this:

services.AddTransient<IFetchDataViewModel, FetchDataViewModel>(); services.AddTransient<IFetchDataModel, FetchDataModel>(); services.AddTransient<IBasicForecastViewModel, BasicForecastViewModel>(); services.AddTransient<IFullForecastModel, DailyForecastModel>(); services.AddTransient<IBasicForecastModel, DailyForecastModel>(); services.AddTransient<IFullForecastModel, HourlyForecastModel>();

What happens when we build a large application that needs to register dozens or hundreds of classes? We add another line to this method every time we need to register another interface or we can set our application up to do it automatically. Users of DI frameworks like AutoFac and StructureMap are already doing it and .Net Core DI container users are doing it as well.

To register our services in code we will need to use reflection. First we will need to identify which assembly or assemblies we want to search through. After that we can identify the classes we want and register them. To do this well we will want to come up with a naming scheme for our classes and assemblies that ensures we are only registering our classes and only registering them once. In our application if we use a criteria like `FullName.Contains(“Model”)` we will be registering our Models and our ViewModels which may not be what we intended.

The first step we will take will be to get a reference to our client assembly:

var assembly = AppDomain.CurrentDomain.GetAssemblies()

.Where(a => a

.FullName.StartsWith("BlazorMVVM.Client"))

.First();

That statement looks at all of the assemblies and pulls all of the assemblies with a matching name. We already know ahead of time that we will only have one match so we can avoid receiving an array with .First() . The next step is to get all of the classes I want to register. I don’t want to accidentally grab classes I don’t want so I am going to use Visual Studio to rename all of my models to end with _model . This will make it much easier for me to grab them to register. Luckily I have them on my screen in Startup.cs so it only takes a few clicks to rename the parent and two child Models. With that done I can grab them all like this:

var classes = assembly.ExportedTypes .Where(a => a.FullName.Contains("_Model"));

Now I just need to loop through the classes and then loop through their interfaces and register each one like this:

foreach (Type t in classes)

{

foreach (Type i in t.GetInterfaces())

{

services.AddTransient(i, t);

}

}

We now only have two manually registered ViewModels left in ConfigureServices() and a block of code that has automatically registered all of our Models. The updated ConfigureServices() will look like this:

Once again when we launch our application it works just like it did when we manually registered every service.

In a larger application you will probably take that code out of ConfigureServices and make it a separate method or even another class entirely. You could be using different criteria for different assemblies and you may have other requirements like only registering public classes or interfaces. As long as you plan ahead and stick to your own guidelines you’ll easily be able to register all of your dependencies.

Wrapping Up

Even though we did not change anything visible to the user we were able to eliminate an unneeded API and make some improvements to our implementation. We saw how to use Dependency Injection with a class implementing two different interfaces as well as with an interface that is implemented by two different classes. We also saw how easy it is to automate registration of our services in Startup.cs with just a few lines of code.

This series will also continue with additional articles. Leave feedback or send me a tweet @LouisHendricks if there is a topic you would like to see covered. Update: The fifth article has been published: