0

[This is for a Windows 10 IoT UWP app on a Raspberry Pi 2]:

Concretely, I'm trying to create two serial ports and link each one to a device driver for device that has a serial connection (I have two identical devices and a serial port to talk to each). I have a class (call it DeviceDriver) that implements an IDeviceDriver interface for this type of device. My hardware configuration also includes an external chip that has multiple serial ports. I have a class SerialPort for those and they implement an ISerialPort interface.

I need two instances of DeviceDriver as I have two devices, and two instances of SerialPort - one for each device. I can get Ninject to make one serialport and pass the ISerialPort object to the DeviceDriver constructor. Where I am stuck is that I want two DeviceDriver objects; one that gets linked to a SerialPort (COM1) and the other gets linked to a separate SerialPort (COM2). Examples for Ninject show you can bind multiple different classes to one instance (a Shuriken and Sword can both be bound to IWeapon), but I don't see how to bind a COM1 serialport and a COM2 serialport to ISerialPort - it gives me a compilation error. So how do I create two instances of the same SerialPort class (with different constructor arguments to say one is COM1 and the other is COM2, I know how to specify constructor arguments already) and then tell Ninject which SerialPort to pass to two instances of a DeviceDriver class, where one needs COM1 and one needs COM2?

My DeviceDriver basically looks like this:

public class DeviceDriver :IDeviceDriver
{
    ISerialPort m_localPort;

    public DeviceDriver(ISerialPort port)
    {
        m_localPort = port;
    }

    // Other stuff
    // ...
}

Anybody have any thoughts how I can do this? The following link is the only thing I've found, but they are talking about Unity and XML configuration files and it seems overly complex for what I'm trying to do.

Initialising configurable objects with dependency injection container

Thanks!

Community
  • 1
  • 1
T.C.
  • 13
  • 4
  • [this](http://stackoverflow.com/questions/35676511/ninject-contextual-binding-for-2-dependent-classes-w-different-names-for-same) recent question discusses something very similar. – BatteryBackupUnit Mar 05 '16 at 21:27
  • How would you like to use these ports? Do you just need a collection of two distinct `DeviceDrivers` with their respective ports at some point? or do you need to know which is "COM1" and which is "COM2"? Is the same `ISerialPort` "COM1" injected into multiple classes or just once into `DeviceDriver` for "COM1"? – BatteryBackupUnit Mar 05 '16 at 21:29
  • I need to know which DeviceDriver is COM1 and which is COM2. To be a bit more specific, DeviceDriver is used to control a servo motor that has an RS-232 port on it. So I have one motor hooked up to COM1 and another, identical, motor hooked to COM2. COM1 is dedicated to that motor and would never get passed to another class or instance. The goal is to create an instance of DeviceDriver saying "This is Motor #1, and it is connected to COM1." I then want to create an instance of DeviceDriver that will be Motor #2 connected to COM2. – T.C. Mar 09 '16 at 22:56
  • ok in that case you can either used the "named binding" or "factory" approach shown in my answer. – BatteryBackupUnit Mar 10 '16 at 07:18

2 Answers2

2

Let's say we've got the following implementation:

public class SerialPortAddress
{
    public SerialPortAddress(string address)
    {
        this.Address = address;
    }

    public string Address { get; }
}

public interface ISerialPort
{
    SerialPortAddress Address { get; }
}

public class SerialPort : ISerialPort
{
    public SerialPort(SerialPortAddress address)
    {
        this.Address = address;
    }

    public SerialPortAddress Address { get; }
}

public interface IDeviceDriver
{
    ISerialPort SerialPort { get; }
}

public class DeviceDriver : IDeviceDriver
{
    public DeviceDriver(ISerialPort serialPort)
    {
        SerialPort = serialPort;
    }

    public ISerialPort SerialPort { get; }
}

Multi-Injection

We can then create the bindings as follows and retrieve a list of IDeviceDrivers with their serial ports as follows:

public class Test
{
    [Fact]
    public void Bla()
    {
        var com1 = new SerialPortAddress("COM1");
        var com2 = new SerialPortAddress("COM2");

        var kernel = new StandardKernel();

        kernel.Bind<ISerialPort>().To<SerialPort>();
        kernel.Bind<IDeviceDriver>().To<DeviceDriver>()
            .WithParameter(new TypeMatchingConstructorArgument(
                typeof(SerialPortAddress),
                (ctx, target) => com1, true));
        kernel.Bind<IDeviceDriver>().To<DeviceDriver>()
            .WithParameter(new TypeMatchingConstructorArgument(
                typeof(SerialPortAddress),
                (ctx, target) => com2, true));

        var deviceDrivers = kernel.Get<List<IDeviceDriver>>();

        deviceDrivers.Should().HaveCount(2)
            .And.Contain(x => x.SerialPort.Address == com1)
            .And.Contain(x => x.SerialPort.Address == com2);
    }
}

Also see Multi Injection

Named Bindings

Alternatively, if you need to know which IDeviceDrivers is which, you can also use named bindings:

[Fact]
public void NamedBindings()
{
    const string DeviceDriver1 = "DeviceDriver1";
    const string DeviceDriver2 = "DeviceDriver2";

    var com1 = new SerialPortAddress("COM1");
    var com2 = new SerialPortAddress("COM2");

    var kernel = new StandardKernel();

    kernel.Bind<ISerialPort>().To<SerialPort>();
    kernel.Bind<IDeviceDriver>().To<DeviceDriver>()
        .Named(DeviceDriver1)
        .WithParameter(new TypeMatchingConstructorArgument(
            typeof(SerialPortAddress), 
            (ctx, target) => com1, true));
    kernel.Bind<IDeviceDriver>().To<DeviceDriver>()
        .Named(DeviceDriver2)
        .WithParameter(new TypeMatchingConstructorArgument(
            typeof(SerialPortAddress),
            (ctx, target) => com2, true));

    kernel.Get<IDeviceDriver>(DeviceDriver1).SerialPort.Address.Should().Be(com1);
    kernel.Get<IDeviceDriver>(DeviceDriver2).SerialPort.Address.Should().Be(com2);
}

Factory

Finally, you could also create the components by factory, which requires a factory interface to begin with:

public interface IDeviceDriverFactory
{
    IDeviceDriver Create(SerialPortAddress address);
}

using Ninject.Extensions.Factory we can now do the following:

[Fact]
public void Factory()
{
    var com1 = new SerialPortAddress("COM1");
    var com2 = new SerialPortAddress("COM2");

    var kernel = new StandardKernel();

    kernel.Bind<ISerialPort>().To<SerialPort>();
    kernel.Bind<IDeviceDriver>().To<DeviceDriver>();
    kernel.Bind<IDeviceDriverFactory>()
          .ToFactory(() => new TypeMatchingArgumentInheritanceInstanceProvider());

    var factory = kernel.Get<IDeviceDriverFactory>();

    factory.Create(com1).SerialPort.Address.Should().Be(com1);
    factory.Create(com2).SerialPort.Address.Should().Be(com2);
}

EDIT: Ninject.Extension.Factory may not run on the raspberry pi. If that's the case you might need to implement the factory yourself:

public class DeviceDriverFactory : IDeviceDriverFactory
{
    private readonly IResolutionRoot resolutionRoot;

    public DeviceDriverFactory(IResolutionRoot resolutionRoot)
    {
        this.resolutionRoot = resolutionRoot;
    }

    public IDeviceDriver Create(SerialPortAddress address)
    {
        var serialPortAddressParameter = new TypeMatchingConstructorArgument(
            typeof(SerialPortAddress),
            (ctx, t) => address)
        this.resolutionRoot.Get<IDeviceDriver>(serialPortAddressParameter);
    }
}

Bind<IDeviceDriverFactory>().To<DeviceDriverFactory>();
BatteryBackupUnit
  • 12,934
  • 1
  • 42
  • 68
  • Thank you! Will try this out when I am back at my dev computer! – T.C. Mar 07 '16 at 23:55
  • Named bindings solved it for me, thanks very much! The other missing piece in my knowledge, which I figured out from your example, was to pass constructor arguments with shouldInherit=true. EG -- DeviceDriver can be bound .WithParameter(new ConstructorArgument("baudRate", baudratevalue, true)), and that value gets passed along to the SerialPort it uses. Thanks again! – T.C. Mar 14 '16 at 20:25
0

I'm not familiar Ninject or Unity, but Castle Windsor has a lifestyle called Pooled, which will create up to a specified number of instances and then return those instances to the pool of instances after they've been released. When using this lifestyle, Windsor will create as many objects as are requested up to the limit specified, and then either recycle the instance (if you've derived from IRecyclable and implemented the Recycle() method) or dispose of it normally.

You can have your components created using a simple factory method that provides the correct constructor arguments, and then when they are returned to the pool they will be correctly configured.

EDIT: If you're set on using Ninject, then I would solve this problem by injecting an ISerialPortFactory into the constructor of DeviceDriver and using that to create your ISerialPort objects. Since your DeviceDriver class doesn't care which ISerialPort it's using, the factory can be used to manage the instances that you need.

Your factory would look something like this:

public interface ISerialPortFactory
{
    ISerialPort CreateNext();
}

public class SerialPortFactory : ISerialPortFactory
{        

    public ISerialPort CreateNext()
    {
        var serialPortConfiguration = GetNextConfiguration();
        return new SerialPort(serialPortConfiguration);
    }

    private GetNextConfiguration()
    {
        // you could manage some kind of internal registry of COMx configurations here
    }

}

And your client DeviceDriver class would look like this:

public class DeviceDriver : IDeviceDriver
{
    public DeviceDriver(ISerialPortFactory factory)
    {
        m_localPort = factory.CreateNext();
    }
}

The abstract factory method is sort of a heavy-handed way of getting what you want, but it's a surefire way to get exactly what you need since you have complete control over it. Its main use case is to resolve dependencies where you don't necessarily know the exact implementation you want until runtime.

topher
  • 328
  • 1
  • 8