MEF problem with import - wpf

I have problem with import shell-view-model to view-model class, I use MEF.
Shell-view-model :
namespace Spirit.ViewModels
{
using Caliburn.Micro;
using System.ComponentModel.Composition;
public interface IShellViewModel
{
void ShowLogOnView();
void ShowMessengerView();
}
[Export(typeof(IShellViewModel))]
public class ShellViewModel : Conductor<IScreen>, IShellViewModel
{
public ShellViewModel()
{
ShowLogOnView();
}
public void ShowLogOnView()
{
ActivateItem(new LogOnViewModel());
}
public void ShowMessengerView()
{
ActivateItem(new LogOnViewModel());
}
}
}
I need this class import in view-model class:
[Export]
public class LogOnViewModel : Screen, IDataErrorInfo
{
[Import]
private IShellViewModel _shellViewModel;
public void LogOn(string nick, string password)
{
IMessengerViewModel vm = IoC.Get<MessengerViewModel>();
_shellViewModel.ShowMessengerView();
}
}
Problem is after initialize is variable _shellViewModel null.
My bootstraper look like this:
public class MefBootStrapper : Bootstrapper<IShellViewModel>
{
}
MY SOLUTION:
I create interface assembly and refer this assembly in external service dll and also in wpf app.
In bootstraper I load this assembly with reflection:
var catalog =
new AggregateCatalog(
AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>());
catalog.Catalogs.Add(
new AssemblyCatalog(string.Format(
CultureInfo.InvariantCulture, "{0}{1}", System.IO.Directory.GetCurrentDirectory(), #"\Pokec_Toolkit.dll")));
_container = new CompositionContainer(catalog);
Than I create conductor class:
public interface IShellViewModel
{
void ShowLogOnView();
void ShowMessengerView();
}
[Export(typeof(IShellViewModel))]
public class ShellViewModel : Conductor<IScreen>, IShellViewModel
{
public ShellViewModel()
{
ShowLogOnView();
}
public void ShowLogOnView()
{
ActivateItem(IoC.Get<LogOnViewModel>());
}
public void ShowMessengerView()
{
ActivateItem(IoC.Get<MessengerViewModel>());
}
}
And in view-model I have this:
[Export]
public class LogOnViewModel : Screen, IDataErrorInfo, ILogOnViewModel
{
[Import]
private IShellViewModel _shellViewModel;
[Import]
private IPokecConnection _pokecConn;
//this method is bind on event click of button
public void LogOn(string nick, string password)
{
//SHOW NEW WIEW
_shellViewModel.ShowMessengerView();
}
}

Related

Proper way to handle prism navigation exception

I am using Prism Wpf 8.1.97, and DryIoc container.
In my scenario i have to navigate between two views, so I implemented the INavigationAware Interface provided by Prism Framework in my viewModels.
I call the IRegionManager RequestNavigate method to perform the navigation from the VMA to VMB.
Unfortunately, I forgot to register a IService dependency that is needed in the VMB Ctor, so when I perform the navigation process I get the exception in the navigation callback.
The IService interface
public interface IService
{
}
ViewModel A:
public class ViewAViewModel : BindableBase, INavigationAware
{
private readonly IRegionManager regionManager;
public ViewAViewModel(IRegionManager regionManager)
{
this.regionManager = regionManager;
}
public void OnNavigatedTo(NavigationContext navigationContext)
{
regionManager.TryRequestNavigate("ContentRegion", "ViewB", new NavigationParameters());
}
public bool IsNavigationTarget(NavigationContext navigationContext)
{
return true;
}
public void OnNavigatedFrom(NavigationContext navigationContext)
{
}
}
ViewModelB:
public class ViewBViewModel : BindableBase, INavigationAware
{
public ViewBViewModel(IService service)
{
}
public bool IsNavigationTarget(NavigationContext navigationContext)
{
return true;
}
public void OnNavigatedFrom(NavigationContext navigationContext)
{
}
public void OnNavigatedTo(NavigationContext navigationContext)
{
}
}
I created an extension method to keep it simple..
public static class IRegionManager_Extensions
{
public static void TryRequestNavigate(this IRegionManager regionManager, string regionName, string target, NavigationParameters navigationParameters)
{
regionManager.RequestNavigate(regionName, target, NavigationResult =>
{
if (NavigationResult.Result == false)
{
MessageBox.Show(NavigationResult.Error.InnerException.Message, "Navigation Exception", MessageBoxButton.OK, MessageBoxImage.Error);
}
}, navigationParameters);
}
}
Unfortunately what I get from the InnerException.Message is
ContainerException: code: Error.UnableToResolveFromRegisteredServices;
message: Unable to resolve Resolution root
Module1.ViewModels.ViewBViewModel from container without scope with
Rules with {TrackingDisposableTransients,
UseDynamicRegistrationsAsFallbackOnly, FuncAndLazyWithoutRegistration,
SelectLastRegisteredFactory} and without
{ThrowOnRegisteringDisposableTransient,
UseFastExpressionCompilerIfPlatformSupported} with
FactorySelector=SelectLastRegisteredFactory with
Made={FactoryMethod=ConstructorWithResolvableArguments} with normal
and dynamic registrations: (DefaultDynamicKey(0), {FactoryID=178,
ImplType=Module1.ViewModels.ViewBViewModel, Reuse=TransientReuse,
HasCondition})
without no reference to "IService"
I need to show that the navigation failed because IService implementation is not found in the container.
Is there a way to get the missing service Interface name from the exception?
Service name in the exception is null
Thanks.

Navigation between lazy loaded modules with Prism and WPF

Hello I'm trying to setup an architecture where only one module gets booted when the app is launched. Then I'd like to lazy load other modules based on the user's actions.
To achieve this in my app.xaml.cs I have one module loaded at bootstrap time (MainModule), and an other has InitializationMode = InitializationMode.OnDemand
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
{
Type BlipModuleType = typeof(BlipModule);
moduleCatalog.AddModule(new ModuleInfo()
{
ModuleName = BlipModuleType.Name,
ModuleType = BlipModuleType.AssemblyQualifiedName,
InitializationMode = InitializationMode.OnDemand
});
moduleCatalog.AddModule<MainModule>();
}
then my main module, which displays the view correctly, has a single view registered to the only region available:
public class MainModule : IModule
{
private readonly IRegionManager _regionManager;
public MainModule(IRegionManager regionManager)
{
_regionManager = regionManager;
}
public void OnInitialized(IContainerProvider containerProvider)
{
_regionManager.RegisterViewWithRegion(RegionNames.ContentRegion, typeof(ViewA));
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
}
}
The lazy loaded module has the same structure, registering a different view (which works properly if i decide to use it as my main module)
public class BlipModule : IModule
{
private readonly IRegionManager _regionManager;
public BlipModule(IRegionManager regionManager)
{
_regionManager = regionManager;
}
public void OnInitialized(IContainerProvider containerProvider)
{
_regionManager.RegisterViewWithRegion(RegionNames.ContentRegion, typeof(ViewB));
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
}
}
finally I have a Command in the viewmodel of my MainModule ViewA, that is supposed to load the new module and navigate to it.
public class ViewAViewModel : BindableBase
{
const string BlipModuleName = "BlipModule";
public ReactiveCommand ChangeRoute { get; set; } = new ReactiveCommand();
public ViewAViewModel(IRegionManager regionManager, IModuleManager moduleManager)
{
ChangeRoute.Subscribe(res =>
{
moduleManager.LoadModule(BlipModuleName);
});
moduleManager.LoadModuleCompleted += (s, e) =>
{
if (e.ModuleInfo.ModuleName == BlipModuleName)
{
regionManager.RequestNavigate(RegionNames.ContentRegion, new Uri(BlipModuleName, UriKind.Relative));
}
};
}
}
The viewB of the BlipModule is actually loaded (I get a hit if I set a breakpoint in the view's constructor), but instead of the view I get a white page with "System.Object" inside of it.
Any idea? thanks!
You want to RegisterForNavigation instead of RegisterViewWithRegion.

Caliburn Micro content control navigation

I'm using caliburn micro for this project.
I have my ShellView with my contentcontrol:
<ContentControl x:Name="ActiveItem"
Grid.Row="0" Grid.Column="0" />
In ShellViewModel i got it to show my usercontrol LoginView with:
public class ShellViewModel : Conductor<object>
{
public ShellViewModel()
{
ActivateItem(new LoginViewModel());
}
public void ShowSignUp()
{
ActivateItem(new SignUpViewModel());
}
}
However, i can't navigate to SignUpView from LoginView with my button:
<!-- Row 4 -->
<Button x:Name="ShowSignUp"
Content="Sign Up Now!"
Grid.Row="3" Grid.Column="1"
Style="{StaticResource LoginBtnsStyle}" />
LoginViewModel deriving from ShellViewModel:
public class LoginViewModel : ShellViewModel
{
}
How do i navigate from LoginView to SignUpView with a button that is on the LoginView?
I'm getting no errors, it just isn't changing view.
I also tried putting ShowSignUp() on the LoginViewModel but no success.
Update 1 ShellViewModel:
public class ShellViewModel : Conductor<object>, IHandle<ActionInvokedMessage>
{
DispatcherTimer dt = new DispatcherTimer();
private SplashScreenViewModel _splashVM;
private LoginViewModel _loginVM;
private SignUpViewModel _signUpVM;
private IEventAggregator _eventAggregator;
public ShellViewModel(SplashScreenViewModel splashVM, LoginViewModel loginVM, SignUpViewModel signUpVM)
{
_loginVM = loginVM;
_signUpVM = signUpVM;
_splashVM = splashVM;
ActivateItem(_splashVM);
dt.Tick += new EventHandler(Dt_Tick);
dt.Interval = new TimeSpan(0, 0, 2);
dt.Start();
}
private void Dt_Tick(object sender, EventArgs e)
{
dt.Stop();
ActivateItem(_loginVM);
}
public ShellViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
_eventAggregator.Subscribe(this);
ActivateItem(new LoginViewModel(_eventAggregator));
}
public void Handle(ActionInvokedMessage message)
{
ActivateItem(message.Page);
}
public void ShowSignUp()
{
ActivateItem(new SignUpViewModel());
}
}
You could achieve this using EventAggregator to publish indicative messages from LoginViewModel to ShellViewModel to update the UI.
To begin with, you need to define an message class, which would tells the ShellViewModel which ViewModel needs to be changed. For example,
public class ActionInvokedMessage
{
public Screen Page { get; set; }
}
The Page property would indicate which Screen needs to be loaded. Now, you could change your LoginViewModel as the following.
public class LoginViewModel: Screen
{
private IEventAggregator _eventAggregator;
public LoginViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
_eventAggregator.Subscribe(this);
}
public void ShowSignUp()
{
_eventAggregator.PublishOnUIThread(new ActionInvokedMessage { Page = new SignupViewModel() }); ;
}
}
The PublishOnUIThread method would broadcast a message to all the listeners of the Message Type ActionInvokedMessage for the change. Next step would be to ensure the ShellViewModel would be listening to the change.
public class ShellViewModel : Conductor<object>, IHandle<ActionInvokedMessage>
{
private IEventAggregator _eventAggregator;
public ShellViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
_eventAggregator.Subscribe(this);
ActivateItem(new LoginViewModel(_eventAggregator));
}
public void Handle(ActionInvokedMessage message)
{
ActivateItem(message.Page);
}
public void ShowSignUp()
{
ActivateItem(new SignupViewModel());
}
}
The Implementation of IHandle interface allows us to handle the action that would be required to be executed when the ShellViewModel recieves the ActionInvokedMessage. As seen in the code, this would be an appropriate place to use the ActivateItem method to load the Signup Page.
You can create an interface for navigation and use it in view models to navigate around app.
interface INavigation {
void NavigateTo(System.Type typeId);
}
class ShellViewModel: Conductor<object>, INavigation {
private List<object> pages = new List<Object>();
public ShellViewModel() {
pages.Add(new SignupViewModel(this));
pages.Add(new LoginViewModel(this));
}
void NavigateTo(System.Type typeId) {
var page = pages.Where(x => x.GetType() == typeId).FirstOrDefault()
ActivateItem(page);
}
}
class SignupViewModel {
public SignupViewModel(INavigation navigation) {
this.ShowLoginCommand= new NavigateCommand<LoginViewModel>(navigation);
}
}
class LoginViewModel {
public LoginViewModel (INavigation navigation) {
this.ShowSignUpCommand = new NavigateCommand<SignupViewModel>(navigation);
}
}
Navigation command may be implemented like follows:
public class NavigateCommand<T> : ICommand
{
public event EventHandler CanExecuteChanged;
private readonly INavigation navigation;
public NavigateCommand(INavigation navigation)
{
this.navigation = navigation;
}
public bool CanExecute(object parameter) => true;
public void Execute(object parameter) => this.navigation.NavigateTo(typeof(T));
}
Here I pass System.Type but you can design type that better describes navigation request so that you may pass additinal paramters.

MEF, Prism and new view instance on navigation

The code below worked on shared instance of the view. Now what I'm trying to achieve is each time I navigate to ViewB I want a new instance of the view and its backing view model. I have tried various combinations of the below but they all seem to ultimately end with the RequestNavigate failing silently the second time I try to navigate to ViewB...
I have also tried setting IsNaviagtionTarget to false after the view has been navigated to once.
Bootstrapper:
public void Initialize()
{
_regionManager.RegisterViewWithRegion(RegionNameConstants.MainRegion, typeof(ViewA));
_regionManager.RegisterViewWithRegion(RegionNameConstants.MainRegion, typeof(ViewB));
}
ViewB (class):
[RegionMemberLifetime(KeepAlive = false)]
[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
internal partial class ViewB
{
[ImportingConstructor]
public ViewB(ViewBViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
}
ViewBViewModel:
[Export(typeof(ViewBViewModel))]
[PartCreationPolicy(CreationPolicy.NonShared)]
internal class ViewBViewModel : BindableBase, INavigationAware
{
public void OnNavigatedTo(NavigationContext navigationContext)
{
}
public bool IsNavigationTarget(NavigationContext navigationContext)
{
return true;
}
public void OnNavigatedFrom(NavigationContext navigationContext)
{
}
}
ViewA simply has a button with a command that calls:
ViewA Navigation command:
public override void Execute(object parameter)
{
_regionManager.RequestNavigate(RegionNameConstants.MainRegion, new Uri(nameof(ViewB), UriKind.Relative));
}
Don't register typeof(ViewB) with the region manager in the bootstrapper:
public void Initialize()
{
_regionManager.RegisterViewWithRegion(RegionNameConstants.MainRegion, typeof(ViewA));
}
And since you are navigating to the Uri of nameof(ViewB), you should also export the view with a contract name of nameof(ViewB):
[Export(nameof(ViewB))]
[PartCreationPolicy(CreationPolicy.NonShared)]
[RegionMemberLifetime(KeepAlive = false)]
internal partial class ViewB
{
[ImportingConstructor]
public ViewB(ViewBViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
}
Then you should get a new instance of ViewB each time you navigate to it using:
_regionManager.RequestNavigate(RegionNameConstants.MainRegion, new Uri(nameof(ViewB), UriKind.Relative));

MEF problem with import

models from shell-view-model with abstract factory pattern. I need inject in view-models classes from external assembly. If I use abstract factory pattern on creation view-models. Problem is imported classes in view-models are null.
Shell-view-models look like this:
public interface IViewModelFactory
{
ILogOnViewModel CreateLogOnViewModel(IShellViewModel shellViewModel);
IMessengerViewModel CreateMessengerViewModel(IShellViewModel shellViewModel);
}
[Export(typeof(IViewModelFactory))]
public class DefaulFactoryViewModel:IViewModelFactory
{
#region Implementation of IViewModelFactory
public ILogOnViewModel CreateLogOnViewModel(IShellViewModel shellViewModel)
{
return new LogOnViewModel(shellViewModel);
}
public IMessengerViewModel CreateMessengerViewModel(IShellViewModel shellViewModel)
{
return new MessengerViewModel(shellViewModel);
}
#endregion
}
public interface IShellViewModel
{
void ShowLogOnView();
void ShowMessengerView();
}
[Export(typeof(IShellViewModel))]
public class ShellViewModel : Conductor<IScreen>, IShellViewModel
{
private readonly IViewModelFactory _factory;
[ImportingConstructor]
public ShellViewModel(IViewModelFactory factory)
{
_factory = factory;
ShowLogOnView();
}
public void ShowLogOnView()
{
var model = _factory.CreateLogOnViewModel(this);
// var model = IoC.Get<LogOnViewModel>();
ActivateItem(model);
}
public void ShowMessengerView()
{
var model = _factory.CreateMessengerViewModel(this);
ActivateItem(model);
}
}
Some view-model.:
public class LogOnViewModel : Screen,ILogOnViewModel
{
[Import]//inject class from external assembly
private IPokecConnection _pokecConn;
private readonly IShellViewModel _shellViewModel=null;
private User _user=null;
public LogOnViewModel(IShellViewModel shellViewModel)
{
_shellViewModel = shellViewModel;
_user = new User();
}
}
variable _pokecConn are null becasuse I use abstract factory on creation new view-models.
if I use in shell-view model this:
var model = IoC.Get<LogOnViewModel>();
instead this:
var model = _factory.CreateLogOnViewModel(this);
and add Export attribute on view-models classes it works good, but I would like use abstract factory, and inject in view-model only classes from extrenal assembly.
It exist solution on this problem, or I must create view-models from IoC and export all class? Thanl for advance.
EDITED :
MEF BOOTSTRAPER CLASS:
public class MefBootStrapper : Bootstrapper<IShellViewModel>
{
#region Fields
private CompositionContainer _container;
#endregion
#region Overrides
protected override void Configure()
{ // configure container
#if SILVERLIGHT
_container = CompositionHost.Initialize(
new AggregateCatalog(AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>()));
#else
var catalog =
new AggregateCatalog(
AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>());
//add external DLL
catalog.Catalogs.Add(
new AssemblyCatalog(string.Format(
CultureInfo.InvariantCulture, "{0}{1}", System.IO.Directory.GetCurrentDirectory(), #"\Pokec_Toolkit.dll")));
_container = new CompositionContainer(catalog);
#endif
var batch = new CompositionBatch();
batch.AddExportedValue<IWindowManager>(new WindowManager());
batch.AddExportedValue<IEventAggregator>(new EventAggregator());
batch.AddExportedValue(_container);
_container.Compose(batch);
_container.SatisfyImportsOnce(this);
}
protected override object GetInstance(Type serviceType, string key)
{
string 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);
}
#endregion
}
Did you forget the attribute ImportingConstructor for the LogOnViewModel constructor?
EDIT: Import property always null (MEF import issue)

Resources