1

I've finally given in and started learning and applying MVVM architecture on my EFCore project. However, I haven't understood how scoped services work. On my previous question I was told I needed to use one scope per viewmodel otherwise, since I'm working with async methods, I would be getting concurrent transactions on the same DbContext (which is not allowed).

I've also read here that one way of using dialog windows while still following MVVM would be to have a service that would create a dialog window container with a dynamically filled viewmodel.

So far, so good. However I've hit a snag: I'm on my MainView, and I run a command that opens a new dialog window (with a new scope, I think). And on that dialog, I run a command that should open another dialog window. However, when trying to close, somehow I manage to close the parent dialog rather than the focused window. My relevant code is as follows:

What am I doing wrong here?

PS.: I translated the names to english for better readability, but I might've let one or another slip by - let me know if I can fix anything.

public class PurchasesViewModel : ViewModelBase
{
    private readonly IDialogGenerator _purchaseDialogGenerator;
    private readonly IDialogViewModelFactory _purchaseRecordVMFactory;

    public ICommand CreateNewPurchase { get; set; }

    public PurchasesViewModel(IServiceProvider services, IDialogGenerator purchaseVMDialogGenerator)
    {
        _purchaseDialogGenerator = purchaseVMDialogGenerator;
        IServiceScope purchaseVMScope = services.CreateScope();
        IServiceProvider provider = purchaseVMScope.ServiceProvider;
        _purchaseRecordVMFactory = provider.GetRequiredService<IDialogViewModelFactory>();
        CreateNewPurchase = new CreatePurchaseCommand(_purchaseDialogGenerator, _purchaseRecordVMFactory);
    }
}

internal class CreatePurchaseCommand : ICommand
{
    private IDialogGenerator _purchaseDialogGenerator;
    private IDialogViewModelFactory _purchaseVMFactory;

    public CreatePurchaseCommand(IDialogGenerator PurchaseRecordDialogGenerator, IDialogViewModelFactory PurchaseRecordVMFactory)
    {
        _purchaseDialogGenerator = PurchaseRecordDialogGenerator;
        _purchaseVMFactory = PurchaseRecordVMFactory;
    }

    public event EventHandler CanExecuteChanged;

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public void Execute(object parameter)
    {
        _purchaseDialogGenerator.ShownViewModel = _purchaseVMFactory.CreateDialogContentViewModel(DialogType.RecordPurchases);
        _purchaseDialogGenerator.ShowDialog();
    }
}

public class PurchaseRecordViewModel : DialogContentViewModelBase
{
    private readonly IMessaging<PURCHASE> _purchaseMessaging;
    private readonly IDialogGenerator _purchaseDialogGenerator;
    private readonly IDialogGenerator _importViaXMLDialogGenerator;
    private readonly IDialogViewModelFactory _importViaXMLVMFactory;
    
    public ICommand SavePurchase { get; set; }
    public ICommand ImportXML { get; set; }

    public PurchaseRecordViewModel(IServiceProvider servicos, IDialogGenerator PurchaseRecordDialogGenerator)
    {
        _purchaseDialogGenerator = PurchaseRecordDialogGenerator;
        IServiceScope scope = servicos.CreateScope();
        IServiceProvider provider = scope.ServiceProvider;

        _importViaXMLDialogGenerator = provider.GetRequiredService<IDialogGenerator>();
        _importViaXMLVMFactory = provider.GetRequiredService<IDialogViewModelFactory>();
        _purchaseMessaging = provider.GetRequiredService<IMessaging<PURCHASE>>();

        SavePurchase = new SalvaPurchaseCommandAsync(PurchaseRecordDialogGenerator);
        ImportXML = new ImportXMLDePurchaseCommand(_importViaXMLDialogGenerator, _importViaXMLVMFactory);
    }
}

internal class ImportXMLFromPurchaseCommand : ICommand
{
    private readonly IDialogGenerator _importviaXMLDialogGenerator;
    private readonly IDialogViewModelFactory _importXMLVMFactory;

    public ImportXMLFromPurchaseCommand(
        IDialogGenerator importviaXMLDialogGenerator,
        IDialogViewModelFactory importXMLVMFactory)
    {
        _importviaXMLDialogGenerator = importviaXMLDialogGenerator;
        _importXMLVMFactory = importXMLVMFactory;
    }

    public event EventHandler CanExecuteChanged;

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public void Execute(object parameter)
    {
        _importviaXMLDialogGenerator.ShownViewModel = _importXMLVMFactory.CreateDialogContentViewModel(DialogType.ImportXML);
        _importviaXMLDialogGenerator.ShowDialog();
    }
}

internal class SalvaPurchaseCommandAsync : ICommand
{
    private readonly IDialogGenerator _dialogGenerator;

    public SalvaPurchaseCommandAsync(IDialogGenerator dialogGenerator)
    {
        _dialogGenerator = dialogGenerator;
    }

    public event EventHandler CanExecuteChanged;

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public void Execute(object parameter)
    {
        _dialogGenerator.Result = DialogResult.OK;
        _dialogGenerator.Close();
    }
}

public class ImportViaXMLViewModel : DialogContentViewModelBase
{
    public ICommand ImportArquivoXML { get; set; }
    public ICommand Import { get; set; }


    private readonly IDialogGenerator _importViaXMLDialogGenerator;
    
    public ImportViaXMLViewModel(IMessaging<PURCHASE> messaging, 
        IDialogGenerator importViaXMLDialogGenerator)
    {
        _importViaXMLDialogGenerator = importViaXMLDialogGenerator;
        Import = new ImportCommand(this, messaging, _importViaXMLDialogGenerator);
    }
}

internal class ImportCommand : ICommand
{
    private readonly IMessaging<PURCHASE> _messaging;
    private readonly IDialogGenerator _dialogGenerator;

    public ImportCommand(IMessaging<PURCHASE> messaging, IDialogGenerator dialogGenerator)
    {
        _messaging = messaging;
        _dialogGenerator = dialogGenerator;
    }

    public event EventHandler CanExecuteChanged;

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public void Execute(object parameter)
    {
        _messaging.Message = new COMPRA();
        _dialogGenerator.Result = DialogResult.OK;
        _dialogGenerator.Close();
    }
}
public abstract class AsyncCommandBase : ICommand
    {
        private bool _isExecuting;
        private readonly Action<Exception> _onException;

        public bool IsExecuting
        {
            get { return _isExecuting; }
            set {
                _isExecuting = value;
                CanExecuteChanged?.Invoke(this, new EventArgs());
            }
        }

        public AsyncCommandBase(Action<Exception> onException)
        {
            this._onException = onException;
        }
        public virtual event EventHandler CanExecuteChanged;

        public virtual bool CanExecute(object parameter)
        {
            return !IsExecuting;
        }

        public async void Execute(object parameter)
        {
            IsExecuting = true;
            try
            {
                await ExecuteAsync(parameter);
            }
            catch (Exception ex)
            {
                _onException(ex);
            }
            IsExecuting = false;
        }

        protected abstract Task ExecuteAsync(object parameter);
    }
Artur S.
  • 185
  • 3
  • 15

1 Answers1

0

I found out a way to work it out. I found an answer after reading Good or bad practice for Dialogs in wpf with MVVM?.

I have created a singleton dialogStore in which I keep all the currently opened dialogs. Whenever I need a new dialog window, I "register" a new dialogGenerator with the dialogStore, and it'll open it for me. When I need to close it, the store is also responsible for that. Furthermore, so far, I've been using only one modal dialog active at a time, so whenever I close a dialog, I'll be sure it's the latest added one. But in the future, each dialogGenerator will know its set dialogStore index, and will be able to close themselves (and other dialogs) in any order needed.

My dialogStore is as follows:

    public class DialogsStore : IDialogsStore
{
    public List<IDialogGenerator> OpenDialogs { get; set; } = new List<IDialogGenerator>();

    public void CloseDialog(DialogResult dialogResult)
    {
        IDialogGenerator lastDialogGenerator = OpenDialogs[OpenDialogs.Count - 1];
        lastDialogGenerator.Resultado = dialogResult;
        //lastDialogGenerator.DialogClosed = null;
        lastDialogGenerator.Close();
    }

    public int RegisterDialog(IDialogGenerator dialog)
    {
        OpenDialogs.Add(dialog);
        dialog.DialogClosed += Dialog_DialogClosed;
        dialog.ShowDialog();
        return OpenDialogs.Count - 1;
    }

    private void Dialog_DialogClosed()
    {
        OpenDialogs.RemoveAt(OpenDialogs.Count - 1);
    }
}

One important point I have to deal with is that closing the dialog and removing it from the store didn't dispose of the scoped dialogGenerator, so I will still need to dispose from anything inside my dialogGenerator before removing it from my dialogStore.

If anyone has a better way to deal with dialog windows in MVVM with Dependency Injection, I'll be glad to hear it.

Artur S.
  • 185
  • 3
  • 15