Caliburn.Micro can't match View and ViewModel from different assemblies - c#

I just started with Caliburn.Micro.
I'm trying to bootstrap my simple sample solution placing the ShellView (usercontrol) in an Test.App assembly, and the ShellViewModel in the Test.ViewModel assembly.
What i get is a window with the following text: "Cannot find view for Caliburn.Test.ViewModel.ShellViewModel".
But if I move the ViewModel to the .App assembly, it works perfectly.
this is the Bootstraper in the Caliburn.Micro.Test assembly (executable):
namespace Caliburn.Micro.Test
{
public class AppBootstrapper : BootstrapperBase
{
SimpleContainer container;
public AppBootstrapper()
{
this.Start();
}
protected override void Configure()
{
container = new SimpleContainer();
this.container.Singleton<IWindowManager, WindowManager>();
this.container.Singleton<IEventAggregator, EventAggregator>();
this.container.PerRequest<IShell, ShellViewModel>();
}
protected override object GetInstance(Type service, string key)
{
var instance = this.container.GetInstance(service, key);
if (instance != null)
return instance;
throw new InvalidOperationException("Could not locate any instances.");
}
protected override IEnumerable<object> GetAllInstances(Type service)
{
return this.container.GetAllInstances(service);
}
protected override void BuildUp(object instance)
{
this.container.BuildUp(instance);
}
protected override void OnStartup(object sender, System.Windows.StartupEventArgs e)
{
this.DisplayRootViewFor<IShell>();
}
protected override IEnumerable<System.Reflection.Assembly> SelectAssemblies()
{
var assemblies = new List<Assembly>()
{
Assembly.GetExecutingAssembly(),
Assembly.Load("Caliburn.Micro.Test.ViewModel"),
};
return assemblies;
}
}
}
this is my ViewModel in the Caliburn.Micro.Test.ViewModel assembly (class library):
namespace Caliburn.Micro.Test.ViewModel
{
public interface IShell
{
}
public class ShellViewModel : IShell
{
}
}
Can you help me solve my problem, please?
Thank you! :D

Check that you have selected your assembly for CM by overriding SelectAssemblies in your bootstrapper.
The documentation here has an example:
http://caliburnmicro.codeplex.com/wikipage?title=Customizing%20The%20Bootstrapper
protected override IEnumerable<Assembly> SelectAssemblies()
{
return new[] {
Assembly.GetExecutingAssembly()
};
}
Edit:
Ok not only do you need to select assemblies to tell CM where to look - it sounds like in your case your VMs and your Views may be in different namespaces since you have them in separate libraries. You can use the same root namespace in both libraries and the standard view resolution should work fine - however, you need to make sure you have selected the assembly in the bootstrapper in order to tell CM what assemblies to try to resolve views in.
If you want to put your views/VMs in different namespaces for some reason or another, you need to customise the logic that CM uses to resolve a view. It uses naming conventions to locate a View based on the fully qualified type name of the viewmodel (or vice-versa if you are using a view-first approach)
I suggest reading up on the introductory documentation:
http://caliburnmicro.codeplex.com/wikipage?title=Basic%20Configuration%2c%20Actions%20and%20Conventions&referringTitle=Documentation
Then follow it through. If you want to skip directly to naming conventions, check out this particular page:
http://caliburnmicro.codeplex.com/wikipage?title=View%2fViewModel%20Naming%20Conventions&referringTitle=Documentation
and
http://caliburnmicro.codeplex.com/wikipage?title=Handling%20Custom%20Conventions&referringTitle=Documentation

Solved thanks to this article
http://www.jerriepelser.com/blog/split-views-and-viewmodels-in-caliburn-micro/
EDIT: since you integrated your reply with mine I change the accepted answer to be yours.

Related

Prism with Unity wrong instance gets passed into constructor

I am trying to understand/learn Prism with Unity
I created following classes:
==========================================
Seperate Assembly containing a "Module":
using GlobalContracts;
using Prism.Ioc;
using Prism.Modularity;
namespace ModuleA
{
[Module(ModuleName = MyModuleA.NAME, OnDemand = true)]
public class MyModuleA : IModule
{
public const string NAME = "MyModuleA";
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.Register<MyControlA>();
containerRegistry.Register<IView, MyControlA>(NAME);
containerRegistry.Register<PluginViewModelBase, MyControlViewModel>(NAME);
}
public void OnInitialized(IContainerProvider containerProvider)
{
}
}
}
==========================================
A ViewModel
using GlobalContracts;
namespace ModuleA
{
public class MyControlViewModel : PluginViewModelBase
{
public MyControlViewModel(IView view) : base(view)
{
}
}
}
==========================================
The Host Application (other assembly):
public partial class App : PrismApplication
{
private Shell mShell;
private ShellViewModel mShellViewModel;
protected override IModuleCatalog CreateModuleCatalog()
{
return new DirectoryModuleCatalog(){ModulePath = #"..\..\..\..\ModulesOutput"};
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.Register<IView, Shell>();
containerRegistry.Register<IViewModel, ShellViewModel>();
}
protected override Window CreateShell()
{
mShellViewModel = Container.Resolve<ShellViewModel>();
mShell = (Shell)mShellViewModel.View;
return mShell;
}
(...)
Now my question is:
How do I tell Prism to resolve the IView-Parameter passed to the
constructor of the ViewModel properly?
It resolves it as "Shell" and not as "MyControlA".
Further tips regarding my code are welcome
I found some sources in the web but they used "RegisterType" method of a container. And for now I do not have dependencies to Unity in my ModuleA and I would not know how to get the container to call the "RegisterType". All sources are outdated in the web..
By default, it resolves the default registration, which in your case is Shell.
Registering a type with a name does not mean that that name is automatically used to resolve dependencies. You have to do that manually, with parameter override, injection factory or the like. But I'd try to avoid that as it makes things a bit fragile and tedious.

DI Unity AssemlyInformation.Title ModuleAttribute.ModuleName and load module on demand/from directory

i started to work with prism + unity DI. I have two problems and questions until now.
1. Problem/Question
I would like to use the AssemblyInformation Title as ModuleAttribute.ModelName.
Problem:
This doesnt work:
[Module(ModuleName = AssemblyName.GetAssemblyName(Assembly.GetExecutingAssembly().FullName).Name)]
This works well.
[Module(ModuleName ="Module1")]
Question:
Is there any possibility to use AssemblyInformation.Title as ModuleAttribute.Modulename?
2. Problem/Question
I would like to load on demand from Directory my Modules, but only modules which has a special prefix in the module name.
Problem:
If I check my ModuleCatalog, then is still empty before I initialze the modules.
Question:
Is it possible to check before module init the name of the module, because I do not want to exeute the method Initialize() inide of the Module.
My Bootstrapper:
protected override IModuleCatalog CreateModuleCatalog()
{
return new DirectoryModuleCatalog() { ModulePath = #".\Modules" };
}
protected override void ConfigureContainer()
{
base.ConfigureContainer();
}
protected override void InitializeModules()
{
ModuleCatalog moduleCatalog = (ModuleCatalog) this.ModuleCatalog;
// Here I would like to discover my modules with special modulename prefix.
//If the prefix is not found, then I dont want to init the module.
try
{
base.InitializeModules();
}
catch (DuplicateModuleException e)
{
}
}
Module:
[Module(ModuleName = "MyPrefix-Module1")]
public class PrismModule1Module : IModule
{
IRegionManager _regionManager;
public PrismModule1Module(IRegionManager regionManager)
{
_regionManager = regionManager;
}
public void Initialize()
{
Debug.WriteLine("Module init");
}
}
There is no such built-in functionality and cannot be because in general module is a class (implementing IModule interface) and an assembly may contain several modules.
This is achievable, however, by overriding the default implementation.
You need to mark modules as loaded on-demand. You need to initialize the catalog so that module information has been read (about on-demand loading). Then you may query module information and actually load the modules.

Caliburn Micro ViewLocator with different namespace

I'm using Caliburn Micro for MVVM. Now I have the following situation. I have a UserControl with View and ViewModel in my first assembly assembly1 in namespace1. If I use it in an second assembly assembly2 that has the same namespace namespace1 (it´s in the same solution) everything works fine.
Now I'd like to use my ViewModel in another Solution with namespace namespace3. If I try this I always get the error, that View couldn't be located.
I build up a workaround that sets the Binding manually in the bootstrapper (using Ninject).
protected override void Configure()
{
_kernel = new StandardKernel();
_kernel.Bind<OverlayManagerView>().To<OverlayManagerView>().InSingletonScope();
_kernel.Bind<OverlayManagerViewModel>().To<OverlayManagerViewModel>().InSingletonScope();
base.Configure();
}
protected override void OnStartup(object sender, System.Windows.StartupEventArgs e)
{
ViewModelBinder.Bind(IoC.Get<OverlayManagerViewModel>(), IoC.Get<OverlayManagerView>(), null);
...
}
This is working, but if I'd like to use my ViewModels from assembly1 I won't always set the Binding manually and as Singleton.
Is there a way to tell the Caliburn ViewLocator that Views might be at a different namespace?
I tried following not working...
ViewLocator.AddNamespaceMapping("namespace1", "namespace3");
ViewLocator.AddNamespaceMapping("namespace1", "namespace1");
ViewLocator.AddNamespaceMapping("namespace3", "namespace1");
Maybe someone knows a solution.
In your Configuremethod you should use :
ViewLocator.AddSubNamespaceMapping("ViewModelsNamespace", "ViewsNamespace");
and you have to override the following method :
protected override IEnumerable<Assembly> SelectAssemblies()
{
var assemblies = new List<Assembly>();
assemblies.AddRange(base.SelectAssemblies());
//Load new ViewModels here
string[] fileEntries = Directory.GetFiles(Directory.GetCurrentDirectory());
assemblies.AddRange(from fileName in fileEntries
where fileName.Contains("ViewModels.dll")
select Assembly.LoadFile(fileName));
assemblies.AddRange(from fileName in fileEntries
where fileName.Contains("Views.dll")
select Assembly.LoadFile(fileName));
return assemblies;
}
in order to let Caliburn know about your new dlls.

Simple Prism Bootstrapper

im currently porting my program to using Prism 6, it's a WPF application.
So i installed Prism.Unity (6.1.1) which came with Prism.Wpf (6.1.0), Prism.Core (6.1.0), Unity (4.0.1) and CommonServiceLocator (1.3.0).
Then i came along those PRISM samples, but for the love of god i can't get it to run.
Here's my Bootstrapper:
public class Bootstrapper : Prism.Unity.UnityBootstrapper
{
/// <exception cref="ActivationException">If there are errors resolving the shell instance.</exception>
protected override DependencyObject CreateShell()
{
return Container.Resolve<Shell>();
}
protected override void InitializeShell()
{
base.InitializeShell();
Application.Current.MainWindow = (Window)this.Shell;
Application.Current.MainWindow.Show();
}
protected override void ConfigureContainer()
{
base.ConfigureContainer();
this.RegisterTypeIfMissing(typeof(IWorkRepository), typeof(WorkRepository), true);
}
}
Unfortunately i can't start it. VS 2015 says it needs System.Runtime to run
return Container.Resolve<Shell>();
but once added the whole class is marked as error. If i start it directly i get the exception it couldn't load Microsoft.Practices.ServiceLocation.
I'm wondering of the dependency since several posts (including ms) suggests to remove all Practices.*.
Help would be really appreciated, since i can't get it to run. :(
What usings do you use?
The whole bootstrapper can be as simple as this (created by the Prism-template):
using Microsoft.Practices.Unity;
using Prism.Unity;
using PrismUnityApp2.Views;
using System.Windows;
namespace PrismUnityApp2
{
class Bootstrapper : UnityBootstrapper
{
protected override DependencyObject CreateShell()
{
return Container.Resolve<MainWindow>();
}
protected override void InitializeShell()
{
Application.Current.MainWindow.Show();
}
}
}
and System.Runtime isn't needed as reference. Probably you inadvertently use a namespace from that (instead of Microsoft.Practices.Unity where the Container.Resolve<> extension is).

How to register views automatically in Xamarin.Forms with Prism

In Xamarin.Forms with Prism and Unity, is there a way to register all the views that are subject to navigation without specifying them explicitly?
The sample project provided by Prism, has a function RegisterTypes in the App.xaml.cs that has the following line :
Container.RegisterTypeForNavigation<MainPage>();
I expect this to be much larger at some point of developping the application.
I am no expert of Unity, but I was trying some ways with the DependencyService, or the IUnityContainer, without any success.
Container.Registrations.Where(cm => cm.RegisteredType == typeof (IView));
Container.ResolveAll<IView>();
DependencyService.Get<IEnumerable<IView>>();
So how would I go about registering all the views (or at least a subset of the views, that for example, implements a given interface) for navigation?
With a tiny bit of reflections you could register all types of the core assembly that inherit from Page.
public class Bootstrapper : UnityBootstrapper
{
protected override void OnInitialized()
{
NavigationService.Navigate("MainPage");
}
protected override void RegisterTypes()
{
RegisterAllPages();
}
private void RegisterAllPages()
{
var pageBaseTypeInfo = typeof(Page).GetTypeInfo();
var types = GetType().GetTypeInfo().Assembly.DefinedTypes;
var pageTypeInfos = types
.Where(x => x.IsClass && pageBaseTypeInfo.IsAssignableFrom(x));
foreach (var page in pageTypeInfos)
{
// the next two lines do what RegisterTypeForNavigation does
Container.RegisterType(typeof(object), page.AsType(), page.Name);
PageNavigationRegistry.Register(page.Name, page.AsType());
}
}
}

Categories