Dependency Injection: Method(s) and a Challenge

Dependency Injection: Solution Walkthrough
The implementations for both the C# and Swift implementations are very similar; so I'm only intending to walk through the C# code.

I however will provide two zipped projects for download:
  • C# Visual Studio project: using a simple singleton architecture for dependency injection. This project as mentioned previously only presents the output in the console; but is very easy to tie in with a View / ViewController.
  • Swift Xcode project: this is very similar in design the C# codebase, except that it includes both a implementation for construction and the singleton style dependency architecture. It also includes a vrey rudimentary implementation of a View / ViewController using a macOS NSTableView. The split between the ctor and singleton code resides in the their respective Xcode playgrounds.
I'll try to be as thorough as possible in my walkthrough, but feel free to PM me if anything doesn't make sense.

Code for each C# file will be posted inline with an explanation, but it may be simpler to just download the zip project file and follow along in Visual Studio.

The following dependencies are used for the C# solution:
  • LanguageExt.Core (Functional Data Types. for example: Either)
  • Newtonsoft.Json (Deserialisation of JSON encoding)
The C# first posts walking through the codebase, should start appearing just after 7pm.
 
Last edited:
First off we'll start with some simple configuration and helper methods.

Configuration:
There's not much to say about configuration; except to note that the Github API rejects any API connection where the httpUserAgent is not set; empty string is acceptable ;).

File: Config.cs
Purpose: Central location for configuration
C#:
using System;
namespace GithubRepo
{
  public static class Config
  {
    public static string user = "google";
    public static string baseAPI = "https://api.github.com/users/";
    public static Func<string, string> reposFor = (account) => baseAPI + account + "/repos";
    public static string httpUserAgent = "";
  }
}

DateTime Extensions:
Simple extension method to convert a TimeSpan duration to a unit of Days.

File: DatetimeExtensions.cs
Purpose: Extension method(s) for DateTime; conversion to duration of x Days
C#:
using System;
namespace GithubRepo
{
  public static class DateTimeExtensions
  {
    public static string ToDays(this DateTime from, DateTime to) {
      return string.Format("{0}d", (to - from).Days);
    }
  }
}

DateTime Extensions:
Helper methods for logging string encodings of exceptions.

File: Log.cs
Purpose: String format for exceptions.
C#:
using System;
using System.Net;
namespace GithubRepo
{
  public static class Log
  {
    public static string Default(Exception e)
    {
      return string.Format("message: {0}\ntrace: {1}\n", e.Message, e.StackTrace);
    }

    public static string Web(WebException e)
    {
      try {
        return Log.Default(e) + string.Format("response: {0}\nStatus: {1}\nResponseUri: {2}\n", e.Response, e.Status, e.Response.ResponseUri);
      } catch {
        return Log.Default(e);
      }
    }
  }
}

Newtonsoft.Json Helper Extensions:
Helper extension methods for JSON deserialisation.

File: TokenExtensions.cs
Purpose: Deserialise JSON keys to data types.
C#:
using System;
using Newtonsoft.Json.Linq;

namespace GithubRepo
{
  public static class JTokenExtensionMethods
  {
    private static JToken GetJToken(this JToken token, string key)
    {
      return GetJToken<JToken>(token, key, null);
    }

    public static string String(this JToken token, string key)
    {
      return token.GetJToken(key, "");
    }

    public static int Int(this JToken token, string key)
    {
      return token.GetJToken(key, -1);
    }

    public static bool Bool(this JToken token, string key)
    {
      return token.GetJToken(key, false);
    }

    public static double Double(this JToken token, string key)
    {
      return token.GetJToken(key, -1d);
    }

    public static DateTime Date(this JToken token, string key)
    {
      try {
        return DateTime.Parse(token[key].ToString());
      } catch (Exception e) {
        Console.WriteLine(e.StackTrace);
        return DateTime.Parse("1997/01/01 12:00");
      }
    }

    private static T GetJToken<T>(this JToken token, string key, T failValue)
    {
      try {
        return token[key].ToObject<T>();
      } catch {
        return failValue;
      }
    }
  }
}
 
Last edited:
Monadic Either Driven URLSession Object:
Process to download a JSON encoded string from a given API URL.

All exceptions are stored (as per norm) in the Left branch of the Either type. The monadic binding ensure that computations only continue when the previous result was Right i.e. correct. This basically avoids the need to adorn every bounded method with if x != null style boilerplate code.

File: URLSession.cs
Purpose: Monadic URLSession object to download a string encoded API result.
C#:
using System;
using System.IO;
using System.Net;
using LanguageExt;
using static LanguageExt.Prelude;

namespace GithubRepo
{
  public sealed class URLSession
  {
    private readonly Either<string, string> URL;
    private URLSession(string url)
    {
      URL = Right(url);
    }

    public static URLSession Of(string url)
    {
      return new URLSession(url);
    }

    public Either<string, string> Download() {
      return this.URL
                 .Bind(EscapeURL)
                 .Bind(ConnectToURL)
                 .Bind(ValidateConnection)
                 .Bind(DecodeStream);
    }

    private static Either<string, string> EscapeURL(string url) {
      try {
        return Right(Uri.EscapeUriString(url));
      } catch (Exception e) {
        return Left(Log.Default(e));
      }
    }

    private static Either<string, HttpWebRequest> ConnectToURL(string url) {
      try {
        return Right(WebRequest.Create(url) as HttpWebRequest);
      } catch (WebException e) {
        return Left(Log.Web(e));
      }
    }

    private static Either<string, HttpWebResponse> ValidateConnection(HttpWebRequest http) {
      try {
        http.UserAgent = Config.httpUserAgent;
        var response = http.GetResponse() as HttpWebResponse;
        if (response.StatusCode == HttpStatusCode.OK) {
          return Right(response);
        } else {
          return Left(string.Format("Unexpected HttpStatusCode: {0}", response.StatusCode));
        }
      } catch (WebException e) {
        return Left(Log.Web(e));
      }
    }

    private static Either<string, string> DecodeStream(HttpWebResponse http) {
      try {
        return Right((new StreamReader(http.GetResponseStream())).ReadToEnd());
      } catch (WebException e) {
        return Left(Log.Web(e));
      }
    }
  }
}

The body of the process is declaratively simplified in this single method:
C#:
   public Either<string, string> Download() {
      return this.URL
                 .Bind(EscapeURL)
                 .Bind(ConnectToURL)
                 .Bind(ValidateConnection)
                 .Bind(DecodeStream);
    }
 
Github & Repository Data Objects:
The Repository sub class of Github contains the fields that we wish to extract from the repo API request.

The Github class includes a Parse helper method to make it easier to process the deserialisation of the JSON string to a List<Repository> objects. It also is purposefully designed to wrap the Repository class and a single field called fetchRepos, a higher order computation field that essentially encapsulates a void completion handler that expects an input of Either<string, List<Repository>>. i.e. an input parameter that either encapsulates a:
  • Left failure of type string
  • or a Right result containing a List<Repository>.

The type signature of the fetchRepos field that incorporates a completion handler is a conscious design choice to allow us flexibility over what computations we can perform on the input result we receive to the fetchRepos method.

We have also in the default (empty) constructor method for the Github class ensured that the fetchRepos field has a default initialised value; i.e. by default it call the production code method:
C#:
  public static void FetchRepos(Action<Either<string, List<Repository>>> fetchRepos) {
    DataTask(Config.reposFor(Config.user), fetchRepos);
  }

Which in turn calls this DataTask method and ultimately asynchronously executes our custom completion handler.
C#:
  public static void DataTask(string url, Action<Either<string, List<Repository>>> completionHandler) {
    Task.Run(() => {
      return URLSession.Of(url).Download().Bind(Github.Parse);
    }).ContinueWith(t => {
      completionHandler(t.Result);
    });
  }
File: Github.cs
Purpose: Github class encapsulating our require repository fields, and a flexible completion handler.
C#:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using LanguageExt;
using static LanguageExt.Prelude;

namespace GithubRepo
{
  public sealed class Github
  {
    public class Repository {
      public readonly bool archived;
      public readonly string description;
      public readonly string htmlURL;
      public readonly string name;
      public readonly DateTime pushedAt;

      private Repository(bool archived, string description, string htmlURL, string name, DateTime pushedAt) {
        this.archived = archived;
        this.description = description;
        this.htmlURL = htmlURL;
        this.name = name;
        this.pushedAt = pushedAt;
      }

      private Repository() {}

      public static Repository Of(bool archived, string description, string htmlURL, string name, DateTime pushedAt) {
        return new Repository(archived, description, htmlURL, name, pushedAt);
      }

      override public string ToString()
      {
        return string.Format("archived: {0}, description: {1}, htmlURL: {2}, name: {3}, pushedAt: {4}", archived, description, htmlURL, name, pushedAt.ToDays(Current.Instance.environment.date));
      }
    }

    public static Either<string, List<Repository>> Parse(string json)
    {
      try {
        var tokens = JArray.Parse(json);
        var repositories = new List<Repository>();
        foreach (JToken t in tokens) {
          repositories.Add(Repository.Of(
            t.Bool("archived"),
            t.String("description"),
            t.String("htmlUrl"),
            t.String("name"),
            t.Date("pushed_at")));
        }
        return Right(repositories);
      } catch (Exception e) {
        return Left(Log.Default(e));
      }
    }

    public Action<Action<Either<string, List<Repository>>>> fetchRepos = FetchRepos;

    public Github() { }

    public Github(Action<Action<Either<string, List<Repository>>>> fetchRepos) {
      this.fetchRepos = fetchRepos;
    }

    public static void FetchRepos(Action<Either<string, List<Repository>>> fetchRepos) {
      DataTask(Config.reposFor(Config.user), fetchRepos);
    }

    public static void DataTask(string url, Action<Either<string, List<Repository>>> completionHandler) {
      Task.Run(() => {
        return URLSession.Of(url).Download().Bind(Github.Parse);
      }).ContinueWith(t => {
        completionHandler(t.Result);
      });
    }
  }
}
The Task class is quite similar in operation to a Future in other languages.
 
Last edited:
Defining our Dependencies
We have two primary mutable dependencies in this solution:
  • The network bound Github API response
  • and the current date; from which we compute how many days have elapsed since the project was last pushed.
Both of these inputs make it difficult to test our code; because in the case of the network request; the response values are not fixed and it also can fail for a number of reasons. Secondly the "current" date will affect the computed number of days elapsed since the last push to a project. For validating the consistency of our code in testing; we need the test output to have a verifiably fixed outcome.

Naturally inline with this thread; a solution to this testing challenge is to use Dependency Injection; because aside from the other benefits; it will allow us to inject Mock dependencies for testing purposes.

Let's introduce the Environment class:
This class similar to its Reader Monad namesake, defines an input Environment that contains our dependencies.

C#:
using System;
namespace GithubRepo
{
  public sealed class Environment
  {
    public Github github = new Github();
    public DateTime date = DateTime.Now;

    public Environment() { }

    public Environment(Github github, DateTime? date = null) {
      this.github = github;
      this.date = date ?? DateTime.Now;
    }
  }
}
As you can see it's a very simple class; with two fields; 1 for the Github dependency class (i.e. output from our network API request) and a date, that represents the current system date -- because its impossible to have a fixed response when determining elapsed time against a ticking current time clock.

Next let's encapsulate this in a Singleton class that will be available across the application:

C#:
using System;
namespace GithubRepo
{
  public sealed class Current
  {
    public Environment environment = new Environment();

    private Current() { }
    public static Current Instance { get { return Internal.instance; } }
    private class Internal
    {
      private Internal() { }
      internal static readonly Current instance = new Current();
    }
  }
}
The only things to note about this Singleton object is that it lazy self instantiates and will always ensure that all requesters receive the same result; which in this case is an Instance to our Environment class that defines our dependencies.
 
Last edited:
Ok, now let's run this in Program.cs
C#:
using System;
using System.Collections.Generic;
using static LanguageExt.Prelude;

namespace GithubRepo
{

  class MainClass
  {
    public static void Main(string[] args)
    {
      var current = Current.Instance;
   
      // View Binding: Code to bind the results in our view controller
      // simple pattern matching over the Either type; both outcomes output to the console
      current.environment.github.fetchRepos(e => {
        e.Match(gl => gl.ForEach(Console.WriteLine), ge => Console.WriteLine("We crashed: {0}", ge));
      });

      // Pause processing on the current thread to allow async processes to complete
      // Simulated run loop; not required once a view and viewcontroller is used.
      System.Threading.Thread.Sleep(20000);
    }
  }
}
Note:
The reason for this line: System.Threading.Thread.Sleep(20000); is simply to allow time for the completion of the asynchronous network request; in a UI application this would not be required as you would typically present some style of processing spinner to show that the network request is pending and replace that with the result when it completes.

Example of output:
Code:
archived: False, description: , htmlURL: , name: 0x0g-2018-badge, pushedAt: 31d
archived: False, description: An R package for AB testing leveraging pre-period information, htmlURL: , name: abpackage, pushedAt: 189d
archived: False, description: , htmlURL: , name: abstreet, pushedAt: 88d
archived: False, description: Testing library for JUnit4 and Guice., htmlURL: , name: acai, pushedAt: 35d
archived: False, description: Explore accessibility tree of Java Access Bridge enabled applications, htmlURL: , name: access-bridge-explorer, pushedAt: 794d
archived: False, description: , htmlURL: , name: Accessibility-Test-Framework-for-Android, pushedAt: 163d
archived: False, description: , htmlURL: , name: account-provisioning-for-google-apps, pushedAt: 201d
archived: False, description: A simple ACME command line tool without 3rd party deps!, htmlURL: , name: acme, pushedAt: 331d
archived: False, description: , htmlURL: , name: active-learning, pushedAt: 297d
archived: False, description: , htmlURL: , name: active-qa, pushedAt: 52d
archived: False, description: , htmlURL: , name: adapt-googleanalytics, pushedAt: 604d
archived: False, description: , htmlURL: , name: adb-sync, pushedAt: 243d
archived: False, description: A program which ensures source code files have copyright license headers by scanning directory patterns recursively, htmlURL: , name: addlicense, pushedAt: 32d
archived: False, description: , htmlURL: , name: address-geocoder-js, pushedAt: 498d
archived: False, description: The Advanced Forensic File Format. NOTE: This project has been split into C and Python projects and moved to https://github.com/Velocidex/pyaff4 and https://github.com/Velocidex/c-aff4, htmlURL: , name: aff4, pushedAt: 35d
archived: False, description: A simple UI layer for visualizing Google Analytics v4 hits on Android apps, htmlURL: , name: agata, pushedAt: 516d
archived: False, description: Reactive Programming for Android, htmlURL: , name: agera, pushedAt: 185d
archived: False, description: , htmlURL: , name: ahdlc, pushedAt: 126d
archived: True, description: A TensorFlow integration for deeplearn.js which allows to load tensorflow checkpoint files directly., htmlURL: , name: aiyprojects-deeplearn-tensorflow, pushedAt: 221d
archived: False, description:  API libraries, samples, and system images for AIY Projects (Voice Kit and Vision Kit), htmlURL: , name: aiyprojects-raspbian, pushedAt: 4d
archived: False, description: Send Prometheus Alerts to IRC using Webhooks, htmlURL: , name: alertmanager-irc-relay, pushedAt: 85d
archived: False, description: A Java agent that rewrites bytecode to instrument allocation sites, htmlURL: , name: allocation-instrumenter, pushedAt: 87d
archived: False, description: , htmlURL: , name: amp-client-id-library, pushedAt: 206d
archived: False, description: A simple, dependency-free blog that uses a Progressive Web App (PWA) to show Accellerated Mobile Pages (AMP)., htmlURL: , name: amp-pwa-demo, pushedAt: 514d
archived: False, description:  A collection of AMP tools making it easier to publish and host AMP pages. , htmlURL: , name: amp-toolbox, pushedAt: 25d
archived: False, description: , htmlURL: , name: amss, pushedAt: 84d
archived: False, description: Retrieve Intel AMT's Audit Log from a Linux machine without knowing the admin user's password., htmlURL: , name: amt-forensics, pushedAt: 318d
archived: False, description: Reverse engineering, Malware and goodware analysis of Android applications ... and more (ninja !), htmlURL: , name: androguard, pushedAt: 763d
archived: False, description: ArscBlamer is a command-line tool that can parse an Android app's resources.arsc file and extract useful, actionable information about its contents, htmlURL: , name: android-arscblamer, pushedAt: 871d
archived: False, description: A tool to analyse your APK (Android app), htmlURL: , name: android-classyshark, pushedAt: 37d
 
Last edited:
Ok so let's see how this singleton / Environment class style of dependency injection can facilitate mocking of fixed outcomes for unit testing, offline coding and / or screenshot testing.

Let's override both our data and Github API request:
C#:
using System;
using System.Collections.Generic;
using static LanguageExt.Prelude;

namespace GithubRepo
{
  class MainClass
  {
    public static void Main(string[] args)
    {
      var current = Current.Instance;

      // override the current date
      current.environment.date = DateTime.Parse("2018/10/12 11:45");

      // Mock Success : Inline code example
      current.environment.github.fetchRepos = callback => {
        callback(Right(new List<Github.Repository>(){
          Github.Repository.Of(
            false,
            "Mock Repo 1",
            "https://github.com/mock/mockrepo1",
            "mockrepo1",
            DateTime.Parse("2018/09/06 7:21"))
          })
        );
      };

      // View Binding: Code to bind the results in our view controller
      current.environment.github.fetchRepos(e => {
        e.Match(gl => gl.ForEach(Console.WriteLine), ge => Console.WriteLine("We crashed: {0}", ge));
      });

      // Pause processing on the current thread to allow async processes to complete
      // Simulated run loop; not required once a view and viewcontroller is used.
      System.Threading.Thread.Sleep(100000);
    }
  }
}

... and that's all there is to it; we can easy enough; substitute mock responses for our Enviroment dependencies by overriding the fields encapsulate by our Current singleton.

The output from this is (guaranteed consistent):
Code:
archived: False, description: Mock Repo 1, htmlURL: https://github.com/mock/mockrepo1, name: mockrepo1, pushedAt: 36d


Here's another example with a mock failure scenario:
C#:
using System;
using System.Collections.Generic;
using static LanguageExt.Prelude;

namespace GithubRepo
{
  class MainClass
  {
    public static void Main(string[] args)
    {
      var current = Current.Instance;

      // Mock Failure : Inline code example
      current.environment.github.fetchRepos = callback => {
        callback(Left("Mock Failure"));
      };

      // View Binding: Code to bind the results in our view controller
      current.environment.github.fetchRepos(e => {
        e.Match(gl => gl.ForEach(Console.WriteLine), ge => Console.WriteLine("We crashed: {0}", ge));
      });

      // Pause processing on the current thread to allow async processes to complete
      // Simulated run loop; not required once a view and viewcontroller is used.
      System.Threading.Thread.Sleep(100000);
    }
  }
}

The output from this failure mock is (guaranteed consistent):
Code:
We crashed: Mock Failure
 
Last edited:
What if we have multiple mock scenarios that we'd like to encapsulate?
To encapsulate multiple easy to repeat mock scenarios; you probably want to wrap the mocks in a class; here's a simple example of how this can be done:

File: GithubMock.cs
Purpose: Github Mock class that encapsulates fixed outcome scenarios that can be used for testing and/or offline development.
C#:
using System;
using System.Collections.Generic;
using LanguageExt;
using static LanguageExt.Prelude;

namespace GithubRepo
{
  public static class GithubMock
  {
    private static void FetchSuccess(Action<Either<string, List<Github.Repository>>> fetchRepos)
    {
      fetchRepos(Right(new List<Github.Repository>(){
        Github.Repository.Of(
          false,
          "Mock Repo 1",
          "https://github.com/mock/mockrepo1",
          "mockrepo1",
          DateTime.Parse("2018/09/06 7:21")),
        Github.Repository.Of(
          false,
          "Mock Repo 2",
          "https://github.com/mock/mockrepo2",
          "mockrepo2",
          DateTime.Parse("2018/09/04 9:45"))
        })
      );
    }

    private static void FetchFail(Action<Either<string, List<Github.Repository>>> fetchRepos)
    {
      fetchRepos(Left("Mock Failure"));
    }

    public static Environment Success = new Environment(new Github(GithubMock.FetchSuccess), DateTime.Parse("2018/09/06 12:00"));
    public static Environment Fail = new Environment(new Github(GithubMock.FetchFail));
  }
}

Then in our Program.cs
We can easily switch between production, success and failure scenarios in 1 line of code.
C#:
using System;
using System.Collections.Generic;
using static LanguageExt.Prelude;

namespace GithubRepo
{
  class MainClass
  {
    public static void Main(string[] args)
    {
      var current = Current.Instance;

      // Success mock
      current.environment = GithubMock.Success;
  
      // Failure mock
      // current.environment = GithubMock.Fail;
  
      // View Binding: Code to bind the results in our view controller
      current.environment.github.fetchRepos(e => {
        e.Match(gl => gl.ForEach(Console.WriteLine), ge => Console.WriteLine("We crashed: {0}", ge));
      });

      // Pause processing on the current thread to allow async processes to complete
      // Simulated run loop; not required once a view and viewcontroller is used.
      System.Threading.Thread.Sleep(100000);
    }
  }
}
As you can hopefully see this allows us to more consistently override the environment for Success and Fail scenarios, or any others than we require. Naturally to run the production code we simply comment out the mocking assignment lines.
 
Last edited:
Project bundle (zip files) links:

Anyway that almost concludes this thread; only parts remaining are a brief overview of dependency injection in respect of Partial Function Application. PM if you think I've missed anything or if you have any questions; code related or otherwise.
 
Last edited:
Top
Sign up to the MyBroadband newsletter
X