Caliburn Micro ViewLocator with different namespace - c#

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.

Related

Activity is a namespace but is used like a type

I was working on the code and accidental faced this error message
Activity is a namespace but is used like a type
namespace MainAppHotelXamarin
{
public class AdminActivity : Activity
{
string dpPath = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), "HotelDb.db3");
Button BtnOrderFood;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.Admin);
BtnOrderFood.Click += BtnOrderFood_Click;
CreateDB();
}
private void BtnOrderFood_Click(object sender, EventArgs e)
{
StartActivity(typeof(FoodOrdersListActivity));
}
public string CreateDB()
{
var db = new SQLiteConnection(dpPath);
}
}
}
All of my activities have this error too, even those ones that I didn't edited them recently.
Anyone knows what's wrong with it? Thanks beforehand
Problem solved. When I was creating new activity to my project in Activity folder, there was a .Activity after the namespace.Problem solved by deleting that one.
The (advisable) default in Visual Studio is to keep Folders and namespaces in sync. So the best course here is to rename that folder to Activities and add that as the tail namespace to all your classes already in that folder.
Activities is also the usual name for that folder in a Droid project.
You have some namespace called "Activity". So you need to either:
1) Rename your namespace
2) Specify class with exactly namespace Android.App.Activity

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.

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());
}
}
}

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

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.

AOP with Ninject Interception, Castle DynamicProxy and WPF window: Can't find XAML resource in DynamicProxy of window

In our real world application we defined an attribute that is used to enable logging in methods or classes (the usual AOP use case). When we apply this attribute to a WPF window class, objects of this class can't be created by Ninject. Here is a minimal example to reproduce the issue:
dummy interceptor for logging:
public class MyInterceptor: IInterceptor
{
public void Intercept(IInvocation invocation)
{
Console.WriteLine("Calling {0} at {1}", invocation.Request.Method.Name, DateTime.Now);
invocation.Proceed();
}
}
the corresponding attribute:
public class MyAttribute: InterceptAttribute
{
public override IInterceptor CreateInterceptor(IProxyRequest request)
{
return new MyInterceptor();
}
}
the window class (completely empty, only the automatically generated empty grid is inside):
[My]
public partial class MainWindow: Window
{
public MainWindow()
{
InitializeComponent();
}
}
and finally the app startup code where the object is requested:
public partial class App: Application
{
private void Application_Startup(object sender, StartupEventArgs e)
{
var kernel = new StandardKernel(new NinjectSettings() { LoadExtensions = false }, new DynamicProxyModule());
var window = kernel.Get<MainWindow>();
window.ShowDialog();
}
}
When requesting the window via kernel.Get<MainWindow>(); an TargetInvocationException is thrown with an inner exception telling me that Castle.Proxies.MainWindowProxy doesn't have a resource specified by URI "/NinjectInterceptionWPF;component/mainwindow.xaml" where NinjectInterceptionWPF is our assembly's short name.
When we look at the automatically created InitializeComponent of MainWindow we can see that an URI is created to address the XAML code, which seems to be missing for the proxy:
System.Uri resourceLocater = new System.Uri("/NinjectInterceptionWPF;component/mainwindow.xaml", System.UriKind.Relative);
#line 1 "..\..\..\MainWindow.xaml"
System.Windows.Application.LoadComponent(this, resourceLocater);
I already played around a bit and tried to use an absolute URI but LoadComponent only accepts relative ones.
Some internet searching shows that a lot of people use Ninject Interception and DynmaicProxy for WPF binding (INotifyPropertyChanged), so I think in general it should be possible to build a proxy of a WPF window.
But how?
The interception extension of Ninject created a new dynamic assembly. This means you won't be able to load resources with a relative path. But the question here is if you really want to create a dynamic proxy for the view. Usually you should do this on your ViewModel instead.

Categories