Using Autofac for injection.
Given
interface IStateMachine<TState, TTrigger> { }
class ConcreteStateMachine : IStateMachine<MachineState, Trigger> { }
builder.RegisterType<ConcreteStateMachine>().As<IStateMachine<MachineState, Trigger>>();
class Consumer { Consumer(IStateMachine<MachineState, Trigger> machine) { } }
Why does container.Resolve<Consumer>();
fail with this exception:
Unhandled exception.
Autofac.Core.DependencyResolutionException: An exception was thrown while activating Consumer.
---> Autofac.Core.DependencyResolutionException: An exception was thrown while invoking the
constructor 'Void .ctor(IStateMachine`2[MachineState,Trigger])' on type 'Consumer'.
---> System.NullReferenceException: Object reference not set to an instance of an object.
Full code contrived example: Requires nuget for Autofac and Stateless
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using Autofac;
using Stateless;
public class Program
{
public static void Main()
{
var builder = new ContainerBuilder();
builder.RegisterType<Consumer>()
.InstancePerLifetimeScope();
builder.RegisterType<ConcreteStateMachine>()
.As<IStateMachine<MachineState, Trigger>>()
.InstancePerLifetimeScope();
var container = builder.Build();
using (var scope = container.BeginLifetimeScope())
{
var consumer = scope.Resolve<Consumer>();
consumer.TurnOn();
consumer.GetData();
consumer.TurnOff();
}
}
}
public class Consumer
{
protected IStateMachine<MachineState, Trigger> Machine;
public Consumer(IStateMachine<MachineState, Trigger> machine)
{
Machine = machine;
Machine.Register(MachineState.Off, () => Debug.WriteLine("State Off"));
Machine.Register(MachineState.On, () => Debug.WriteLine("State On"));
}
public List<string> GetData()
{
if (!Machine.IsInState(MachineState.On)) throw new InvalidOperationException("Can't GetData when machine is off!");
Debug.WriteLine("Getting Data");
return new List<String> {"Data", "Data", "Data"};
}
public void TurnOn()
{
Machine.Fire(Trigger.TurnOn);
}
public void TurnOff()
{
Machine.Fire(Trigger.TurnOff);
}
}
public class ConcreteStateMachine : IStateMachine<MachineState, Trigger>
{
protected StateMachine<MachineState, Trigger> Machine;
public MachineState CurrentState { get; set; }
public void Fire(Trigger trigger)
{
Machine.Fire(trigger);
}
public async Task FireAsync(Trigger trigger)
{
await Machine.FireAsync(trigger);
}
public bool IsInState(MachineState state)
{
return Machine.IsInState(state);
}
public void Register(MachineState state, Action callback)
{
Machine.Configure(state)
.OnEntry(() => callback.Invoke());
}
public void RegisterAsync(MachineState state, Func<Task> callback)
{
Machine.Configure(state)
.OnEntryAsync(async () => await callback.Invoke());
}
public void Start()
{
ConfigureMachine();
Machine.Activate();
}
protected void ConfigureMachine()
{
Machine = new StateMachine<MachineState, Trigger>(MachineState.Off);
Machine.Configure(MachineState.Off)
.Permit(Trigger.TurnOn, MachineState.On);
Machine.Configure(MachineState.On)
.Permit(Trigger.TurnOff, MachineState.Off);
}
}
public interface IStateMachine<TState, TTrigger>
{
TState CurrentState { get; set; }
bool IsInState(TState state);
void Fire(TTrigger trigger);
Task FireAsync(TTrigger trigger);
void Start();
void Register(TState state, Action callback);
void RegisterAsync(TState state, Func<Task> callback);
}
public enum MachineState
{
Off,
On
}
public enum Trigger
{
TurnOff,
TurnOn
}
I've tried to find an example of this type of injection using Autofac and I can't find anyone else who's had a similar problem.
The container will resolve an instance of ConcreteStateMachine
directly.
As in container.Resolve<IStateMachine<MachineState, Trigger>>()
correctly provides a ConcreteStateMachine
, but Autofac doesn't seem to know how to send an instance to Consumer
.
CodePudding user response:
Machine
field of ConcreteStateMachine
is not initialized and is null
.
So it fails with NullReferenceException
in:
public void Register(MachineState state, Action callback)
{
Machine.Configure(state)
.OnEntry(() => callback.Invoke());
}
CodePudding user response:
Just a case of introducing a bug while creating the contrived example...
I never called Machine.Start() in the constructor of Consumer, which instantiates the internal StateMachine, prior to calling Register, which invokes a method on the underlying StateMachine. This caused an exception to be thrown while creating Consumer and was not in fact an Autofac configuration issue.
Sorry!