4 minutes to read
Created by
Calytic
Updated by
Calytic

Service container

Using dependency injection with the uMod service container

This guide is for uMod, not Oxide.
Join our discord for the latest updates and the latest news! Join discord

Dependency injection

A dependency is just an object that a class or method uses and depends on to function.

For example, if the Model class fetches data from a Database class then the Model class has a dependency on the Database class.

Injecting a dependency literally means that the dependency is not instantiated from within a class or method, but is expected to already exist elsewhere and be injected at run-time into the dependant class or method.

Using dependency injection with the service container allows developers to easily decouple a class constructor or method from the construction of its dependencies.

This concept can be expressed very simply in code with a Loaded hook method that injects the IServer singleton as a dependency.

void Loaded(IServer server)
{
    // Code here
}

Parameter binding

When an injectable method or constructor is invoked from the service container any tailing parameters that are not supplied by the original caller will be supplied automatically if a matching type (or interface) is found in the service container.

It is possible to customize parameter binding for hooks and factory constructors using converters and resolvers.

Overloading

It is important to note that the parameter order matters for the purpose of being consistent with C# method overloading.

Generics support

The dependency injector will leverage type information provided by generic type constraints.

OnTestHook<T>(T player) where T : IPlayer

Binding singletons

Singletons are objects which will only ever have a single instance within an application scope. uMod provides many singletons (e.g. plugins, extensions, and libraries) and they are globally available for injection by default.

Binding contract implementations

A contract interface may be specified as a hook parameter when the supplied object implements the requested interface. Use contract interfaces instead of concrete implementations to ensures code remains flexible across different application domains.

By specifying an IServer parameter, the Loaded hook method below will automatically receive whatever concrete implementation of IServer is available in the service container.

void Loaded(IServer server)
{
    if (server.MaxPlayers < 10)
    {
      // Do something clever
    }
}

Converters

Converters transform an object or value of one type into another type. Conversions are then available for parameter substitution in surrounding hook calls and factory constructor methods.

private Dictionary<int, MyData> myData = new Dictionary<int, MyData>();

public class MyData
{
      public int ID;
      public string Title;
}

Define a contract interface and a Converter method to inform the service container that the key type is a valid substitute for the value itself.

[Converter]
public MyData ConvertMyData(int id)
{
    myData.TryGetValue(id, out MyData myDataImpl);
    return myDataImpl;
}

void OnAnyHook(MyData someData)
{
    Logger.Info($"OnAnyHook - {{someData.ID}:{someData.Title}");
}

With the converter method and hook method above, subsequent hook calls may substitute an object argument of type MyData with a value argument of type int.

myData.Add(new MyData() { ID = 1, Title = "Hello world" });

CallHook("OnAnyHook", 1);
// Prints: OnAnyHook - 1:Hello world