Tuesday, July 02, 2013

Service Locator vs Dependency Injection

While I've been exclusively using DI since I wrote articles like this one or this one (and several others on the subject of Service Locators), I think I finally figured out how to explain why DI is better: it's because it forces a design change in classes.

Start with the normal mess, where your method directly initializes a concrete class, let's say in the constructor:

  public class DoesSomethingThatRequiresLogging
  {
    public DoesSomethingThatRequiresLogging()
    {
      logger = new FileLogger(@"c:\temp\log.txt");
    }

    //

    private FileLogger logger;
  }

Your methods happily call logger.Log(...) and all is good with the world.

However, at some point you have to test this, or reuse it, or change it to log to a database table instead of a file. Ouch.

The first solution is the use of a service locator. Create the logger outside of the constructor and use it as required:

  // in Main
  ServiceLocator.Register(new FileLogger(@"c:\temp\log.txt"));

  // in constructor
  logger = ServiceLocator.Resolve<FileLogger>();

This removes the creation of the object from the class, but it still leaves it with a dependency on the concrete, file logging implementation. We can change that by using interfaces.

  public interface ILogger
  {
    void Log(string message);
  }

  public class FileLogger: ILogger
  {
    ...
  }

  // in Main
  ServiceLocator.Register(new FileLogger(@"c:\temp\log.txt"));

  // in constructor
  logger = ServiceLocator.Resolve<ILogger>();

Now the logger can be changed easily to a DatabaseLogger, without affecting the class in any way. That's huge - it solves two of the three problems we started with (the class can be tested and the logger can be changed to log to some other medium). There's still the problem of reuse, though. We can't determine, just by looking at the class, what other objects it will need (what its dependencies are). It is possible to try to use it in another project only to discover that it crashes in some cases because IDoSomethingHidden wasn't registered in the ServiceLocator.

That leads us to the next step: make all dependencies explicit. This is usually done with constructor injection but there are alternatives (like property injection, used normally for dependencies that aren't required: like a class that will log something if there's a logger but will happily work without one otherwise):

  public class DoesSomethingThatRequiresLogging
  {
    public DoesSomethingThatRequiresLogging(ILogger logger)
    {
      this.logger = logger;
    }

    //

    private FileLogger logger;
  }

  // in Main
  var logger = new FileLogger(...);
  var instance = new DoesSomethingThatRequiresLogging(logger);

  // in tests
  var logger = new Mock<ILogger>();
  var instance = new DoesSomethingThatRequiresLogging(logger.Object);

(I used Moq in the testing example.)

The final step would be to use some kind of dependency injection container (I use Munq) but that's mostly required when objects are not explicitly created by you (like the controllers in the case of the MVC framework).

So, there you have it: the reason for the road from regular spaghetti code to service locator to dependency injection (to maybe a DI container).

Posting dynamic Master / Detail forms with Knockout

I have the following dynamic form in my GitHub Inventory project:

I created the form with KnockoutJS, using some ideas from this article. The detail view looks like this:

    <table id="acquisition_items" class="jtable">
      <thead>
        <tr>
          <th>Product Name</th>
          <th>Quantity</th>
          <th>Price</th>
          <th>Value</th>
          <th><button data-bind="click: addItem">[+]</button></th>
        </tr>
      </thead>
      <tbody data-bind="foreach: items">
        <tr>
          <td>
            <input data-bind="value: ProductName" type="text" value="" />
          </td>
          <td>
            <input data-bind="value: Quantity" type="text" value="" />
          </td>
          <td>
            <input data-bind="value: Price" type="text" value="" />
          </td>
          <td>
            <input data-bind="value: Value" readonly="readonly" type="text" value="" />
          </td>
          <td>
            <button data-bind="click: $parent.delItem">[--]</button>
          </td>
        </tr>
      </tbody>
    </table>

and the corresponding ViewModel:

    function ViewModel() {
      var self = this;

      self.CompanyName = ko.observable();
      self.Date = ko.observable();
      self.items = ko.observableArray([new Item()]);

      self.addItem = function() {
        self.items.push(new Item());
      };

      self.delItem = function(item) {
        self.items.remove(item);
        if (self.items().length == 0)
          self.addItem();
      };
    }

    function Item() {
      var self = this;

      self.ProductName = ko.observable();
      self.Quantity = ko.observable();
      self.Price = ko.observable();
      self.Value = ko.computed(function() {
        try {
          var result = parseFloat(self.Quantity()) * parseFloat(self.Price());
          return isNaN(result) ? 0 : result;
        } catch(ex) {
          return 0;
        }
      });
    }

(Note that I will re-create an empty record if the last one gets deleted.)

Now, as Pete says in the article, the usual way of POST-ing a knockout viewmodel to the server is to serialize it and post that - however, like him, I would prefer to use a regular POST / refresh. (I like getting the feedback of the browser refreshing the page.) There is the problem of having the correct field IDs/names (in the HTML) so that the MVC model binder maps them correctly to the model. Pete solved that with a knockout plugin but, as he says, it has some disadvantages:

  • you need to have an empty item in the items list; that would be ok, but
  • the root of the repeating groups has to be a fieldset

Since I wanted my repeating records in a table and I wanted a more generic solution, I came up with the DataBinding extension method (and a helper to get the property name from an Expression<Func<U, V>>):

    public static string DataBinding<TModel, U, V>(this HtmlHelper<TModel> helper,
      Expression<Func<TModel, IEnumerable<U>>> listExpr,
      Expression<Func<U, V>> memberExpr)
    {
      var meta1 = ModelMetadata.FromLambdaExpression(listExpr, helper.ViewData);
      var listName = meta1.PropertyName;

      var itemName = GetProperty(memberExpr).Name;

      return string.Format("value: {1}, attr: {{ id : '{0}_' + $index() + '__{1}', name: '{0}[' + $index() + '].{1}' }}",
        listName, itemName);
    }

    public static PropertyInfo GetProperty<T, U>(this Expression<Func<T, U>> expression)
    {
      MemberExpression memberExpression = null;

      switch (expression.Body.NodeType)
      {
        case ExpressionType.Convert:
          memberExpression = ((UnaryExpression) expression.Body).Operand as MemberExpression;
          break;

        case ExpressionType.MemberAccess:
          memberExpression = expression.Body as MemberExpression;
          break;
      }

      if (memberExpression == null)
        throw new ArgumentException("Not a member access", "expression");

      return memberExpression.Member as PropertyInfo;
    }

This is how it's used to generate the HTML:

    <table id="acquisition_items">
      <thead>
        <tr>
          <th>Product Name</th>
          <th>Quantity</th>
          <th>Price</th>
          <th>Value</th>
          <th><button data-bind="click: addItem">[+]</button></th>
        </tr>
      </thead>
      <tbody data-bind="foreach: items">
        <tr>
          <td>
            <input data-bind="@Html.DataBinding(m => m.Items, it => it.ProductName)" type="text" value="" />
          </td>
          <td>
            <input data-bind="@Html.DataBinding(m => m.Items, it => it.Quantity)" type="text" value="" />
          </td>
          <td>
            <input data-bind="@Html.DataBinding(m => m.Items, it => it.Price)" type="text" value="" />
          </td>
          <td>
            <input data-bind="value: Value" readonly="readonly" type="text" value="" />
          </td>
          <td>
            <button data-bind="click: $parent.delItem">[--]</button>
          </td>
        </tr>
      </tbody>
    </table>

and it generates the following HTML:

    <table id="acquisition_items" class="jtable">
      <thead>
        <tr>
          <th>Product Name</th>
          <th>Quantity</th>
          <th>Price</th>
          <th>Value</th>
          <th><button data-bind="click: addItem">[+]</button></th>
        </tr>
      </thead>
      <tbody data-bind="foreach: items">
        <tr>
          <td>
            <input data-bind="value: ProductName, attr: { id : &#39;Items_&#39; + $index() + &#39;__ProductName&#39;, name: &#39;Items[&#39; + $index() + &#39;].ProductName&#39; }" type="text" value="" />
          </td>
          <td>
            <input data-bind="value: Quantity, attr: { id : &#39;Items_&#39; + $index() + &#39;__Quantity&#39;, name: &#39;Items[&#39; + $index() + &#39;].Quantity&#39; }" type="text" value="" />
          </td>
          <td>
            <input data-bind="value: Price, attr: { id : &#39;Items_&#39; + $index() + &#39;__Price&#39;, name: &#39;Items[&#39; + $index() + &#39;].Price&#39; }" type="text" value="" />
          </td>
          <td>
            <input data-bind="value: Value" readonly="readonly" type="text" value="" />
          </td>
          <td>
            <button data-bind="click: $parent.delItem">[--]</button>
          </td>
        </tr>
      </tbody>
    </table>

This solves the id/name problem by using the $index knockout observable; newly created records will always have the correct values for these attributes, no matter how rows are added or deleted.

Again, thanks to Pete for the inspiration and I hope this will help someone else.