How to use CM without "Application"-Object

Topics: Bootstrappers & IoC, Getting Started
Jun 9, 2011 at 10:30 AM

I'm referring to this thread and I'm still wondering that there is no answer during the last two weeks.
I assume that nobody understood my question. Let's try to explain it in a different way.

I would like to create a Class Library with several public Methods and WPF-Dialogs and I would like
to use caliburn.Micro within these Class Library.

But as you know, CL does not have an Application.xaml nor Application.class so I'm struggling with this
matter at the very beginning of my project.

The Class Library will be used from Applications using Caliburn and Applications NOT using Caliburn.

Any hints for me? There are no samples of how to use CM ("only") within a Class Library.

Please excuse the new thread, but I believe this matter is quite simple and probably
the original question was a bit weird.

 

Coordinator
Jun 9, 2011 at 12:10 PM

Somehow or at some time you need to run the bootstrapper so that everything is configured. That's all that is required. It may mean that the apps that are using you are asked to run it, or it may mean that you run it internally the first time your code is called. It depends on how you intend your library to be used. In either case, you can run the bootstrapper without an Application. To do that you use this code:

new Bootstrapper(false);

That's it.

Coordinator
Jun 9, 2011 at 12:11 PM

FYI, that's a v1.1 feature. It was added for your specific scenario. Sorry I missed the original forum post. There were others like it that prompted the improvement.

Jun 9, 2011 at 12:46 PM

Sounds quite easy just to start the Bootstrapper. Thank's a lot for your answer.
(Shame on me, didn't recognized Version 1.1. - Keep up the very good work)

 

Jul 11, 2011 at 7:36 AM

As a newbie to caliburn micro.

Is there a sample available  which shows how to use a caliburn micro in a class library and use it in an application that does not use caliburn micro.

I am intending to use caliburn micro within a user control and i am  wondering how to initiate this user control within an application that does not have caliburn micro.

 

 

Jul 13, 2011 at 8:34 AM

Unfortunatley not. And I still got problems at my end, becuase it sounds simple but I still have no clue how to use this feature.

I already mailed a sample to Rob appx. two weeks ago, but I'm still waiting for a reply. Seems that he's quite busy.
But anyone else might help us here?  You could download the minimalistic C#-Project (ZIP) here:

http://www.2shared.com/file/wJmfheTb/Caliburn.html

Coordinator
Jul 13, 2011 at 1:23 PM

I sent you a response in email with a fix. Please update this forum with an explanation when you get a chance.

Jul 14, 2011 at 7:59 AM
Edited Jul 14, 2011 at 8:00 AM

Thank's a lot again for your kind help, Rob.

So, my problem was, das I had no Idea where and how to implement "new Bootstrapper(false);"
Additionally I have to work with VB here instead of C# which makes things sometimes more complicated.As Rob already told, your ClassLibrary has to use it's own Bootstrapper, in case it shall be used by another Assembly without Caliburn.You can use your "usual" Bootstrapper, but you have to add something to the constructor.

    public class AppBootstrapper : Bootstrapper {
        CompositionContainer container;

        // INSERT THIS CONSTRUCTUR!!!
        public AppBootstrapper() : base(false) {}

        protected override void Configure() {
            var aggCatalog =
                new AggregateCatalog(
                    AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>()
                    );

            container = new CompositionContainer(aggCatalog);

            var batch = new CompositionBatch();

            batch.AddExportedValue<IWindowManager>(new WindowManager());
            batch.AddExportedValue<IEventAggregator>(new EventAggregator());
            batch.AddExportedValue(container);

            container.Compose(batch);
        }

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

        protected override object GetInstance(Type serviceType, string key) {
            var contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(serviceType) : key;
            var exports = container.GetExportedValues<object>(contract);

            if(exports.Count() > 0)
                return exports.First();

            throw new Exception(string.Format("Could not locate any instances of contract {0}.", contract));
        }

        protected override IEnumerable<object> GetAllInstances(Type serviceType) {
            return container.GetExportedValues<object>(AttributedModelServices.GetContractName(serviceType));
        }

        protected override void BuildUp(object instance) {
            container.SatisfyImportsOnce(instance);
        }
    }

In your public method of the class library, you will call this bootstrapper before you will use any of the CB-Features:

        public void StartTest() {
            new AppBootstrapper();

            var w = IoC.Get<IWindowManager>();
            w.ShowDialog(new TestViewModel());
        }

For VB.NET-Users, the Bootstrapper-Code would be as follows:

Public Class AppBootstrapper
    Inherits Bootstrapper

    Private container As CompositionContainer

    Public Sub New()
        MyBase.New(False)
    End Sub
 
    '[SNIP]

And the call of the Bootstrapper seems to need a variable-declaration:

Dim x As New AppBootstrapper()

Just "new AppBootstapper" will not compile in VB.Net, although while parsing the C#-Code with RedGates Reflector, it should work.  

Jan 4, 2012 at 9:16 AM

Hi,

I've got the same issue as Akkarin, but I use the strongly typed Bootstrapper as a base for my AppBootstrapper class, which does not have a boolean constructor argument.

How to do it there?

Jan 5, 2012 at 7:53 AM
Edited Jan 5, 2012 at 8:14 AM

Can anyone help me on this issue, because it is a bit urgent...

When I try to instantiate my Boostrapper

public class AppBootstrapper : Bootstrapper<IShellViewModel>

it would throw an NullReferenceException.

Jan 5, 2012 at 6:32 PM
Edited Jan 5, 2012 at 6:34 PM

Hm.... you are using C# or VB?  Because the line you posted is in C#.

Couldn't you just add something like above?

public class AppBootstrapper : Bootstrapper<IShellViewModel> {

        // INSERT THIS CONSTRUCTUR!!!
        public AppBootstrapper() : base(false) {}

}

It may be possible, that the generic Bootstrapper does not have this Boolean parameter.
Don't have sourcecode in hand right now.
Jan 6, 2012 at 7:10 AM

Just checked, and indeed the generic Bootstrapper has no constructor with Parameters.

So it seems that you have to use the non-generic version.

Jan 11, 2012 at 9:14 AM

Hi Akkarin, thanks for the reply.

I'm using the non-generic verson of the bootstrapper now, which I can instanciate without exception.

But it seems to lack of something additional now to get the IoC running.

The instanciation of the WindowManager works fine, but none of my MEF-[Import] attributes works, which worked when I used to start the application as normal WPF application. They remain "null" now. Do I have to add something to the bootstrapper for this?

Jan 11, 2012 at 10:33 AM

I'm currently also still learning, so my answer might not be the best.

If the Import is in the same assembly, it should work automatically.
If it's in another assembly, you could try some of the Catalogs like DirectoryCatalog etc.
If not, you should not have to add anything additional to the bootstrapper.

If you say "Import-Attribute" it sounds that you are using Property-Injection instead of Constructor-Injection.
There could be some issues:

- You forgot the export-attribute?
- You are setting a breakpoint within the constructor and then checking if the Import ist Null?
  This would be the wrong place, because Properties will be injected after the Constructor.

Maybe some sourcecode would help.

Jan 11, 2012 at 12:55 PM
Edited Jan 11, 2012 at 12:59 PM

I started all over with a new project to try it out.

Here's what i did:

1. Created a new CM project with a little view + View model saying "Hello Caliburn Micro!" (the one you get when you add CM via Nuget. Worked fine out of the box. Had the AppBoostrapper adjusted as told above resulting in the application can not be started directly any more.

2. Created a helper class to start the window from another assembly without needing to know anything about CM

    public class ShowWindowHelper
    {
        public void ShowWpfWindow()
        {
            new AppBootstrapper();
            
            var windowManager = IoC.Get<IWindowManager>();
            windowManager.ShowDialog(new ShellViewModel());
        }
    }

3. Created a Winforms-Project in the same solution, added a reference to wpf assembly and a button with the code

new ShowWindowHelper().ShowWpfWindow();

Running it and pressing the button results in a form with just the text"Cannot find view for MyProject.ShellViewModel.".

In the other project i could run the form (and it actually found the view), but no [Import]-marked properties were filled in the ShellViewModel instance, neither before nor after the constructor. I cannot use the Constructor-Injection here because I create the instance as told above. If I use the IoC to create the ViewModel (and inject the dependencies), I get the "Could not locate any instances of contract" exception. This originally lead me to the question if there is something else necessary to geht the IoC running.

Jan 11, 2012 at 1:15 PM

Hm...

is it possible that you either forgot the Configure-Method within AppBootrapper or you missed an Export of "ShellViewModel"?
Otherwise I unfortunately have no real idea. Maybe Rob will take a look at this.

Coordinator
Jan 11, 2012 at 1:39 PM

By default, the Bootstrapper automatically registers the Application's entry assembly in the AssemblySource. Since you changed what the entry assembly is, and the view is in a different assembly, Caliburn.Micro is no longer searching the appropriate assemblies for views. In you bootstrapper, you should override SelectAssemblies and return all assemblies that contain views, since the default implementation no longer is sufficient for your scenario.

Jan 11, 2012 at 2:23 PM

Perfect, this did the trick!

So the answer was here in your article all the time, i just didn't see it:

    protected override IEnumerable<Assembly> SelectAssemblies()  
    {  
        return new[] {  
            Assembly.GetExecutingAssembly()  
        };  
    }  
Thanks to both of you for the time and the patience.

Jan 16, 2012 at 12:45 PM

Now another problem popped up:

Since I am calling the window from the "outside", it is also (and should be) possible to call it more than once.

But if I open the window for the second time, the method GetInstance in the bootstrapper doesn't find any exports for my ShellViewModel any more.

I tried implementing IDisposable and decorated the ShellViewModel with [PartCreationPolicy(CreationPolicy.NonShared)] but nothing helped. I am not even shure i was trying to solve this in the right direction...

Jan 18, 2012 at 7:31 AM

We found the solution to this one ourselfs:

Since the class AssemblySource is static, the Assembly was added to the Instance collection more than once, which somehow confused GetExportedValues. The following adjustment to the code mentioned above helped:

        protected override IEnumerable<Assembly> SelectAssemblies()
        {
            return AssemblySource.Instance.Any() ? 
                new Assembly[] { } : 
                new[] { typeof(AppBootstrapper).Assembly };
        }
Aug 3, 2012 at 9:00 PM

I used your solution and works fine for me.

Now i have problems with theming.

Because there is no application object, how can i add global ResourceDictionary globaly from code?

If i add for example ResourceDictionary to Application.Resources theming is fine, but when i add ResourceDictionary to UserControl or Window theming is not ok.

Does someone has some idea?

Feb 24 at 9:36 AM
Hi,
I am Novice to CM. I am in the same situation when Akkarin started this discussion and Eisenberg's reply to use "new Bootstrapper(false)". Well, I guess this was working with older versions of CM, but now the current version what I am using is "v1.5.2.0" and this does not support "new Bootstrapper(false)" any more.

Background: I am intending to introduce CM in an Class Library project under an existing application which does not use CM (A simple WPF application with MVVM).

Anybody could help me with a suggestion how to counter attack this problem. A demo code would be an added advantage.

Thanks in advance...
Mar 18 at 2:36 PM
ssak32 wrote:
Hi,
I am Novice to CM. I am in the same situation when Akkarin started this discussion and Eisenberg's reply to use "new Bootstrapper(false)". Well, I guess this was working with older versions of CM, but now the current version what I am using is "v1.5.2.0" and this does not support "new Bootstrapper(false)" any more.

Background: I am intending to introduce CM in an Class Library project under an existing application which does not use CM (A simple WPF application with MVVM).

Anybody could help me with a suggestion how to counter attack this problem. A demo code would be an added advantage.

Thanks in advance...
Add the following constructor to your AppBootstrapper class:
public AppBootstrapper() : base(false) { }
and start your library by:
new YourNamespace.AppBootstrapper().Start();
var w = IoC.Get<IWindowManager>();
return w.ShowDialog(new YourNamespace.ViewModels.MainWindowViewModel());
Apr 3 at 9:23 PM
Edited Apr 3 at 9:28 PM
ssak32,

You have to use the non-generic form of the bootstrapper, like so:
public class AppBootStrapper : BootstrapperBase
    {
        public AppBootStrapper() : base(false)
        {
        }
Apr 3 at 10:43 PM
Indeed I did miss the part about version 1.5.2 removing that base constructor. Neither did I realize I was working with an old version.

Nice find on the solution, Pete