Overcoming dependency injection hurdles with TinyIoC: A lightweight Inversion of Control container

Sometimes when embarking on a software project you may find yourself wishing that you had a lightweight way of achieving Inversion of Control (IoC) via dependency injection. On the other hand, you might not be very familiar with the principle of IoC, but have heard it can be very beneficial and are looking for a simple way of getting started.

For many things in software development, the number of available choices can be overwhelming. There are often trade-offs to be made in regards to the simplicity of the solution versus the available feature-set. When it comes to IoC containers things are no different.

In the world of .NET, there are many different IoC containers to choose from. If you’re looking for a way of using dependency injection in your project and want something that is both simple and performant, TinyIoC could be the right choice for you.

In this article, I touch on what Inversion of Control is, what TinyIoC brings to the table, and provide some basic examples of how to register and resolve dependencies in your code with TinyIoC.

What is Inversion of Control?

Rather than attempt to rehash other explanations of what Inversion of Control is, let me point you in the direction of a helpful article on the subject that summarises IoC as follows.

Inversion of control is a software design principle that asserts a program can benefit in terms of pluggability, testability, usability and loose coupling if the management of an application’s flow is transferred to a different part of the application.

As stated above, IoC is a design principle, not a design pattern.

Dependency injection

The article mentioned further above goes on to clarify how the term dependency injection has since been used to describe a software design pattern where dependencies are registered in a container and objects are materialised upon request.

Ideally in the context of software architecture, lower layers should not depend on higher layers. Dependency injection provides us with a way of achieving this by allowing us to register abstractions in an IoC container. These dependencies are resolved via the container, instead of code in lower layers directly creating concrete object instances. In this way, control is reversed so that changes in the higher layers do not affect the lower layers.

Below is an example of the kind of code we want to improve.

private readonly ITodoServices _todoServices;
 
public TodoView()
{
    _todoServices = new TodoServices();
}

In the above code, the TodoView class is newing up a concrete object type in its constructor. This code is now dependent on the TodoServices class. Even though our backing field is an interface type, we can’t easily swap out the implementation with a new one from a central place.

Even without an IoC container, you can accomplish simple dependency injection by accepting object references as part of your class constructors, rather than ‘newing up’ these objects directly in the constructors. An example of this is shown in the following code.

private readonly ITodoServices _todoServices;
 
public TodoView(ITodoServices todoServices)
{
    _todoServices = todoServices;
}

By passing an object instance into the constructor we are inverting the control so that the code that creates the object is also responsible for creating and supplying the other objects that the object depends on.

IoC containers

An IoC container can bring several benefits and helps to simplify things, especially when an application is made up of multiple layers and has lots of objects with multiple dependencies. IoC containers typically provide features such as controlling how long registered objects live for and the automatic registration of dependencies based on conventions.

Let’s look at some of the benefits that TinyIoC can offer us next.

TinyIoC features

Despite the core logic being less than 5000 lines of code, TinyIoC packs in a lot of great features.

Below is the current list of key features taken from the TinyIoC GitHub project page.

  • Simple inclusion – just add the CS file (or VB file coming soon!) and off you go.
  • Wide platform support – actively tested on Windows, Mono, MonoTouch, PocketPC and Windows Phone 7. Also works just fine on MonoDroid.
  • Simple API for Register, Resolve, CanResolve and TryResolve.
  • Supports constructor injection and property injection. Constructors are selected automatically but can be overridden using a “fluent” API.
  • Lifetime management – including singletons, multi-instance and ASP.Net per-request singletons.
  • Automatic lazy factories – a Func dependency will automatically create a factory.
  • RegisterMultiple/ResolveAll/IEnumerable support – multiple implementations of an interface can be registered and resolved to an IEnumerable using ResolveAll, or taking a dependency on IEnumerable.
  • Child containers – lifetime can be managed using child containers, with automatic “bubbling” of resolving to parent containers where required.

If you’ve worked with other IoC containers in the past you’ll appreciate that all of the features needed for the most common scenarios are included, such as constructor and property injection, and lifetime management of objects.

TinyIoC has excellent performance and works very well on lower-powered, portable devices, so it’s a solid choice for Xamarin applications. It also works just as well for standard desktop and ASP.NET web applications with the TinyIoC.AspNetExtensions NuGet package.

For those wondering about cross-platform capabilities, the current release (v1.3.0) of TinyIoC is several years old, so it doesn’t support .NET Core/Standard/5+. However, the goods news is that another version is soon to be released (v1.4.0) that will support .NET Standard v1.2 or greater. The first v1.4.0 release candidate has been available since the beginning of 2022.

Package installation

TinyIoC is available for installation as a NuGet package.

The version you need to install will depend on the framework you are targeting.

Note that the sub-sections below assume you are using Visual Studio. If you’re using another editor such as Visual Studio Code, the installation steps will vary (TIP: Try the NuGet Gallery extension as the GUI for managing packages).

.NET Framework

If you need to use TinyIoC in a .NET Framework project, you can install the latest TinyIoC NuGet package. You can do this either via the ‘Manage NuGet Packages…’ option on the context menu that appears when you right-click on your project in Visual Studio or by running the following command from the Package Manager Console.

Install-Package TinyIoC

Skip to the Source heading if you’ve installed TinyIoC for the .NET Framework.

.NET Core/Standard/5+

If you’re targeting .NET Core, you’ll need to install the latest TinyIoC Release Candidate NuGet package version. You’ll need to tick the ‘Include prerelease’ option within the ‘Manage NuGet Packages…’ tab in Visual Studio to see the option ‘1.4.0-rc1’, or run the following command from the Package Manager Console.

Install-Package TinyIoC -Version 1.4.0-rc1

Note that by the time you read this v1.4.0 or a newer version may already be available as the latest official release. If so, you’ll need to adapt the above instructions accordingly.

Source

After the TinyIoC NuGet package has been installed you’ll notice a new file called ‘TinyIoc.cs’ has been added to the root of your project; this is the only file needed to start using TinyIoC.

This really is the definition of ‘Simplified Inclusion’, meaning that an additional assembly reference isn’t required, along with the resulting DLL file that would otherwise need to be deployed along with your application.

If you open the TinyIoc.cs file, you’ll see the full source code for TinyIoC. The TinyIoc.cs file is editable in a .NET Framework project, so be careful not to unintentionally alter it (unless you really meant to!). In a .NET Core project, TinyIoc.cs is included as a linked file and cannot be edited directly from the project in Visual Studio.

Manual install

If you don’t want to use the NuGet package for whatever reason, you can choose instead to simply copy and paste the TinyIoC source code into your project.

To do this, create a new file in your project called ‘TinyIoc.cs’, then copy and paste the contents of the TinyIoc.cs file on GitHub into your local file and save it.

Using TinyIoC

Now that TinyIoC is installed, let’s look at how to use it in our projects.

We’ll start with registering dependencies.

Registering dependencies

Before we can start availing of the benefits of dependency injection, we first of all need to register our dependencies with the IoC container.

After doing this, any time that a dependency needs to be resolved, TinyIoC will be able to supply an appropriate object instance based on what has been registered with the container. If in the future we need to switch to a different implementation of an object/service, we can simply register a different type with the container. The code that resolves the dependency from the container will then use the new implementation without requiring any further changes.

Similar to other IoC frameworks, TinyIoC provides methods that allow dependencies to be registered using different lifetime management types. I’ll cover this in the code sample that follows.

Bootstrapping

For projects where I use TinyIoC, I usually create a class called Startup (or Bootstrap) and create a static RegisterServices (or Register) method within this. This is intended to act as the central place where application dependencies are registered and this method should be called early on when the application starts up.

Within your project, I recommend creating a file called ‘Startup.cs’ and add the following using statement to the top of the file.

using TinyIoC;

Below is the definition of the rest of the file, minus the namespace and any other using statements that may be custom to your particular project.

/// <summary>
/// Startup class for IoC container configuration.
/// </summary>
internal static class Startup
{
    #region Methods
 
    /// <summary>
    /// Registers application dependencies.
    /// </summary>
    internal static void RegisterServices()
    {
        TinyIoCContainer.Current.Register<ITodoServices, TodoServices>().AsMultiInstance();
        TinyIoCContainer.Current.Register<IProductServices, ProductServices>().AsSingleton();
 
        // Other registrations...
    }
 
    #endregion
}

Note that you’ll need to update ITodoServices and TodoServices etc. with your own interfaces and classes.

The above code uses the Current property of the TinyIoCContainer class to access a ‘lazy’ initialised TinyIoCContainer object instance. This approach works just fine for simple scenarios where you don’t need to use more advanced features like child containers.

The first line of the RegisterServices method calls the Register method on the container object and specifies two generic type parameter values. The first value is the type to register, this is usually an interface that acts as a layer of abstraction to produce loosely coupled code. The second value is the type of object to instantiate that implements the interface type. A call to the AsMultiInstance method is chained onto the Register method call to explicitly specify that TinyIoC should instantiate a new object instance every time the dependency is resolved.

The second line is almost the same as the first line, except that the interface and concrete object type are different and a different type of lifetime management has been specified. Using the AsSingleton method signals that TinyIoC should supply the same object instance every time the dependency is resolved.

Specific object instances

Another thing we can do is register a specific object instance, and we can optionally provide a name to create a named object registration, as follows.

var todoServices = new TodoServices();
 
TinyIoCContainer.Current.Register<ITodoServices>(todoServices, "Home");

In the above example, a specific instance of TodoServices is registered with the name ‘Home’.

We can register multiple objects that implement ITodoServices and retrieve them specifically by name when resolving dependencies from the container. I’ll demonstrate how to resolve a dependency by name later in the article.

What else can we do?

These are the very basics of registering dependencies with TinyIoC. Knowing how to register dependencies in the ways shown further above should be enough to get you started and will cover the most basic scenarios for small projects.

If you need to do something more advanced, I recommend checking out the TinyIoCTests on GitHub. The tests provide a great way of finding examples of how to register and resolve dependencies and generally for determining what’s possible with TinyIoC.

Let’s take a look at how to resolve dependencies next.

Resolving dependencies

Now that we have the code in place that will register dependencies when our application starts up, we can proceed to add the code that will resolve object instances from the IoC container.

Resolving basic registrations

A very simple example of resolving a dependency is as follows.

var todoServices = TinyIoCContainer.Current.Resolve<ITodoServices>();

The above code uses the Resolve method on the container object to get an appropriate object instance for us. The method overload that is used features a type parameter that allows us to specify the type of object we want to resolve.

In this case, the concrete object instance will be a TodoServices object, as that is the type that was registered with the container when the application started up.

We can then start calling methods on our object, for example:

var todos = todoServices.GetTodos(userId: 1);

Of course, your object will have its own methods and associated parameters.

Typically you would call the Resolve method within the constructor of a class where you need to inject a dependency and assign the result to private readonly field, for example, as follows.

private readonly ITodoServices _todoServices;
 
public TodoView()
{
    _todoServices = TinyIoCContainer.Current.Resolve<ITodoServices>();
}

In the above code, the constructor of the TodoView class is resolving an object instance that implements the ITodoServices interface from the container. The TodoView object doesn’t know what concrete object instance it will get, it’s down to the container to provide the correct type of object according to the registrations that have been made.

Named registrations

There are several overloads available for the Resolve method. For example, to resolve a specific named instance of an object we can do the following.

var todoServices = TinyIoCContainer.Current.Resolve<ITodoServices>("Home");

The above code will look for a registration that was registered with the name ‘Home’. This can be very useful for cases where we need to retrieve specific object instances from the container.

Constructors with parameters

Normally, when it comes to dependency injection, it is recommended that your object constructors are parameterless.

However, sometimes you may need to pass one or more parameters to your objects when resolving.

Below is an example of how to achieve this with TinyIoC.

var todoServices = TinyIoCContainer.Current.Resolve<ITodoServices>(
    new NamedParameterOverloads(
        new Dictionary<string, object>() { { "userId", 1 } }));

There’s a bit of ceremony and the code is a bit verbose, but it’s good to know that this is possible if you need to do it.

As mentioned previously, I highly recommend checking out the TinyIoCTests on GitHub as a way of exploring what else is possible in regards to resolving dependencies and in relation to the general usage of TinyIoC.

Summary

In this article, I have introduced you to the TinyIoC Inversion of Control container.

I started by briefly explaining the concepts of Inversion of Control and dependency injection and I looked at the key features of TinyIoC. I then looked at how to install TinyIoC into your .NET project and how to register and resolve dependencies with some simple examples to help you get started.

I trust that you found this article helpful as an introduction to TinyIoC and that you find a good use case for TinyIoC in one or more of your projects.


I hope you enjoyed this post! Comments are always welcome and I respond to all questions.

If you like my content and it helped you out, please check out the button below 🙂

Comments