2

I'm replacing MVVM Light default SimpleIOC with Autofac. So far so good, but now I'm trying to initialize one ViewModel as soon the app starts, as I need to register some calls with MessengerInstance, with SimpleIOC this was as easy as SimpleIoc.Default.Register<MyViewModel>(true);, but I can't find the way with Autofac.

I tried containerBuilder.RegisterType<MyViewModel>().AutoActivate(); that initializes the ViewModel but then it's not registered when I need it for binding:

    containerBuilder.RegisterType<MyViewModel>().AutoActivate();
    ...
    public MyViewModel MyVM
    {
        get { return this.container.Resolve<MyViewModel >(); } //<- Boom! ComponentNotRegisteredException!!
    }

Edit

The issue is that I want the ViewModel to be activated before I resolve it, because I'm registering a message there:

public MyViewModel()
{
        MessengerInstance.Register<bool>(this,(b) => DoThisAction(b));
}

If the ViewModel is not registered at this point the message will be lost:

//MyView is binded to MyViewModel
NavigationService.NavigateTo("MyView);
MessengerInstance.Send<bool>(true);
D.Rosado
  • 5,634
  • 3
  • 36
  • 56
  • 1
    I don't understand the problem. Why are you using `AutoActivate`? I guess you simply need to register the instance as regular registration and resolve it. – Sriram Sakthivel Oct 29 '14 at 15:20
  • But if your view is bound to your ViewModel, doesn't it mean that the ctor on VM will be called as soon as you navigate to your view, at which point the MessengerInstance will know what to do with the message being sent? – Darek Oct 29 '14 at 16:20
  • @Darek The navigation is asynchronous – D.Rosado Oct 29 '14 at 16:47
  • @D.Rosado so the message sent, might get lost? What happens if the message will be send again after a while? Should we understand that the navigation might be occurring on a different thread than the send event? If not, if they are occurring on the same thread, would you be allowed to put it in an async-completed callback? – Darek Oct 29 '14 at 16:51
  • See my update on iterating through all registrations. Does it solve your challenge? – Darek Oct 29 '14 at 17:53
  • As a side note, you probably want to register your viewmodels as single instance, otherwise registering the message will be executed on every viewmodel creation. – Darek Oct 29 '14 at 19:16
  • @Darek Yes, it's a workable solution thanks. And good point about the instances too. – D.Rosado Oct 30 '14 at 09:31

1 Answers1

4

This seems to work perfectly fine:

internal class Program
{
    public static IContainer container;


    public MyViewModel MyVM
    {
        get { return Program.container.Resolve<MyViewModel>(); }
    }

    private static void Main(string[] args)
    {
        ContainerBuilder containerBuilder = new ContainerBuilder();
        containerBuilder.RegisterType<MyViewModel>();

        container = containerBuilder.Build();

        Program p = new Program();
        MyViewModel vm = p.MyVM;
    }
}

Do you have to use the AutoActivate? In other words, is there a specific need where you have to go through the constructor in the Build event?

From Autofac documentation:

An auto-activated component is a component that simply needs to be activated one time when the container is built. This is a “warm start” style of behavior where no method on the component is called and no interface needs to be implemented - a single instance of the component will be resolved with no reference to the instance held.

So this sounds to me, that if you use AutoActivation, the registered type will not be kept in the builders registry, so you would have to re-register it.

If you absolutely need it, than it must be registered twice:

internal class Program
{
    public static IContainer container;


    public MyViewModel MyVM
    {
        get { return Program.container.Resolve<MyViewModel>(); }
    }

    private static void Main(string[] args)
    {
        ContainerBuilder containerBuilder = new ContainerBuilder();
        Console.WriteLine("Before register");
        containerBuilder.RegisterType<MyViewModel>().AutoActivate();
        containerBuilder.RegisterType<MyViewModel>();

        container = containerBuilder.Build();

        Program p = new Program();
        Console.WriteLine("Before property retrieval");
        MyViewModel vm = p.MyVM;
    }
}

internal class MyViewModel
{
    public MyViewModel()
    {
        Console.WriteLine("MyViewModel");
    }
}

Result:

Before register
MyViewModel
Before property retrieval
MyViewModel
Press any key to continue . . .

If you need to run some code at startup, consider IStartable:

public class StartupMessageWriter : IStartable
{
   public void Start()
   {
      Console.WriteLine("App is starting up!");
   }
}

....

var builder = new ContainerBuilder();
builder
    .RegisterType<StartupMessageWriter>()
    .As<IStartable>()
    .SingleInstance(); 

UPDATE

If you absolutely must, you could iterate through all the registrations:

foreach (var r in container.ComponentRegistry.Registrations)
{
     var dump = container.ResolveComponent(r, Enumerable.Empty<Parameter>());
}

If your ViewModels implement a base class, you could use:

foreach (var r in container.ComponentRegistry.Registrations)
{
    if (r.Target.Activator.LimitType.IsSubclassOf(typeof (ViewModel)))
    {
        var dump = container.ResolveComponent(r, Enumerable.Empty<Parameter>());
    }
}

Dirty, but works.

Darek
  • 4,687
  • 31
  • 47
  • Thanks for all the information, I was already trying the double registration, but it felt a bit weird. – D.Rosado Oct 29 '14 at 16:00
  • Initially it felt weird to me, but after reading documentation the behavior is understandable. Basically, AutoActivate will create and dispose the instance for you, but it will not keep it around for later use. So, either "auto-activate" yourself, or use IStartable to perform any type of initialization required, or use the double registration. – Darek Oct 29 '14 at 16:08
  • this tightly couples the ioc-container to the VM, whereas going for real inversion and DI you should rather let the container construct your object with all the dependencies needed - which makes this approach literally an anti-pattern ... it would be nice to address, if you'd addressed this issue in your answer (making `MyVM` injectable via eg ctor). –  Aug 03 '15 at 12:20