mvvmcross - multiple Init methods in viewmodel with different signature not working - mvvmcross

in a main viewmodel where i collect data from another viewmodels, I created in summary two or three public Init methods with different signatures. When i navigate back to the base viewmodel from the other viewmodels with ShowViewModel, I awaited that the right Init method will be executed, but this don't happen. Regarding the greet practical documentation here:
http://slodge.blogspot.ch/2013/03/v3-new-viewmodel-lifecycle.html
This should be work :-/.
I will explain this with some code.
My main view model is e.g.:
public class MainViewModel : MvxViewModel
{
MainViewModel() {}
public class ParameterFirst
{
public string Id { get; set; }
}
public class ParameterSecond
{
public string Id { get; set; }
}
public class ParameterSecond
{
public string Id { get; set; }
}
public class ParameterThird
{
public string Id { get; set; }
}
public void Init(ParameterFirst objFirst)
{
//do something
}
public void Init(ParameterSecond objSecond)
{
//do something
}
public void Init(ParameterThird objThird)
{
//do something
}
}
Then I will navigate from another viewmodel and await that the right Init method will be executed:
public class CollectData_ONE_ViewModel : MvxViewModel
{
CollectData_ONE_ViewModel() {}
public void DidWork()
{
//Hopefully the Init method with argument ParameterFirst should be called
base.ShowViewModel<MainViewModel>(new MainViewModel.ParameterFirst { Id = "11" });
}
}
next here the second viewmodel
public class CollectData_SECOND_ViewModel : MvxViewModel
{
CollectData_SECOND_ViewModel() {}
public void DidWork()
{
//Hopefully the Init method with argument ParameterFirst should be called
base.ShowViewModel<MainViewModel>(new MainViewModel.ParameterSecond { Id = "22" });
}
}
and the third viewmodel
public class CollectData_THIRD_ViewModel : MvxViewModel
{
CollectData_THIRD_ViewModel() {}
public void DidWork()
{
//Hopefully the Init method with argument ParameterFirst should be called
base.ShowViewModel<MainViewModel>(new MainViewModel.ParameterThird { Id = "33" });
}
}
In my code, each time the First Init method is called, I'm really at the end and don't have further ideas :) Did anyone here experienced the same issue? Or do anyone here have another Idea to collect data to the main viewmodel in an elegant way? Thanks a lot in advance for reading :)

The Init mechanism in MvvmCross is deliberately lightweight. If you declare multiple methods, all of them will be called - this is by design. Also if some of the Init parameter objects were to share properties then these would clash - see Custom types in Navigation parameters in v3
As it says in the blog post you reference "generally you will probably only want to use one within your application" - so I'd recommend refactoring to a single navigation parameter object and using your own ViewModel-based logic to decide how your ViewModel should initialise.
If you really do need three Init methods called in three different situations, then you can easily pack and unpack your own parameter objects using a custom method (possibly in a BaseViewModel class) like in https://stackoverflow.com/a/19059938/373321

Related

Trouble creating a base ViewModel for MvvmCross 5.1.0

I'm currently diving into the world of Xamarain with the MvvmCross framework. In my current project I want to make use of a MVVM base ViewModel to be able to reuse some of my code in other ViewModels.
When trying to implement this I've ran into a problem when using the MvxViewModel which supports passing parameters between navigation.
public abstract class BaseViewModel<TParameter> : MvxViewModel, IMvxViewModel<TParameter> where TParameter : class
{
protected readonly IMvxNavigationService _navigationService;
public BaseViewModel(IMvxNavigationService navigationService)
{
_navigationService = navigationService;
}
public new abstract Task Initialize(TParameter parameter);
}
This way I'm able to use the BaseViewModel as following.
public class ExampleViewModel : BaseViewModel<ExampleParameters>
{
private ExampleParameters _parameter;
public ExampleViewModel(IMvxNavigationService navigationService) : base(navigationService)
{
}
public override Task Initialize(ExampleParameters parameter)
{
return Task.Run(() => { _parameter = parameter; });
}
}
In this situation I think this is a pretty good solution. The ExampleViewModel even tells me I need to implement the Initialize Task when I've forgotten.
Still this solution is not great in every situation. When I have ViewModel that doesn't require the passing of parameters I still need to specify a parameters object and implement the Initialize method.
public class ParameterlessViewModel : BaseViewModel<object>
{
public ParameterlessViewModel(IMvxNavigationService navigationService) : base(navigationService)
{
}
public override Task Initialize(object parameter)
{
return Task.Run(() => { });
}
}
When removing the abstract method from the BaseViewModel I wont need to implement the Initialize method but then I won't be forced to implement it when I'm creating a ViewModel that requires the passing of parameters.
The above solution is workable but I'm curious if anyone ran into this same problem and maybe has a better solution? One which is good in both situations without having to setup two BaseViewModel classes.
Kind regards,
Jop Middelkamp
The documentation for this states: https://www.mvvmcross.com/documentation/fundamentals/navigation
If you have a BaseViewModel you might not be able to inherit MvxViewModel<TParameter> or MvxViewModel<TParameter, TResult> because you already have the BaseViewModel as base class. In this case you can implement the following interface:
IMvxViewModel<TParameter>, IMvxViewModelResult<TResult> or IMvxViewModel<TParameter, TResult>
In case you use TResult you can just copy the source code into your viewmodel:
public override TaskCompletionSource<object> CloseCompletionSource { get; set; }
public override void ViewDestroy()
{
if (CloseCompletionSource != null && !CloseCompletionSource.Task.IsCompleted && !CloseCompletionSource.Task.IsFaulted)
CloseCompletionSource?.TrySetCanceled();
base.ViewDestroy();
}
Do we do the add the Interface IMvxViewModel in the base class or the device class, can you give a simple example
In this case you can implement the following interface:
IMvxViewModel<TParameter>, IMvxViewModelResult<TResult> or IMvxViewModel<TParameter, TResult>

ninject factory constructor selection with runtime generics

How can I use a ninject factory, which creates an instance with constructor parameters, without relying on the argument names.
The problem is that the ToConstructor()-Method not works, because I bind it to a generic definition.
The following example works, if I use the factory method with the corresponding constructor argument name, but I don't like it rely on names.
Because the following solution is very fragil and breaks if someone chooses a wrong name or renames the ctor-argument in the derived class.
Any solution?
Here's the example code:
[TestFixture]
public class NinjectFactoryBindingsTest
{
[Test]
public void ConstructorSelectionWithArguments()
{
NinjectSettings ninjectSettings = new NinjectSettings();
ninjectSettings.LoadExtensions = false;
using (var kernel = new StandardKernel(ninjectSettings, new FuncModule()))
{
// IDependencyA will be passed to the factory, therefore it is not bounded
//kernel.Bind<IDependencyA>().To<DependencyA>();
kernel.Bind<IDependencyB>().To<DependencyB>();
kernel.Bind(typeof(IGenericBaseClass<>)).To(typeof(GenericDerivedClass<>));
kernel.Bind<IGenericClassFactory>().ToFactory();
IGenericClassFactory factory = kernel.Get<IGenericClassFactory>();
DependencyA dependencyA = new DependencyA();
IGenericBaseClass<GenericImpl> shouldWorkInstance = factory.Create<GenericImpl>(dependencyA);
Assert.NotNull(shouldWorkInstance);
}
}
}
public interface IGenericClassFactory
{
IGenericBaseClass<TGeneric> Create<TGeneric>(IDependencyA someName) where TGeneric : IGeneric;
// This works, but relies on ctor-param-names!!!
// IGenericBaseClass<TGeneric> Create<TGeneric>(IDependencyA otherNameThanInBaseClass) where TGeneric : IGeneric;
}
public class DependencyA : IDependencyA
{
}
public class DependencyB : IDependencyB
{
}
public class GenericDerivedClass<TGeneric> : GenericBaseClass<TGeneric> where TGeneric : IGeneric
{
public GenericDerivedClass(IDependencyA otherNameThanInBaseClass, IDependencyB dependencyB)
: base(otherNameThanInBaseClass, dependencyB)
{
}
}
public abstract class GenericBaseClass<TGeneric> : IGenericBaseClass<TGeneric> where TGeneric : IGeneric
{
protected GenericBaseClass(IDependencyA dependencyA, IDependencyB dependencyB)
{
}
}
public interface IGenericBaseClass<TGeneric> where TGeneric : IGeneric
{
}
public interface IDependencyB
{
}
public interface IDependencyA
{
}
public class GenericImpl : IGeneric
{
}
public interface IGeneric
{
}
The factory extension has the convention that arguments must have the same name as the constructor argument they will be passed to. There are no easy ways to do it differently. The only way I can think of is about the following:
Create a new IParameter implementation that can hold a reference to an IDependencyA.
Create either a hardcoded factory or a custom IInstanceProvider (see documentation) that creates an instance of your IParameter implementation so that it is passed to the Get<> request
Add a new binding for IDependencyA: Bind<IDependency>().ToMethod(ctx => extract and return your parameter from the context)

registering open generic decorators for typed implementations in castle windsor

While trying to coerce Windsor into wrapping an implementation with a random number of decorators, i've stumbled upon the following:
i have 3 decorators and an implementation all using the same interface.
if you run this code, windsor resolves icommandhandler<stringcommand> as implementation, which, as far as i can tell, is expected behaviour, because the typed implementation can not be registered with the open typed decorators.
However, if you uncomment the line container.Register(Component.For<ICommandHandler<stringCommand>>().ImplementedBy<Decorator1<stringCommand>>());, all three decorators will be used to resolve implementation, which is the desired result (sort of : ).
class Program
{
static void Main(string[] args)
{
var container = new WindsorContainer();
container.Register(Component.For(typeof(ICommandHandler<>)).ImplementedBy(typeof(Decorator1<>)));
container.Register(Component.For(typeof(ICommandHandler<>)).ImplementedBy(typeof(Decorator2<>)));
container.Register(Component.For(typeof(ICommandHandler<>)).ImplementedBy(typeof(Decorator3<>)));
//uncomment the line below and watch the magic happen
//container.Register(Component.For<ICommandHandler<stringCommand>>().ImplementedBy<Decorator1<stringCommand>>());
container.Register(Component.For<ICommandHandler<stringCommand>>().ImplementedBy<implementation>());
var stringCommandHandler = container.Resolve<ICommandHandler<stringCommand>>();
var command = new stringCommand();
stringCommandHandler.Handle(command);
Console.WriteLine(command.s);
Console.ReadKey();
}
}
public interface ICommandHandler<T>
{
void Handle(T t);
}
public class stringCommand
{
public string s { get; set; }
}
public abstract class Decorator<T> : ICommandHandler<T>
{
public abstract void Handle(T t);
};
public class Decorator1<T> : Decorator<T>
where T : stringCommand
{
private ICommandHandler<T> _handler;
public Decorator1(ICommandHandler<T> handler)
{
_handler = handler;
}
public override void Handle(T t)
{
t.s += "Decorator1;";
_handler.Handle(t);
}
}
public class Decorator2<T> : Decorator<T>
where T : stringCommand
{
private ICommandHandler<T> _handler;
public Decorator2(ICommandHandler<T> handler)
{
_handler = handler;
}
public override void Handle(T t)
{
t.s += "Decorator2;";
_handler.Handle(t);
}
}
public class Decorator3<T> : Decorator<T>
where T : stringCommand
{
private ICommandHandler<T> _handler;
public Decorator3(ICommandHandler<T> handler)
{
_handler = handler;
}
public override void Handle(T t)
{
t.s += "Decorator3;";
_handler.Handle(t);
}
}
public class implementation : ICommandHandler<stringCommand>
{
public void Handle(stringCommand t)
{
t.s += "implementation;";
}
}
Why exactly is this happening, is this a feature of windsor that i am not aware of? Is there perhaps a different way to achieve the same effect? (without resorting to reflection)
When windsor tries to resolve a component it will first try to resolve the more specific interface. So when you register Component.For it will prefer to resolve this over an open generic type.
If the same interface is registered multiple times, it will use the first one specified.
So if you don't uncommment the line your application will resolve implementation since this is the most specific component.
If you do uncomment the line decorator1 will be resolved and indeed the magic starts. The decorator will now start looking for the first registered component that satisfies it's constructor, in this case that would be decorator1 again (you did notice that your output show decorator1 2 times ?). Which will the resolve the next registered component and so on till it comes to the actual implementation.
So the only thing I can think about is not registering decorator1 as an open generic but as a specific type.
Kind regards,
Marwijn.

t4mvc : Cannot inherit a controller class which has no default constructor?

I am using T4MVC with MVC2.
I have the following building blocks:
A simple entity interface which defines that every POCO entity must have a long Id property:
public interface IEntity
{
public long Id;
}
A simple POCO class which implements the IEntity interface and has some string properties:
public class CD : IEntity
{
public long Id { get; set; }
public long Name { get; set; }
}
A base controller:
public abstract class EntityController<T> : Controller where T : class, global::IEntity
{
public EntityController(IEntityManager<T> manager);
}
I use this base controller in my CDController (where CDManager implements the IEntityManager interface, which is a UnitOfWork pattern to add CRUD functionality):
public partial class CDController : EntityController<CD>
{
public CDController() : base(new CDManager()) { }
}
When I run my t4 template, this code is generated:
namespace MyApp.Web.Controllers {
public partial class CDController {
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
protected CDController(Dummy d) { }
But this gives me an error during compilation:
MyApp.EntityController<CD> does not contain a constructor that takes 0 arguments
How can I solve this?
I wanted by controller base class to be abstract and it's constructor protected and parametrized. Got around this issue by adding a blank constructor to ControllerBase that throws a NotImplementedException.
Doesn't quite feel right but it gets the job done. Only issue is when combined with dependency injection the wrong constructor will be called - since it throws an exception the app will bum out.
Code:
public abstract class ControllerBase : Controller
{
protected object AlwaysSupply { get; private set; }
public ControllerBase()
{
throw new NotImplementedException();
}
public ControllerBase(object alwaysSupply)
{
AlwaysSupply = alwaysSupply;
}
}
This will cause T4MVC to generate compilable code. The fault seems to be it always tries to generate a blank (no parameters) constructor for controller classes.
Hope this helps someone.
I see the problem, and it comes down to T4MVC not quite doing the right thing when dealing with generic classes. Normally it would generate a default ctor for it in a partial class, but the fact that it's generic is throwing it off.
You should be able to work around simply by adding a default ctor yourself, e.g.
public abstract partial class EntityController<T> : Controller where T : class, IEntity {
public EntityController() { }
// etc...
}
I've noticed something very odd:
I've added the empty constructor to the base class, but without the throw new NotImplementedException(); and it works fine.
But here's the odd thing, when calling the controller if I have an url like
/{controller}?params (default action being set to Index in the RouteConfig) the parameterless private controller on the base class is called.
But when I have an url like /{controller}/{action}?params then the constructor with parameters is called.

TypedFactoryFacility: how do i initialize an object with an inline parameter?

How can i produce the same output as specified below using the TypedFactoryFacility?
public class Something
{
public void Initialize(Whatever instance) {}
}
public interface ISomethingFactory
{
Something Create(Whatever instance);
}
internal class SomethingFactory : ISomethingFactory
{
private readonly IWindsorContainer _container;
public SomethingFactory(IWindsorContainer container)
{
_container = container;
}
public Something Create(Whatever instance)
{
Something item = _container.Resolve<Something>();
item.Initialize(instance);
return item;
}
}
So I want to replace the manual factory with a proxy-generated ITypedFactoryFacility, but I cant find a way to invoke something on the resolved component after creation. I looked at commission-concerns, but you don't have a reference to the CreationContext from a custom commision concern so that won't work. I could of course move the dependency to the ctor and provide an ctor override, but I think properties are good when you want to convey non-optional dependencies.
You don't need to invoke stuff on the instance upon creation - Windsor will automagically inject stuff when the name of the parameter in the factory method signature matches something that can be injected - be it constructor paramaters or public properties... short example (using a public property):
interface ISomeFactory
{
Something CreateSomething(object dataSource);
}
class Something
{
public object DataSource { get; set; }
}
Given that these are registered like this:
container.Register(Component.For<ISomeFactory>().AsFactory(),
Component.For<Something>().Lifestyle.Transient)
you can resolve instances of Something like this:
var aintThatSomething = someFactory.CreateSomething(new [] {"ZOMG!", "w00t!"});
Remember that if something inside the burden associated with the instance of Something requires decommissioning, you need to provide an appropriate Release method on the factory as well.