Lazy Dependencies and Interfacing Refit with MvvmCross and Prism

Lazy Dependencies and Interfacing Refit with MvvmCross and Prism

Refit is one of those libraries that makes your life as a developer having to write code to work with services that much easier. By way of an example let’s look at the GET List Users method from the sample REST service available at https://reqres.in/

image

Running the response json through JsonToCSharp we get a class model similar to:

public class User
{
     public int id { get; set; }


    public string first_name { get; set; }


    public string last_name { get; set; }


    public string avatar { get; set; }
}


public class UserList
{
     public int page { get; set; }


    public int per_page { get; set; }


    public int total { get; set; }


    public int total_pages { get; set; }


    public List<User> data { get; set; }
}

Next we define the interface for our service (Note that the Get attribute defines the path, including the page parameter that’s passed into the RetrieveUsers method):

public interface IUserService
{
     [Get(“/api/users?page={page}”)]
     Task<UserList> RetrieveUsers(int page);
}

Of course, we need to add a reference to the Refit NuGet package to our application. Lastly we of course need to retrieve an instance of the UserService, which is where the magic comes in – behind the scene, Refit generates the appropriate implementation for the IUserService so that all you need to do is retrieve it using code similar to:

var userService = RestService.For<IUserService>(“https://reqres.in/”);

This is all well and good but when it comes to writing applications using Prism or MvvmCross, what we really want to be able to do is have the IUserService injected into the constructor of our view model.

Injecting Refit with MvvmCross

We’ll start with MvvmCross (I’ve create a brand new project using MvxScaffold to get me started) and I’ve modified the HomeViewModel to add IUserService as a parameter to the constructor. The service is then used in the ViewAppeared method to retrieve the first page of users.

public class HomeViewModel : BaseViewModel
{
     private IUserService UserService { get; }
     public HomeViewModel(IUserService userService)
     {
         UserService = userService;
     }


    public override async void ViewAppeared()
     {
         base.ViewAppeared();


        var users = await UserService.RetrieveUsers(1);
         Debug.WriteLine(users.data.Count);

     }
}

Now the trick is that we need to register the instance of the IUserService with MvvmCross which we can do at the end of the Initialize method of App.cs in the Core project. We don’t want all of the services to be created at start-up, so instead we register the type using the LazyConstructAndRegisterSingleton extension method: For example:

public override void Initialize()
{
     …
     Mvx.IoCProvider.LazyConstructAndRegisterSingleton<IUserService>
         (() => RestService.For<IUserService>(“https://reqres.in/”));
}

There’s a couple of issues here:

– String literal for the host url

– No ability to override the HttpClient used by the IUserService implementation

Let’s see how we can resolve this by registering some other instances that we can use as part of creating the IUserService. One of the other options for the For method is that we can supply a HttpClient that already has a BaseAddress set. We can also set a HttpMessageHandler on the HttpClient instance that will allow us to customise the behaviour of the HttpClient. Unfortunately the only interface that the HttpClient implements is IDisposable. To get around this we wrap the HttpClient instance in a service which returns the instance as a property:

public interface IHttpService
{
     HttpClient HttpClient { get; }
}


public class HttpService : IHttpService
{
     public HttpService(ICustomHttpMessageHandler customHttpMessageHandler, IServiceOptions options)
     {
         HttpClient = new HttpClient(customHttpMessageHandler as HttpMessageHandler)
         {
             BaseAddress = new Uri(options.BaseUrl)
         };
     }
     public HttpClient HttpClient { get; }
}

Note that in this example the HttpService implementation relies on an ICustomHttpMessageHandler and IServiceOptions. These are here to illustrate the chaining of dependencies that can all be lazy loaded.

public interface ICustomHttpMessageHandler
{
}
public class CustomHttpMessageHandler : DelegatingHandler, ICustomHttpMessageHandler
{
     public CustomHttpMessageHandler(IServiceOptions options)
     {
         InnerHandler = new HttpClientHandler()
         {
             AutomaticDecompression = options.Compression
         };
     }
}
public interface IServiceOptions
{
     string BaseUrl { get; }
     DecompressionMethods Compression { get; }
}
public class ServiceOptions : IServiceOptions
{
     public string BaseUrl { get; set; }
     public DecompressionMethods Compression { get; set; }
}

The final registration code in the Initialize method is:

public override void Initialize()
{
     …
     Mvx.IoCProvider.LazyConstructAndRegisterSingleton<IServiceOptions>(() => new ServiceOptions()
     {
         BaseUrl = “https://reqres.in”,
         Compression = DecompressionMethods.Deflate | DecompressionMethods.GZip
     });
     Mvx.IoCProvider.LazyConstructAndRegisterSingleton<ICustomHttpMessageHandler, CustomHttpMessageHandler>();
     Mvx.IoCProvider.LazyConstructAndRegisterSingleton<IHttpService, HttpService>();
     Mvx.IoCProvider.LazyConstructAndRegisterSingleton<IUserService, IHttpService>
         (service => RestService.For<IUserService>(service.HttpClient));
}

Injecting Refit with Prism

I’m not going to cover over generating the classes and IUserService interface. Instead we’re going to look at how we can register and then make use of the IUserService within Prism. One of the limitations of Prism is that there is no way to register a singleton for lazy creation using a function callback. This means we’d need to register an actual implementation of the IUserService which would mean creating the service instance on app startup instead of when the service is first used. Luckily there is a work around that makes use of the Lazy<T> class. Again, the Lazy<T> doesn’t implement an interface, so we’ll inherit from Lazy<T> and implement a new interface ILazyDependency.

public interface ILazyDependency<T>
{
     T Value { get; }
}
public class LazyDependency<T> : Lazy<T>, ILazyDependency<T>
{
     public LazyDependency(Func<T> valueFactory) : base(valueFactory)
     {
     }
}

And now here’s the registration code (added to the RegisterTypes method in App.xaml.cs):

protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
     …
     containerRegistry.RegisterInstance<IServiceOptions>(new ServiceOptions()
     {
         BaseUrl = “https://reqres.in”,
         Compression = DecompressionMethods.Deflate | DecompressionMethods.GZip
     });
     containerRegistry.RegisterSingleton<ICustomHttpMessageHandler, CustomHttpMessageHandler>();
     containerRegistry.RegisterSingleton<IHttpService, HttpService>();
     containerRegistry.RegisterInstance<ILazyDependency<IUserService>>(
         new LazyDependency<IUserService>(() =>
         RestService.For<IUserService>(
             PrismApplicationBase.Current.Container.Resolve<IHttpService>().HttpClient)
         ));
}

This code looks very similar to the registration code when using MvvmCross. However, the difference is that instead of registering IUserService, it registers ILazyDependency<IUserService>. This is important to note because we need to remember to request the correct interface instance in our view model:

public class MainPageViewModel : ViewModelBase
{
     private IUserService UserService { get; }
     public MainPageViewModel(INavigationService navigationService, ILazyDependency<IUserService> userService)
         : base(navigationService)
     {
         UserService = userService.Value;
         Title = “Main Page”;
     }
     public override async void OnNavigatedTo(INavigationParameters parameters)
     {
         base.OnNavigatedTo(parameters);


        var users = await UserService.RetrieveUsers(0);
         Debug.WriteLine(users.data.Count);
     }
}

And there you have it, we’ve abstracted the Refit implementation away from our view models, making them ready for testing.

#The following code was added to the MvvmCross project to simplify the registration of the IUserService:

public static class MvxIoCContainerExtensions
{
     private static Func<TInterface> CreateResolver<TInterface, TParameter1>(
         this IMvxIoCProvider ioc,
             Func<TParameter1, TInterface> typedConstructor)
             where TInterface : class
             where TParameter1 : class
     {
         return () =>
         {
             ioc.TryResolve(typeof(TParameter1), out var parameter1);
             return typedConstructor((TParameter1)parameter1);
         };
     }


    public static void LazyConstructAndRegisterSingleton<TInterface, TParameter1>(this IMvxIoCProvider ioc, Func<TParameter1, TInterface> constructor)
         where TInterface : class
         where TParameter1 : class
     {
         var resolver = ioc.CreateResolver(constructor);
         ioc.RegisterSingleton(resolver);
     }
}

Note: This code has already been merged into MvvmCross but at time of writing isn’t in the stable release.