Search code examples
c#wpfasynchronouscaliburn.microsimple-injector

Caliburn.Micro Bootstrapper 'BuildUp' method throws exception when Simple Injector is used


I had no problems with the built-in CM SimpleContainer, but today I needed to go to Simple Injector.

When I call an asynchronous method through cal:Message.Attach the Bootstrapper's BuildUp method raises an exception:

An exception of type 'SimpleInjector.ActivationException' occurred in SimpleInjector.dll but was not handled in user code

Additional information: The constructor of type SequentialResult contains the parameter with name 'enumerator' and type IEnumerator<IResult> that is not registered. Please ensure IEnumerator<IResult> is registered, or change the constructor of SequentialResult.

Here's my bootstrapper class:

    protected override void Configure()
    {
        _container.RegisterSingleton<IEventAggregator, EventAggregator>();
        _container.RegisterSingleton<IWindowManager, WindowManager>();

        _container.Verify();
    }

    protected override object GetInstance(Type service, string key)
    {
        var instance = _container.GetInstance(service);

        if (instance != null)
            return instance;

        throw new InvalidOperationException("Could not locate any instances.");
    }

    protected override IEnumerable<object> GetAllInstances(Type service)
    {
        IServiceProvider provider = _container;
        Type collectionType = typeof(IEnumerable<>).MakeGenericType(service);
        var services = (IEnumerable<object>)provider.GetService(collectionType);
        return services ?? Enumerable.Empty<object>();
    }

    protected override void BuildUp(object instance)
    {
        var registration = _container.GetRegistration(instance.GetType(), true);
        registration.Registration.InitializeInstance(instance);
    }

    protected override IEnumerable<Assembly> SelectAssemblies()
    {
        return new[] { Assembly.GetExecutingAssembly() };
    }  

Part of XAML:

    <Border Grid.Row="2" Padding="10" Background="#F0F0F0" BorderBrush="#DFDFDF" BorderThickness="0,1,0,0">
        <StackPanel Orientation="Horizontal">
            <Button IsCancel="True" Content="{Resx Key=Close}" />
            <Button IsDefault="True" MinWidth="{Resx Key=CheckOrUpdateBtnWidth, DefaultValue='115'}" Margin="8,0,0,0"
                    cal:Message.Attach="CheckUpdateAsync" />
        </StackPanel>
    </Border>

Part of VM:

    public async Task CheckUpdateAsync()
    {
        IsUpdateDownloading = true;

        try
        {
            await Task.Run(async () =>
            {
                Cts = new CancellationTokenSource();

                var http = new HttpClient();
                HttpResponseMessage rm = await http.GetAsync(UpdateInfo.DownloadUri, HttpCompletionOption.ResponseHeadersRead, Cts.Token);

                long size = rm.Content.Headers.ContentLength.GetValueOrDefault();

                var downloader = FileDownloader.Create(UpdateInfo.DownloadUri);

                byte[] data = downloader.Download(Cts.Token);
                downloader.ValidateHash(data, CloudManager.UpdateInfo.Sha256);
            });
        }
        catch (OperationCanceledException) { }
        catch (Exception ex)
        {
            Logger.Error(ex);
            throw;
        }
        finally
        {
            IsUpdateDownloading = false;
            ProgressValue = 0;
        }
    }

What am I doing wrong?


Solution

  • Using Message.Attach() will internally in C.M. use CoRoutines. Looking at the source of C.M. I see this code:

     public static Func<IEnumerator<IResult>, IResult> CreateParentEnumerator = 
         inner => new SequentialResult(inner);
    

    i.e. if not overwritten the default SequentialResult<IResult> is used.

    Furtheron:

     var enumerator = CreateParentEnumerator(coroutine);
     IoC.BuildUp(enumerator);
    

    This is were the exception comes from. BuildUp is called internally and as you are calling directly into the container to find the registration Simple Injector will throw an ActivationException.

    In most cases you won't need to implement the BuildUp method at all. I can't think of any reason why you would want to use this method when you use Simple Injector as your DI container. I normally don't implement this method.

    BuildUp is a method which you would typically need if you need to inject (using property injection) into components that can't be created using the normal Simple Injector pipeline. You can read about the details in the Simple Injector documentation here.

    In this case I think however that you don't need to Build Up the SequentialResult in this case. There is nothing you need to inject in this C.M. default class.

    So the question here is are there any classes you need to BuildUp? Your application design should normally only use classes defined in the application itself which you register in Simple Injector and which can be resolved directly using GetInstance().

    If the answer is 'no', you never inject dependencies in external classes, remove the BuildUp() method completely.

    If you however need to BuildUp() other classes, this will only make sense if you override the default PropertyInjectionBehaviour. If you don't override this, making a call to registration.InitializeInstance doesn't make sense at all, because Simple Injector doesn't know what to inject, because Simple Injector only supports Explicit Property Injection out of the box. You can create some sort of implicit behavior when overriding the default PropertyInjectionBehaviour.

    To summarize, I think you should just remove the BuildUp method completely as the current implementation doesn't do anything. InitializeInstance will inject nothing.