MVVMCROSS - Pass a parameter to a ViewModel - c#

I am trying to pass a parameter to a child ViewModel constructor which throws "MvvmCross.Platform.Exceptions.MvxException: Failed to construct and initialize ViewModel ... MvxIoCResolveException: Failed to resolve parameter for parameter myParam of type MyType..."
MyChildViewModel.cs
public class MyChildViewModel : MvxViewModel
{
private MyType _myParam;
public MyType MyParam
{
get { return _myParam; }
set
{
if (SetProperty(ref _myParam, value))
{
RaisePropertyChanged(() => MyParam);
}
}
}
public MyChildViewModel(MyType myParam)
{
_myParam = myParam;
}
}
In my parent ViewModel I have:
public ICommand ShowDialogCommand { get; private set; }
ShowDialogCommand = new MvxCommand<MyType>(e => ShowViewModel<MyChildViewModel>(e));
Parent activity call:
ViewModel.ShowDialogCommand.Execute(VarOfMyType);
I am obviously doing something wrong. Is this even remotely acceptable approach to pass data to a child ViewModel? What's the best practice?
Thank you in advance for your valuable time.

If you read up on the documentation
it is easy to pass object with the MvxNavigationService: https://www.mvvmcross.com/documentation/fundamentals/navigation
Note that the documentation is for MvvmCross 5.2 which is currently in a nightly release, but almost the same works for 5.0 and onwards.
In your ViewModel this could look like:
public class MyViewModel : MvxViewModel
{
private readonly IMvxNavigationService _navigationService;
public MyViewModel(IMvxNavigationService navigationService)
{
_navigationService = navigationService;
}
public override void Prepare()
{
//Do anything before navigating to the view
}
public async Task SomeMethod()
{
await _navigationService.Navigate<NextViewModel, MyObject>(new MyObject());
}
}
public class NextViewModel : MvxViewModel<MyObject>
{
public override void Prepare(MyObject parameter)
{
//Do anything before navigating to the view
//Save the parameter to a property if you want to use it later
}
public override async Task Initialize()
{
//Do heavy work and data loading here
}
}

From this website the way they did it is (adapted and modified for your case):
public ICommand ShowDialogCommand { get; private set; }
ShowDialogCommand = new MvxCommand<MyType>(ShowMyVM);
private void ShowMyVM(MyType e)
{
if (e != null)
ShowViewModel<SingleClientViewModel>(e);
else
{
//handle case where your parameter is null
}
}

Related

Calling a Command from a Separate Class File

So I have been at it for days, and for the life of me cannot find any documentation that fits my situation exactly here.
I have essentially set up a custom navigation service and would like to call the command from my ViewModel Class directly from my User Control.
I think I'm on the edge of having it here, but my lack of experience with C# is shooting me in the foot.
Here is the section of code from my Login.xaml.cs in question:
private LoginViewModel _loginViewModel;
public Login(LoginViewModel loginViewModel)
{
_loginViewModel = loginViewModel;
}
private void GrantAccess()
{
int userAccess = Int16.Parse(User.Access);
if (userAccess == 1)
{
MessageBox.Show("The bottom man");
}
if (userAccess == 2)
{
MessageBox.Show("The little boss");
}
if (userAccess == 3)
{
MessageBox.Show("The little big boss");
}
if (userAccess == 4)
{
{
_loginViewModel.NavigateMM1Command.Execute(null);
}
}
}
and here is the command I'm trying to reference from the ViewModel:
public class LoginViewModel : BaseViewModel
{
public ICommand NavigateMM1Command { get; }
public LoginViewModel(NavigationStore navigationStore)
{
NavigateMM1Command = new NavigateCommand<MM1ViewModel>(new NavigationService<MM1ViewModel>(navigationStore, () => new MM1ViewModel(navigationStore)));
}
}
Basically I've been going through tutorial after tutorial trying to apply what they teach to what I need and its worked for the most part but now _loginViewModel is throwing a null reference exception and I'm not sure why.
I have tried:
LoginViewModel loginViewModel = new loginViewModel();
but its asking me to pass a navigationStore argument through it and that feels wrong.
Any help here will cure my temporary insanity XD
You're receiving a Null Object Reference because navigationStore is null when LoginViewModel is being constructed.
That is, you have not configured a means to instantiate the type navigationStore when constructing LoginViewModel.
Dependency Injection (DI), or Invocation of Control (IoC) is bit more comprehensive a subject to cover in this answer.
Having said that,
I'll provide code to review here. It represents a means to configure a service provider using a collection of type mappings.
In this complete, ConsoleApp example, we'll explicitly instantiate a ServiceCollection, add Service Types (specifying mapping where application), and Build the ServiceProvider; With that provider, we'll resolve and instantiate Login type using GetService -- instantiating all the types;
The Types are essentially mockups of the types you've specified, but I've modified some aspects (an made up notioned like what your Execute method and usage of NavigationStore was).
DemoNavTypes
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleDemo.NavLoginDemo
{
public interface ICommand
{
void Execute(string? userName);
}
public interface INavigationStore {
public bool this[string index] { get;set; }
}
public interface INavigationService {
void GrantAccessToUser(string userName);
}
public interface INavigationViewModel { }
internal class NavigationStore : INavigationStore
{
private Dictionary<string, bool> userAccessDict;
public NavigationStore() {
userAccessDict = new Dictionary<string, bool>();
}
public bool this[string index] {
get => userAccessDict.TryGetValue(index, out var val) && val;
set => userAccessDict[index] = value;
}
}
internal class NavigationService : INavigationService
{
private readonly INavigationStore _navigationStore;
public NavigationService(INavigationStore navigationStore)
{
_navigationStore = navigationStore;
}
public void GrantAccessToUser(string? userName)
{
if (string.IsNullOrWhiteSpace(userName))
throw new ArgumentException(nameof(userName));
_navigationStore[userName!] = true;
}
}
internal class NavigationCommand : ICommand
{
private readonly INavigationService _navigationService;
public NavigationCommand(INavigationService navigationService)
{
_navigationService = navigationService;
}
public void Execute(string? userName)
{
if (userName != null)
{
_navigationService.GrantAccessToUser(userName);
}
}
}
internal class User
{
public string? Name { get; set; }
public string Access { get; set; } = "1";
}
public abstract class BaseViewModel
{
internal User User { get; set; } = new User();
protected BaseViewModel() { }
}
internal class LoginViewModel : BaseViewModel, INavigationViewModel
{
private readonly ICommand _command;
public LoginViewModel(ICommand command) : base()
{
_command = command;
}
internal ICommand NavigateMM1Command => _command;
}
internal class Login
{
private User User => _loginViewModel.User;
private readonly LoginViewModel _loginViewModel;
public Login(LoginViewModel loginViewModel)
{
_loginViewModel = loginViewModel;
}
internal void SetAccess(int access)
{
SetAccess($"{access}");
}
internal void SetAccess(string access)
{
User.Access = access;
}
internal void SetUserName(string userName) { User.Name = userName; }
internal async Task GrantAccessAsync()
{
await Task.Yield();
int userAccess = Int16.Parse(User.Access);
switch (userAccess)
{
case 1:
Console.WriteLine("The bottom man");
break;
case 2:
Console.WriteLine("The little boss");
break;
case 3:
Console.WriteLine("The little big boss");
break;
case 4:
_loginViewModel.NavigateMM1Command.Execute(User.Name);
break;
default:
throw new NotImplementedException();
}
}
}
}
Program.cs (using Microsoft.Extensions.DependencyInjection)
using Microsoft.Extensions.DependencyInjection;
using System.Collections.Immutable;
using System.ComponentModel.Design;
using System.Linq;
using ConsoleDemo.NavLoginDemo;
internal class Program
{
private static async Task Main(string[] args)
{
var services = new ServiceCollection();
var provder = ConfigureServices(services);
var login = provder.GetService<Login>();
if (login != null)
{
await login.GrantAccessAsync();
login.SetAccess(2);
await login.GrantAccessAsync();
login.SetAccess(3);
await login.GrantAccessAsync();
login.SetUserName("James Bond");
login.SetAccess(4);
await login.GrantAccessAsync();
}
}
private static IServiceProvider ConfigureServices(IServiceCollection services)
{
return services
.AddScoped<INavigationStore, NavigationStore>()
.AddScoped<INavigationService, NavigationService>()
.AddScoped<ICommand, NavigationCommand>()
.AddScoped<LoginViewModel>()
.AddScoped<Login>()
.BuildServiceProvider();
}
}
Note
-- However,
In your application, you'll probably already have a ServiceCollection instance in your Program.cs or Startup.cs file. And the ServiceProvider (or HostProvider) will be managed over by the application; So, you probably won't need to explicitly resolve (or GetService<T>) -- just add the Service Type (mappings) in ServiceCollection. Those parameter types will be instantiated and 'injected' into the constructor of Type that is itself being instantiated.

In MVVMCross, is it possible to close a viewmodel and pass values back to the previous viewmodel in the navigation stack?

Consider the following example. I have three view models, ViewModel_A, ViewModel_B, and ViewModel_Values.
I want to be able to navigate to ViewModel_Values from either ViewModel_A or ViewModel_B, select a value from ViewModel_Values, then return that value to the calling view model.
Is there a way of passing arguments to previous view models in the navigation stack so that I can simply call ViewModel_Values.Close(this), thereby ensuring that the ViewModels_Values is decoupled from any other view models and can be used with arbitrary "parent" view models?
MvvmCross 5 onwards
From MvvmCross 5 you can use the new IMvxNavigationService that allows you to have a much richer navigation. One of the new features is the possibility to await a value from another ViewModel after navigating to it and should be the approach to take after MvvmCross 5 instead of Messenger, e.g.:
public class ViewModel_A : MvxViewModel
{
private readonly IMvxNavigationService _navigationService;
public ViewModel_A(IMvxNavigationService navigation)
{
_navigationService = navigationService;
}
public override async Task Initialize()
{
//Do heavy work and data loading here
}
public async Task SomeMethod()
{
var result = await _navigationService.Navigate<ViewModel_Values, MyObject, MyReturnObject>(new MyObject());
//Do something with the result MyReturnObject that you get back
}
}
public class ViewModel_Values : MvxViewModel<MyObject, MyReturnObject>
{
private readonly IMvxNavigationService _navigationService;
public ViewModel_Values(IMvxNavigationService navigation)
{
_navigationService = navigationService;
}
public override void Prepare(MyObject parameter)
{
//Do anything before navigating to the view
//Save the parameter to a property if you want to use it later
}
public override async Task Initialize()
{
//Do heavy work and data loading here
}
public async Task SomeMethodToClose()
{
// here you returned the value
await _navigationService.Close(this, new MyReturnObject());
}
}
More info here
HIH
Use messaging center. Here is the sample code.
//for trigger
MessagingCenter.Send<object> (this, "Hi");
//put this where you want to receive your data
MessagingCenter.Subscribe<object> (this, "Hi", (sender) => {
// do something whenever the "Hi" message is sent
});
Installing & using the MvxMessenger plugin is a great way to decouple view model communication in MvvmCross -
In your case, you could set up a new message -
public class ValuesChangedMessage : MvxMessage
{
public ValuesChangedMessage(object sender, int valuea, string valueb)
: base(sender)
{
Valuea = valuea;
Valueb = valueb;
}
public int Valuea { get; private set; }
public string Valueb { get; private set; }
}
In ViewModel_Values, you would act on / publish your UX changes with -
_mvxMessenger.Publish<ValuesChangedMessage>(new ValuesChangedMessage(this, 1, "boo!"));
And in ViewModel_A, ViewModel_B you would subscribe and act on them (as your ViewModel A / B would be still in the navigation stack when you pushed ViewModel_Values from them, so they could receive the message) -
private MvxSubscriptionToken _messageToken;
_messageToken = _mvxMessenger.Subscribe<ValuesChangedMessage>(async message =>
{
// use message.Valuea etc ..
});
More infos here -
https://www.mvvmcross.com/documentation/plugins/messenger?scroll=644
https://www.youtube.com/watch?feature=player_embedded&v=HQdvrWWzkIk
In my case of trying to navigate in this pattern:
//pseudo code
"ModelA" => "ModelB<List<MyObject>>" => "ModelC<MyObject>"
OR
//pseudo code
"ModelA" => "ModelC<MyObject>"
I used the following work around in my ViewDestroy() override of ModelB<List>:
private bool destroyView = true;
public bool DestroyView
{
get => destroyView;
set
{
destroyView = value;
RaisePropertyChanged(() => DestroyView);
}
}
public override void ViewDestroy(bool viewFinishing)
{
viewFinishing = DestroyView;
base.ViewDestroy(viewFinishing);
}
private async Task ModifySelectedObject()
{
DestroyView = false;
MyObject obj = SelectedObject;
MyObject modifiedObj = await _navigationService.Navigate<ModifySingleViewModel, MyObject, MyObject>(new MyObject());
if (modifiedObj != null)
{
obj = modifiedObj;
}
else
{
await Application.Current.MainPage.DisplayAlert("", "No changes made.", "OK");
}
DestroyView = true;
}
This keeps the original
"await _navigationService.Navigate<ModifyMultipleViewModel,
List, List>(new MyObject);"
from ModelA open when navigating to ModelC from ModelB, but still allows the ViewDestroy Method to close otherwise.

Updating a property on one class from other classes while multithreading

I am using WPF and MVVM pattern, but that's not really relevant in this case. In my ViewModel MainVM, I have a property like this:
public int Posts {
get { return this.posts; }
set {
this.posts = value;
this.OnPropertyChanged(nameof(this.Posts));
}
}
Through properties, I supply MainVM a list of Foo. (Actually, I want to supply it using the constructor, but I can't. Read below) And there is a start method on the MainVM which does as follows:
foreach(Foo foo in this.Foos){ // this.Foos is just a List<Foo>
Task.Run(() => foo.Bar());
}
In the execution of Foo.Bar (There are lots of methods being called in Bar, so passing an Action to bar itself is not really feasible), it is supposed to update a value, and that value is MainVM.Posts. Passing MainVM to Foo is not an option. So I thought of passing an Action instead. So I wrote this Method in MainVM.
public void IncrementPosts()
{
lock(this.whatever)
{
this.Posts++;
}
}
Then I made Foo take an Action in the constructor. So here's how the Foos are created and passed to MainVM.
var vm = new MainVM();
var foo1 = new Foo(vm.IncrementPosts);
var foo2 = new Foo(vm.IncrementPosts);
vm.Foos = new List<Foo>() {foo1, foo2};
Whilst this works, I feel that it looks ugly. First of all, MainVM needs a list of Foo to work properly. So it's supposed to be supplied to the constructor, right? But as a Foo needs an action which points to a method in MainVM, I can't do that. And all of this looks like a hack.
Is there a better way of doing this?
If I understand, what are you want to do..
void Main()
{
var vm = new MainVM(new FooFactory());
Console.WriteLine(vm.Posts);
vm.ExecuteFoo();
Console.WriteLine(vm.Posts);
}
public class Foo
{
private Action _action;
public Foo(Action action)
{
_action = action;
}
public void RunAction()
{
_action();
}
}
public class FooFactory
{
public Foo[] CreateFoo(MainVM vm)
{
return new[] { new Foo(()=>vm.Posts++) };
}
}
public class MainVM : INotifyPropertyChanged
{
public MainVM(FooFactory factory)
{
_foos = factory.CreateFoo(this);
}
public void ExecuteFoo()
{
foreach(var foo in _foos)
foo.RunAction();
}
private Foo[] _foos;
private int posts;
public int Posts
{
get { return this.posts; }
set
{
this.posts = value;
this.OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName]string propertyName = null)
{
var local = PropertyChanged;
if (local != null)
{
local(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Ninject Factory Pattern and Bindings

I am trying to implement the Ninject.Extensions.Factory pattern and my program is telling me my bindings aren't right, but I can't figure out why. I keep getting an "Error activating IHashable. No matching bindings are available, and the type is not self-bindable" exception thrown. The relevant areas of my code are below:
public interface IHashable
{
FileInfo File { get; }
string ComputeHash();
}
public interface IHashableFactory
{
IHashable GetNew(FileInfo file);
}
public class MD5ChecksumProvider : IHashable
{
private FileInfo _file;
public FileInfo File
{
get { return _file; }
}
public MD5ChecksumProvider(FileInfo file)
{
if (file == null)
throw new ArgumentNullException("file");
_file = file;
}
public string ComputeHash()
{
// implementation
}
}
public class AppFileProvider : IAppFileProvider
{
private IHashableFactory _hashFactory;
public IHashableFactory HashProvider
{
get { return _hashFactory; }
}
public AppFileProvider(IHashableFactory hashProviderFactory)
{
_hashFactory = hashProviderFactory;
}
public string GetChecksum(FileInfo fileInfo)
{
var hasher = _hashFactory.GetNew(fileInfo);
return hasher.ComputeHash();
}
}
public class BindingProviders : NinjectModule
{
public override void Load()
{
Bind<IHashable>()
.To<MD5ChecksumProvider>();
}
}
public class BindingFactories : NinjectModule
{
public override void Load()
{
Bind<IHashableFactory>()
.ToFactory();
}
}
// my DI container
public sealed class Container : IDisposable
{
private bool _isDisposed;
private IKernel _kernel;
private BindingFactories _bindingFactories;
private BindingObjects _bindingObjects;
private BindingProviders _bindingProviders;
public Container()
{
_isDisposed = false;
_bindingFactories = new BindingFactories();
_bindingObjects = new BindingObjects();
_bindingProviders = new BindingProviders();
_kernel = new StandardKernel(_bindingObjects, _bindingProviders, _bindingFactories);
}
public T Get<T>()
{
return _kernel.Get<T>();
}
public void Dispose()
{
// nothing worth seeing
}
private void Dispose(bool disposing)
{
// nothing worth seeing
}
}
// the program (composition root)
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
using (var container = new Container())
{
var fileProvider = container.Get<IAppFileProvider>();
foreach (var file in files)
{
string hash = fileProvider.GetChecksum(storePath, file); // this line throws "Error activating IHashable. No matching bindings are available, and the type is not self-bindable.""
}
}
}
}
I feel like my bindings are setup correctly but I must be missing something obvious. Any ideas why I'm getting the exception from the above code?
This is caused by a feature of Ninject.Extensions.Factory.
It treats methods which start with Get differently from those which don't.
If you rename IHashableFactory.GetNew to Create or Make everything works fine.
The "Get" feature is described here:
The default instace provider of the extension has the convention that it tries to return an instance using a named binding whenever a method starts with “Get”. E.g. IFoo GetMySpecialFoo() is equal to
resolutionRoot.Get<IFoo>("MySpecialFoo");
Since i think this is not obvious to the user and the exception isn't helpful at all in this regard, i have filed an issue report here

Inject (Initialize)parameter to constructor - Autofac

Is there any way to inject a parameter to constructor, which that parameter is currently created in Initialize method on basecontroller?
sample code:
public class SSBaseController : Controller
{
protected UserProfileClass UserProfile;
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
base.Initialize(requestContext);
UserProfile = UserProfileGetter.getProfileFromDB();
}
}
public class SSController:SSBaseController
{
private IProcessService processService;
public SSController(IProcessService _processService)
{
processService = _processService;
}
}
public interface IProcessService
{
void doStuff();
}
public class ProcessService1 : IProcessService
{
private UserProfileClass upc;
private string _name;
public ProcessService1(UserProfileClass _profileClass)
{
upc = _profileClass;
}
public void doStuff()
{
_name = upc.name;
upc.name = "xxx";
}
}
public class UserProfileGetter
{
public static UserProfileClass getProfileFromDB()
{
return null;
}
}
public class UserProfileClass
{
public string name;
}
Also i want to get rid of initialize, is this possible?
how to register ProcessService with that parameter without being null in SSController?
It is possible to integrate ASP.NET MVC and Autofaq. The only two things you have to do then is to register interface implementation and specify the Controller's constructor params. Framework will do all the rest.
See https://code.google.com/p/autofac/wiki/MvcIntegration
Here is one more article: http://blogs.msdn.com/b/roncain/archive/2012/07/16/dependency-injection-with-asp-net-web-api-and-autofac.aspx

Categories