Skip to content

Dependency injection

During its lifetime, a QueryStorm app instantiates many objects: components, ribbons, context menu commands, excel functions classes as well as any domain-specific services.

Each of these classes can have dependencies of their own. For example, a component could depend on a service in order to access some remote data.

To make it easy for objects to get the dependencies they need, QueryStorm apps use dependency injection, specifically, the Unity IOC Container.

Resolving dependencies

Dependencies can be resolved in three ways:

  • Constructor injection (preferred)
  • Property injection (using the[Dependency] attribute)
  • Explicitly, by using the Container.Resolve<T>() method

For example, if a component needs access to the current workbook, it can request an IWorkbookAccessor by including an argument for it in the constructor (constructor injection):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class Component1 : ComponentBase
{
    IWorkbookAccessor workbookAccessor;
    public Component1(IWorkbookAccessor workbookAccessor)
    {
        this.workbookAccessor =  workbookAccessor;
    }

    [EventHandler]
    public void HandleSomeEvent()
    {
        var workbook = workbookAccessor.Workbook;
        // do something with the workbook...
    }
}

Built-in dependencies

The container instance itself is created by the App base class and is exposed via the Container property.

App container

The container is initially populated with registrations for a number of services that provide general-purpose functionality to apps:

  • IExcelAccessor - allows access to the root Excel Application instance.
  • IWorkbookAccessor - allows access to the current workbook (i.e. the containing workbook for workbook apps, active workbook for extension apps).
  • IShortcutService - allows registering additional keyboard shortcuts (can override existing Excel shortcuts).
  • IQueryStormLogger - allows writing to the QueryStorm log window (Runtime) and messages pane (IDE).
  • ISyncRunner - allows running actions on the main Excel thread.
  • IDialogService - offers the ability to show general purpose dialogs (message-boxes, save-file dialogs, input dialogs).
  • IDataContext - gives raw (untyped) access to the datacontext (usually workbook tables and variables).
  • IEventHandlerHook - service that allows intercepting event handlers. By default a pass-through implementation that can be replaced with a custom implementation (e.g. to show a custom dialog while async event handlers are executing).
  • ProjectConfiguration - allows strongly typed access to the contents of the project.config file.
  • ConnectionStringsDictionary - allows strongly typed access to the ConnectionStrings section of the project.config file.
  • Tracker - a Jot.Tracker instance used to save configuration settings for the app.
  • CredentialsVault - used to securely store credentials for the app (usernames, passwords, api keys). Mainly used by scripts with templated connection strings, but can be used by user code as well.

Registering user-defined dependencies

Apps can freely register their own specific dependencies as well. This is typically done in the constructor or the Start() method (either option is fine).

For example, suppose multiple parts of an app use a specific service. The service should first be registered with the container inside the App class:

1
2
3
4
5
6
7
8
9
public class App: ApplicationModule
{
    public App(IAppHost appHost)
        : base(appHost)
    {
        // register the service with the container as a singleton
        Container.RegisterType<MyService1>(new ContainerControlledLifetimeManager());
    }
}

A component could then request an instance of this service simply by adding a constructor argument for it.

1
2
3
4
5
6
7
class Component1
{
    public Component1(MyService1 service)
    {
        // store the service and use it in event handlers
    }
}

Expensive objects

For dependencies that are expensive to create, it's best to create them lazily, so they are only created when they are first needed. Instead of creating the instance and registering it with the container using the RegisterInstance method, it's preferable to use the RegisterType and RegisterFactory methods.

Registration lifetimes

It is important to be mindful of the lifetime of the created objects. When registering a type with the Unity container, you can specify if the container should create a single instance of a particular service (ContainerControllerLifetimeManager) which it will return every time it is requested, or if it should return a new instance of the service every time it is requested (TransientLifetimeManager, default if no lifetime manager is specified).

About Unity IOC

This page provided a minimal introduction to using the Unity IOC container. While the Unity IOC container is fairly simple to use, a detailed description of it is outside the scope of this documentation. To learn more about the Unity IOC container, visit the Unity container documentation page.