I have a class UserManagement and always want to get the same instance (like singleton pattern). So my problem is now, that I always get a new instance insted of the named "Singleton". I'm new to StructureMap. I have tried both version (http://structuremap.github.io/object-lifecycle/) (http://structuremap.github.io/glossary/) described on the website.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Container container = new Container(new UserManagement());
//UserManagement instance = container.GetInstance<UserManagement>("Singleton");
UserManagement instance = container.GetInstance<UserManagement>();
MessageBox.Show(instance.Test);
}
}
public interface IUser
{
void DoSomethingFancy();
}
public class User : IUser
{
public void DoSomethingFancy()
{
}
}
public interface IDatabase
{
void DoSomething();
}
public class Database : IDatabase
{
public void DoSomething()
{
}
}
public class UserManagement : Registry
{
public string Test;
private Container _Container;
private IUser _User;
private IDatabase _Database;
public UserManagement()
{
_Container = new Container(_ =>
{
_.For<IUser>().Use<User>();
_.For<IDatabase>().Use<Database>();
});
_User = _Container.GetInstance<IUser>();
_Database = _Container.GetInstance<IDatabase>();
Test = DateTime.Now.ToString();
//For<UserManagement>().AddInstances(x =>
//{
// x.Type<UserManagement>().Named("Singleton");
//});
For<UserManagement>().Singleton();
}
}
Update
My solution has 3 projects
BootstrapperLibrary (class library - Bootstrapper)
Gui (WpfApp - MainWindow.xaml)
Framework (class library - LoginAction - UserManagement)
BootstrapperLibrary
Bootstrapper.cs
public static class Bootstrapper
{
public static Container Container => _Container ?? (_Container = InitializeContainer());
private static Container _Container;
public static Func<Container> InitializeContainer;
}
Gui
App.xaml.cs
public partial class App : Application
{
public App()
{
Bootstrapper.InitializeContainer += InitializeContainer;
}
private Container InitializeContainer()
{
Container container = new Container(c => c.Scan(scanner =>
{
scanner.TheCallingAssembly();
scanner.WithDefaultConventions();
scanner.AssembliesFromApplicationBaseDirectory();
}));
container.Configure(c =>
{
c.ForSingletonOf<UserManagement>();
c.For<IUser>().Use<UserAdv>();
}
);
return container;
}
}
MainWindow.xaml
<Window x:Class="Gui.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:myFramework="clr-namespace:MyFramework;assembly=MyFramework"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Button Content="Button" HorizontalAlignment="Left" Margin="383,228,0,0" VerticalAlignment="Top" Width="75">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<myFramework:LoginAction/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</Grid>
</Window>
Framework
LoginAction.cs
public class LoginAction : TriggerAction<DependencyObject>
{
protected override void Invoke(object parameter)
{
UserManagement userManagement = Bootstrapper.Container.GetInstance<UserManagement>();
Console.WriteLine(userManagement.Test); //Just to see if it's still the same instance who holds for example the current user.
}
}
UserManagement.cs
public interface IUser
{
string Name { get; }
void DoSomethingFancy();
}
public class User : IUser
{
public string Name { get; } = "default";
public void DoSomethingFancy()
{
}
}
public class UserAdv : IUser
{
public string Name { get; } = "Advanced";
public void DoSomethingFancy()
{
}
}
public interface IDatabase
{
void DoSomething();
}
public class Database : IDatabase
{
public void DoSomething()
{
}
}
public class UserManagement
{
public string Test;
public IUser User;
private IDatabase _Database;
public UserManagement(IUser user, IDatabase database)
{
User = user;
_Database = database;
Test = DateTime.Now.ToString();
}
}
If I understood correctly, my main application now initializes and configures the Container within the application startup. The Bootstrapper class is holding a static instance there. MyFramework fetches an instance of UserManangement from the static Container instance.
Is this the correct way how to use StructureMap? Thank you for your patience!
You should declare:
ForConcreteType<UserManagement>().Singleton();
Edit:
You may implement a bootstrapper class and call the Initialize() method during program startup:
public sealed class Bootstrapper
{
private static StructureMap.Container _container;
public StructureMap.Container MyContainer
{
get { return _container; }
}
static Bootstrapper()
{
}
public static Initialize()
{
StructureMap.Configuration.DSL.Registry registry = new StructureMap.Configuration.DSL.Registry();
registry.For<IUser>().Use<User>();
registry.For<IDatabase>().Use<Database>();
registry.ForConcreteType<UserManagement>().Singleton();
_container = new Container(registry);
}
}
Remove the container initialization from the UserManagement class and let the DI framework inject the concrete types/objects.
public class UserManagement
{
public string Test;
private IUser _user;
private IDatabase _database;
public UserManagement(IUser user, IDatabase database)
{
_user = user;
_database = database;
Test = DateTime.Now.ToString();
}
}
Modify your WPF test window:
public partial class MainWindow : Window
{
static MainWindow()
{
// only for demonstration!!!
Bootstrapper.Initialize();
}
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Container container = Bootstrapper.MyContainer;
UserManagement instance = container.GetInstance<UserManagement>();
MessageBox.Show(instance.Test);
}
}
Related
I building WPF application in MVVM architecture. Pressing button should give me data from database on DataGrid. App correctly build and I can start it but when I press button I get "Object reference[...]" and information about dbContext was null.
Below some code:
AuctionDbContext.cs
public class AuctionDbContext: DbContext
{
public AuctionDbContext(DbContextOptions<AuctionDbContext> options): base(options)
{
/* Database.EnsureCreated();*/
}
public DbSet<Auction> Auctions { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
App.cs
public partial class App : Application
{
private ServiceProvider serviceProvider;
private DbCreator dbCreator = new DbCreator();
public App()
{
ServiceCollection services = new ServiceCollection();
services.AddDbContext<AuctionDbContext>(option =>
{
option.UseSqlite("Data Source = " + DbCreator.DATABASE_FILE_PATH);
});
services.AddSingleton<MainWindow>();
serviceProvider = services.BuildServiceProvider();
}
private void OnStartup(object sender, StartupEventArgs e)
{
dbCreator.createDbFile();
dbCreator.createConnectionToDatabase();
dbCreator.createTable();
dbCreator.fillTable();
var mainWindow = serviceProvider.GetService<MainWindow>();
mainWindow.Show();
}
}
}
MainWindow.cs
public partial class MainWindow : Window
{
AuctionDbContext dbContext;
public MainWindow()
{
InitializeComponent();
}
private void MarketMenu_Clicked(object sender, RoutedEventArgs e)
{
DataContext = new MarketViewModel(dbContext);
}
}
MarketViewModel.cs
public class MarketViewModel
{
AuctionDbContext dbContext;
MarketView marketView = new MarketView();
public MarketViewModel(AuctionDbContext dbContext)
{
this.dbContext = dbContext;
GetAuctions();
}
private void GetAuctions()
{
marketView.AuctionDG.ItemsSource = dbContext.Auctions.ToList(); /* Here I got error */
}
}
}
I used this doc and I do not see any mistake :( https://learn.microsoft.com/en-us/ef/core/miscellaneous/configuring-dbcontext
Before, when I had all in mainWindow class, everything was ok but that was PoC. Something went wrong, when I refactor project to MVVM. I spent a couple of hours looking for a solution but without success.
If it will help, here's my repo on GitHub https://github.com/BElluu/EUTool. Look at branch: 1-refactor-to-mvvm coz of master is outdated yet :)
You don't seem to initialize the dbContext field in the MainWindow:
public partial class MainWindow : Window
{
AuctionDbContext dbContext;
public MainWindow(AuctionDbContext dbContext)
{
this.dbContext = dbContext;
InitializeComponent();
}
private void MarketMenu_Clicked(object sender, RoutedEventArgs e)
{
DataContext = new MarketViewModel(dbContext);
}
}
I would like to know how to send the value of a view model to another viewmodel using mvvcross and uwp
Does anyone know how to do it?
Thanks,
You can use the IMvxNavigationService to pass and return objects. The full documentation is at: https://www.mvvmcross.com/documentation/fundamentals/navigation?scroll=26
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()
{
_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
}
}
Using a IMvxMessenger you can send values without have a connection: https://www.mvvmcross.com/documentation/plugins/messenger?scroll=1446
public class LocationViewModel
: MvxViewModel
{
private readonly MvxSubscriptionToken _token;
public LocationViewModel(IMvxMessenger messenger)
{
_token = messenger.Subscribe<LocationMessage>(OnLocationMessage);
}
private void OnLocationMessage(LocationMessage locationMessage)
{
Lat = locationMessage.Lat;
Lng = locationMessage.Lng;
}
// remainder of ViewModel
}
I read the documentation how can communicate with view models and I try it. But is can't changed. Like the examples from github I create CommunicationViewModel:
public abstract class CommunicationViewModel : ViewModelBase
{
public CommunicationViewModel()
{
ExampleCommand = new Command(OnExampleCommandExecute);
}
public string Property
{
get { return GetValue<string>(PropertyProperty); }
set { SetValue(PropertyProperty, value); }
}
public static readonly PropertyData PropertyProperty = RegisterProperty("Property", typeof(string), null);
public void UpdateProperty(string modifier)
{
Property = modifier.ToString();
}
public Command ExampleCommand { get; private set; }
private void OnExampleCommandExecute()
{
ExecuteCommand();
}
protected abstract void ExecuteCommand();
protected void AddPropertyChange(string propertyName, Type senderType)
{
Argument.IsNotNull(() => propertyName);
Argument.IsNotNull(() => senderType);
// do some stuff
}
}
Then from OpenViewModel I want to send information to MainWindowViewModel.
The OpenViewModel Source code:
public class OpenViewModel : CommunicationViewModel
{
private readonly IMessageService iMessage;
private readonly IPleaseWaitService iPleaseWait;
private readonly ILanguageService iLanguage;
public OpenViewModel(IMessageService iMessageService, IPleaseWaitService iPleaseWaitService, ILanguageService iLanguageService)
{
Argument.IsNotNull(() => iMessageService);
Argument.IsNotNull(() => iPleaseWaitService);
Argument.IsNotNull(() => iLanguageService);
iMessage = iMessageService;
iPleaseWait = iPleaseWaitService;
iLanguage = iLanguageService;
ChooseDatabaseForOpen = new Command(HandleChooseDatabaseForOpen);
}
public ObservableCollection<string> Databases
{
get { return new ObservableCollection<string>(SettingGenerator.ReadFewSameDatabaseSettings("Databases", "Database", "Value")); }
set { SetValue(DatabasesProperty, value); }
}
public static readonly PropertyData DatabasesProperty = RegisterProperty("Databases", typeof(ObservableCollection<string>), () => new ObservableCollection<string>());
public Command ChooseDatabaseForOpen { get; private set; }
protected override async Task InitializeAsync()
{
await base.InitializeAsync();
// TODO: subscribe to events here
}
protected override async Task CloseAsync()
{
// TODO: unsubscribe from events here
await base.CloseAsync();
}
private void HandleChooseDatabaseForOpen()
{
iPleaseWait.Show(iLanguage.GetString("PleaseWaitMessage"));
SettingGenerator.ChangeDatabaseSetting(null, "ActiveDatabase", this.Database);
iPleaseWait.Hide();
}
protected override void ExecuteCommand()
{
// InterestedIn does not required any custom logic
}
}
And the MainWindowViewModel
[InterestedIn(typeof(OpenViewModel))]
public class MainWindowViewModel : CommunicationViewModel
{
private const string PATH_TO_CHECKED_IMAGE = #"./../Resources/Images/Icons/Settings/Checked Checkbox.png";
private const string PATH_TO_UNCHECKED_IMAGE = #"./../Resources/Images/Icons/Settings/Checkbox.png";
private readonly IUIVisualizerService iUIVisualizer;
private readonly IPleaseWaitService iPleaseWait;
private readonly IMessageService iMessage;
private readonly IOpenFileService iOpenFile;
private readonly ILanguageService iLanguage;
private readonly ISplashScreenService iSplashScreen;
private readonly string settingFile = AppDomain.CurrentDomain.BaseDirectory + FileConstants.PATH_TO_SETTINGS_FILE;
public MainWindowViewModel(IUIVisualizerService iUIVisualizerService, IPleaseWaitService iPleaseWaitService, IMessageService iMessageService, IOpenFileService iOpenFileService, ILanguageService iLanguageService, ISplashScreenService iSplashScreenService)
{
Argument.IsNotNull(() => iOpenFileService);
Argument.IsNotNull(() => iUIVisualizerService);
Argument.IsNotNull(() => iMessageService);
Argument.IsNotNull(() => iPleaseWaitService);
Argument.IsNotNull(() => iLanguageService);
Argument.IsNotNull(() => iSplashScreenService);
this.iOpenFile = iOpenFileService;
this.iUIVisualizer = iUIVisualizerService;
this.iMessage = iMessageService;
this.iPleaseWait = iPleaseWaitService;
this.iLanguage = iLanguageService;
this.iSplashScreen = iSplashScreenService;
Minimize = new Command(HandleMinimizeCommand);
Restore = new Command(HandleRestoreCommand);
CloseApplication = new Command(HandleCloseApplicationCommand);
if (SettingGenerator.ReadDatabaseSetting(null, "Update") == "Automatic")
{
this.AutoUpgradeCheckbox = PATH_TO_CHECKED_IMAGE;
}
else if (SettingGenerator.ReadDatabaseSetting(null, "Update") == "Manual")
{
this.AutoUpgradeCheckbox = PATH_TO_UNCHECKED_IMAGE;
}
SelectIconForMainMenuItems();
//this.Property = this.Database;
}
protected override void OnViewModelPropertyChanged(IViewModel viewModel, string propertyName)
{
AddPropertyChange(propertyName, viewModel.GetType());
}
protected override void ExecuteCommand()
{
// InterestedIn does not required any custom logic
}
}
And from xaml
The OpenView
<ListView Name="DatabasesListView" MinHeight="150" Width="200" ItemsSource="{Binding Databases}" SelectedItem="{Binding Property}" />
The MainWindowView
<Label Content="{Binding Property}" HorizontalAlignment="Center" Margin="0 0 0 0" FontSize="14" />
How can communicate this two view models. In example is working in my case is not working. When I change Property everytime is null. Is not give me some exception just everytime is null.
EDIT: I used two DataWindows, not using UserControls
As the documentation explains, there are several ways to communicate between view models:
Services
InterestedIn (what you are using)
MessageMediator
It looks like what you are doing is correct. If you feel it is not working as expected, please provide a repro in the official tracker.
I've added a DialogService in order to open a ProductView, so far the ShowDetailDialog() is working as expected.
Issue:
I call Close() on the ProductView, the view isn't closed. I debugged this issue by setting a break point on the call to the dialog service close method.
When I stepped through the code, the null check shows that productView is null, which prevents Close() from being called.
Does anyone have idea why productView is null? (although it's showing data on the view)
DialogService:(hosts the Show and Close methods)
namespace MongoDBApp.Services
{
class DialogService : IDialogService
{
Window productView = null;
ProductView _productView;
public DialogService()
{
_productView = new ProductView();
}
public void CloseDetailDialog()
{
if (productView != null)
productView.Close();
}
public void ShowDetailDialog()
{
_productView.ShowDialog();
}
}
}
ProductViewModel: (summary of ProductVM, calls the close method on SaveCommand)
private void SaveProduct(object product)
{
_dialogService.CloseDetailDialog();
Messenger.Default.Send<ProductModel>(SelectedProduct);
}
CustomerOrdersViewmodel: (Where the ShowDetailDialog() is called initially)
private void EditOrder(object obj)
{
Messenger.Default.Send<ProductModel>(SelectedProduct);
_dialogService.ShowDetailDialog();
}
This is how I have always closed my windows.
Here would be my command:
class CancelCommand : ICommand
{
private NewTruckViewModel newTruck;
public CancelCommand(NewTruckViewModel vm)
{
newTruck = vm;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
newTruck.Cancel();
}
}
Here is my view Model and the method that gets called from my command:
private NewTruck myWnd; //View Declaration
//Ctor where I set myView (myWnd) equal to a view that is passed in.
public NewTruckViewModel(ObservableCollection<Truck> Trucks, NewTruck wnd, bool inEditTruck)
{
myEngine.stopHeartBeatTimer();
editTruck = inEditTruck;
myWnd = wnd;
SaveTruckCommand = new SaveTruckCommand(this);
CancelCommand = new CancelCommand(this);
ClearCommand = new ClearCommand(this);
SetLevel1MTCommand = new SetLevel1MTCommand(this);
SetLevel2MTCommand = new SetLevel2MTCommand(this);
SetLevel3MTCommand = new SetLevel3MTCommand(this);
SetLevel1FLCommand = new SetLevel1FLCommand(this);
SetLevel2FLCommand = new SetLevel2FLCommand(this);
SetLevel3FLCommand = new SetLevel3FLCommand(this);
myTrucks = Trucks;
}
public void Cancel()
{
myWnd.Close();
}
This works for me.
I resolved the issue by implementing an IDialogService on the View. Then calling the Show() and Close() methods from the ViewModel.
Solution:
Interface:
public interface IDialogService
{
void CloseDialog();
void ShowDialog(EditProductViewModel prodVM);
}
View:
public partial class ProductView : Window, IDialogService
{
public ProductView()
{
InitializeComponent();
this.DataContext = new EditProductViewModel(this);
}
public void CloseDialog()
{
if (this != null)
this.Visibility = Visibility.Collapsed;
}
public void ShowDialog(EditProductViewModel prodVM)
{
this.DataContext = prodVM;
this.Show();
}
private void Window_Closed(object sender, EventArgs e)
{
this.Visibility = Visibility.Collapsed;
}
}
ViewModel #1:
private IDialogService _dialogService;
public CustomerOrdersViewModel(IDialogService dialogservice)
{
this._dialogService = dialogservice;
}
private void EditOrder(object obj)
{
EditProductViewModel pvm = new EditProductViewModel(_dialogService);
pvm.Present(pvm);
Messenger.Default.Send<ProductModel>(SelectedProduct);
}
ViewModel #2:
private IDialogService _dialogService;
public EditProductViewModel(IDialogService dialogService)
{
this._dialogService = dialogService;
}
private void SaveProduct(object product)
{
SelectedProduct = SelectedProductTemp;
_dialogService.CloseDialog();
}
public void Present(EditProductViewModel prodVM)
{
_dialogService.ShowDialog(prodVM);
}
I am searching a pattern to keep SOLID principles in my application when I use ICommand. Basically my problem is the command execution has a dependency with the view model but at the same time the view model has a dependency with the command (I inject them by constructor). I would like to keep the viewmodel with properties only, so this is an example of my current implementation:
public class MyViewModel : INotifyPropertyChanged
{
public ICommand MyCommand { get; private set; }
public string Message { get; set; } // PropertyChanged ommited
public MyViewModel()
{
}
public void SetCommand(ICommand myCommand)
{
this.MyCommand = myCommand;
}
....
}
internal interface IMyViewModelCommandManager
{
void ExectueMyCommand();
}
internal class MyViewModelCommandManager : IMyViewModelCommandManager
{
private readOnly MyViewModel myViewModel;
public MyViewModelCommandManager(MyViewModel myViewModel)
{
this.myViewModel = myViewModel;
}
public ExectueMyCommand()
{
MessageBox.Show(this.myViewModel.Message);
}
}
internal class MyViewModelFactory: IMyViewModelFactory
{
private readonly IContainerWrapper container;
public MyViewModelFactory(IContainerWrapper container)
{
this.container = container;
}
public MyViewModel Create()
{
MyViewModel viewModel = new MyViewModel();
IMyViewmodelCommandManager manager = this.container.Resolve<IMyViewmodelCommandManager>(new ResolverOverride[] { new ParameterOverride("viewModel", viewModel) });
ICommand myCommand = new DelegateCommand(manager.ExecuteMyCommand);
viewModel.SetCommand(myCommand);
return viewModel;
}
}
So, to avoid use the SetCommand method. I have thought two solutions but I don't know if they are elegant.
The first one is to move the viewmodel dependency from the constructor to the method updating the code in this way:
public class MyViewModel : INotifyPropertyChanged
{
public ICommand MyCommand { get; private set; }
public string Message { get; set; } // PropertyChanged ommited
public MyViewModel(ICommand myCommand)
{
this.MyCommand = myCommand;
}
....
}
internal interface IMyViewModelCommandManager
{
void ExectueMyCommand(MyViewModel viewModel);
}
internal class MyViewModelCommandManager : IMyViewModelCommandManager
{
public MyViewModelCommandManager()
{
....
}
public ExectueMyCommand(MyViewModel viewModel)
{
MessageBox.Show(myViewModel.Message);
}
}
internal class MyViewModelFactory: IMyViewModelFactory
{
private readonly IContainerWrapper container;
public MyViewModelFactory(IContainerWrapper container)
{
this.container = container;
}
public MyViewModel Create()
{
IMyViewmodelCommandManager manager = this.container.Resolve<IMyViewmodelCommandManager>(..);
ICommand myCommand = new DelegateCommand<MyViewModel>(manager.ExecuteMyCommand);
MyViewModel viewModel = new MyViewModel(myCommand);
return viewModel;
}
}
Of course, the xaml code will use CommandParameter:
<Button Content="Show Message" Command="{Binding MyCommand}" CommandParameter="{Binding .}" />
Other solution I have thought is to use a trick creating a Wrapper of the viewModel and the commandManager have a dependency with the Wrapper instead of the viewModel:
internal class MyViewModelCommandContext
{
public MyViewModel ViewModel { get; set; }
}
public class MyViewModel : INotifyPropertyChanged
{
public ICommand MyCommand { get; private set; }
public string Message { get; set; } // PropertyChanged ommited
public MyViewModel(ICommand myCommand)
{
this.MyCommand = myCommand;
}
....
}
internal interface IMyViewModelCommandManager
{
void ExectueMyCommand();
}
internal class MyViewModelCommandManager : IMyViewModelCommandManager
{
private readonly MyViewModelCommandContext context;
public MyViewModelCommandManager(MyViewModelCommandContext context)
{
this.context = context;
....
}
public ExectueMyCommand()
{
MessageBox.Show(this.context.myViewModel.Message);
}
}
internal class MyViewModelFactory: IMyViewModelFactory
{
private readonly IContainerWrapper container;
public MyViewModelFactory(IContainerWrapper container)
{
this.container = container;
}
public MyViewModel Create()
{
MyViewModelCommandContext context = new MyViewModelCommandContext();
IMyViewmodelCommandManager manager = this.container.Resolve<IMyViewmodelCommandManager>(new ResolverOverride[] { new ParameterOverride("context", context) });
ICommand myCommand = new DelegateCommand(manager.ExecuteMyCommand);
MyViewModel viewModel = new MyViewModel(myCommand);
context.ViewModel = viewModel;
return viewModel;
}
}
In my opinion the first one is the best solution for this problem, what do you think is the best solution. Would you apply another solution?
IMHO both solutions are overly complicated. SOLID is great, KISS is better.
Your MyViewModelCommandManager is currently directly coupled to MyViewModel since it needs the latter's Message, so what is the advantage of having them separate? Why not simply implement the command inside MyViewModel?
If this would entail injecting too many dependencies into MyViewModel, then think about what you actually need the command to do, and abstract away everything else that isn't needed.
The command displays a message.
The message is held by MyViewModel
You want to display the message outside MyViewModel (maybe other viewmodels also need to display a message and you want to reuse the code?)
So all you really need is some kind of notification from MyViewModel that it wants to display a message, or that something has occured that would result in a message being displayed.
Possible solutions:
Inject an IMessageDisplayService to MyViewModel. MyViewModel calls it with the message.
Inject a callback to MyViewModel similar to above.
Have MyViewModel raise an event with the message as an EventArg.
The inferred responsibilities of the above solutions are subtly different.
means that MyViewModel is in charge. It wants to display a message.
is less explicit. MyViewModel knows it needs to call the callback, but doesn't really know or care what it does.
is like 2 but even more decoupled. Multiple things can subscribe or unsubscribe to the event but MyViewModel remains blissfully ignorant.
All three of these mean that the thing displaying the message has no need to know about MyViewModel. You have decoupled them. It's MyViewModelFactory that does any wiring up required.
Thanks for your opinions.
I understand you when you say I am creating a complex pattern, but in a big project with a big developers team, if there is not clear patterns with split responsabilities, the code maintenance could be impossible to perform.
Reading you and your third solution I have thought one possible solution. It seems complexity but, in my opinion, improves the code quality. I will create a commandContext, which only have the viewmodel properties needed for the code, avoiding to have all viewmodel in the command manager. Also I will create a class whose responsability is to mantain the context updated when the viewmodel changes. This is the possible code:
internal class MyCommandContext
{
public string Message { get; set; }
}
public class MyViewModel : INotifyPropertyChanged
{
public ICommand MyCommand { get; private set; }
public string Message { get; set; } // PropertyChanged ommited
public string OtherProperty { get; set; }
public ObservableCollection<MyChildViewModel> Childs { get; set; }
public MyViewModel(ICommand myCommand)
{
this.MyCommand = myCommand;
}
....
}
internal interface IMyViewModelCommandManager
{
void ExectueMyCommand();
}
internal class MyViewModelCommandManager : IMyViewModelCommandManager
{
private readonly MyCommandContext context;
public MyViewModelCommandManager(MyViewModelCommandContext context)
{
this.context = context;
....
}
public ExectueMyCommand()
{
MessageBox.Show(this.context.Message);
}
}
internal interface IMyViewModelCommandSynchronizer
{
void Initialize();
}
internal class MyViewModelCommandSynchronizer : IMyViewModelCommandSynchronizer, IDisposable
{
private readOnly MyViewModel viewModel;
private readOnly MyCommandContext context;
MyViewModelCommandSynchronizer(MyViewModel viewModel, MyCommandContext context)
{
this.viewModel = viewModel;
this.context = context;
}
public void Initialize()
{
this.viewModel.PropertyChanged += this.ViewModelOnPropertyChanged;
}
private void ViewModelOnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Message")
{
this.context.Message = this.viewModel.Message;
}
}
// Dispose code to deattach the events.
}
internal class MyViewModelFactory: IMyViewModelFactory
{
private readonly IContainerWrapper container;
public MyViewModelFactory(IContainerWrapper container)
{
this.container = container;
}
public MyViewModel Create()
{
MyCommandContext context = new MyCommandContext();
IMyViewmodelCommandManager manager = this.container.Resolve<IMyViewmodelCommandManager>(new ResolverOverride[] { new ParameterOverride("context", context) });
ICommand myCommand = new DelegateCommand(manager.ExecuteMyCommand);
MyViewModel viewModel = new MyViewModel(myCommand);
IMyViewModelCommandSynchronizer synchronizer = this.container.Resolve<IMyViewmodelCommandManager>(new ResolverOverride[] { new ParameterOverride("context", context), new ParameterOverride("viewModel", viewModel) });
synchronizer.Initialize();
return viewModel;
}
}