1

my problem is this:

I have a base class

public abstract class ViewModelBase 

which contains an abstract method "RegisterCommands"

protected abstract void RegisterCommands();

All my Derived classes must, obviously, implements this method, for example my LoginViewModel has

protected override void RegisterCommands()
{
    LoginCommand?
        .Configure(
            execute: (msg) => { Login(User); },
            canExecute: (x) => { return CanLogin(); }
            );
}

and call it when class is instantiated, but i don't want call this method in every derived class constructor (if I have 100 derived classes I must call RegisterCommand 100 times).

Normal solution is call RegisterCommand in the base class constructor

protected abstract void RegisterCommands();

public ViewModelBase()
{
    RegisterCommands();
}

and this usually works (even if I do not know if it's a good practice) but...but...

in my scenario, in all the RegisterCommands methods I use my ICustomCommand objects, which are initialized in the derived class constructor with dependency injection

public class LoginViewModel : ViewModelBase
{

    private ICustomCommand _loginCommand;

    public ICustomCommand LoginCommand
    {
        get
        {
            return _loginCommand;
        }
        set
        {
            _loginCommand = value;
        }
    }

    public LoginViewModel(ICustomCommand loginCommand)
    {
        _loginCommand = loginCommand;
    }

    protected override void RegisterCommands()
    {
        LoginCommand?
            .Configure(
                execute: (msg) => { Login(User); },
                canExecute: (x) => { return CanLogin(); }
                );
    }

So, because base class constructor is called before derived class constructor, I can't call RegisterCommands() in base class constructor (because my ICustomCommands are not initialized yet in derived class so RegisterCommands() try to use my ICustomCommand which are still null).

I know that is not possible call derived class constructor before base class constructor, so what could be a valid, simple and clean solution to call RegisterCommands in all derived class calling this command in only one point?

thanks for answer

UPDATE:

As I said, RegisterCommands() is plural because every derived class could have N ICustomCommand objects

So I can have

LoginCommand for my LoginViewModel

SaveCommand, DeleteCommand for another ViewModel

etc.

One solution I think now is to remove ICustomCommand initialization from constructor and resolve it "on the fly" in getter property trough a static Resolver class, something like this

public ICustomCommand LoginCommand
{
    get
    {
        if(_loginCommand == null)
            MyStaticResolver.Resolve<ICustomCommand>();
        return _loginCommand;

But I'm still not convinced

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
Proxymus89
  • 15
  • 3
  • Welcome to Stack Overflow! This question may lead to a [xy problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) – Mat Nov 16 '18 at 14:22
  • If you use the RelayCommand "pattern" you have no need to call a `RegisterCommand`. https://stackoverflow.com/questions/22285866/why-relaycommand – Mat Nov 16 '18 at 14:23
  • Do you really need LoginCommand in every ViewModel? I suppose it would be more simple to have one page with login capability that would show or not detailed views. – Grzesiek Danowski Nov 16 '18 at 14:27
  • actually RelayCommand is my ICustomCommand implementation @Mat – Proxymus89 Nov 16 '18 at 15:53
  • @GrzesiekDanowski In every ViewModel I have N ICustomCommand, LoginCommand is only the name of the property in that specific ViewModel class – Proxymus89 Nov 16 '18 at 16:35
  • @Proxymus89 then you have different registrations in every viewModel. – Grzesiek Danowski Nov 16 '18 at 16:38
  • @Proxymus89: I updated my answer again. It should work for you. – JuanR Nov 16 '18 at 16:59

1 Answers1

0

If you use interfaces to represent your commands, you could pass them to the base class and expose a method to retrieve the commands. Your Registration method can then use them as needed:

public abstract class ViewModelBase
{
    public ViewModelBase(params ICustomCommand[] commands)
    {
        _commands = commands;            
        RegisterCommands();
    }

    private IEnumerable<ICustomCommand> _commands;

    protected abstract void RegisterCommands();

    //This method gets you the commands
    protected T GetCommand<T>() where T : ICustomCommand
    {
        var command = _commands.FirstOrDefault(c => typeof(T).IsAssignableFrom(c.GetType()));
        return (T)command ;
    }
}

public class LoginViewModel : ViewModelBase
{
    public LoginViewModel(ILoginCommand command):base(command)
    {

    }

    protected override void RegisterCommands()
    {
        //Get the command from the base class
        var command = GetCommand<ILoginCommand>();
        command?
        .Configure(
            execute: (msg) => { Login(User); },
            canExecute: (x) => { return CanLogin(); }
            );
    }
}

public class LoginCommand : ILoginCommand
{
}

public interface ILoginCommand : ICustomCommand
{
}
JuanR
  • 7,405
  • 1
  • 19
  • 30
  • The plural in `RegisterCommands` suggests that there may be more than one (but there's only one here). How do you write the base class to sensibly deal with multiple instances, in such a way that those instances are stored in the base class in a sane way to allow them to also be used/registered in the derived class? – Damien_The_Unbeliever Nov 16 '18 at 14:38
  • the problem is that every derived class could have N ICustomCommand public LoginViewModel(ICustomCommand loginCommand) public AccountViewModel(ICustomCommand loginCommand, ICustomCommand saveCommand) etc. – Proxymus89 Nov 16 '18 at 14:39
  • @Proxymus89: I see. I think I misunderstood your question. I'll rework my answer. – JuanR Nov 16 '18 at 14:43
  • @Damien_The_Unbeliever: Thank you Damien. I reworked my answer based on your and the OP's feedback. – JuanR Nov 16 '18 at 14:52
  • @Proxymus89: I updated my answer based on additional details. Please take a look. – JuanR Nov 16 '18 at 16:36