Short version:
If I have ViewModel, containing its Model object and exposing its properties, how do I get the model "back" after it has been edited? If the Model-inside-ViewModel is public, it violates encapsulation, and if it is private, I cannot get it (right?).
Longer version:
I am implementing a part of an application which displays collections of objects. Let's say the objects are of type Gizmo, which is declared in the Model layer, and simply holds properties and handle its own serialization/deserialization.
In the Model layer, I have a Repository<T> class, which I use to handle collections of MasterGizmo and DetailGizmo. One of the properties of this repository class is an IEnumerable<T> Items { get; } where T will be some of the Gizmo subtype.
Now since Gizmo doesn't implement INPC, I have created the following classes in ViewModel layer:
GizmoViewModel, which wraps every public property of a Gizmo so that setting any property raises PropertyChanged accordingly;
[**] RepositoryViewModel<T>, which has an ObservableCollection<GizmoViewModel> whose CollectionChanged is listened to by a method that handles Adds, Removes and Updates to the repository.
Notice that the Model layer has a "Repository of Models", while the ViewModel layer has a "ViewModel with an ObservableCollection of ViewModels".
The doubt is related to the [**] part above. My RepositoryViewModel.CollectionChangedHandler method is as follows:
void CollectionChangedHandler(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (var added in e.NewItems)
{
var gvm = added as GizmoViewModel;
if (gvm != null)
{
//// IS ANY OF THE ALTERNATIVES BELOW THE RIGHT ONE?
// Gizmo g = gvm.RetrieveModel(); ?? proper getter ??
// Gizmo g = GetModelFromViewModel(gvm); ?? external getter ??
// Gizmo g = gvm.Model; ?? public model property ??
_gizmo_repository.Add(g);
}
}
break;
....
Besides that, if anyone can detect any MVVM smell here, I'll be happy to know.
We can deal with our Models even outside the View and ViewModel layers, so leaving the model publicly accessible from ViewModel is I believe acceptable.
Let say you are creating the Models in "DataLayer" you can pass the instance of the Model to the ViewModel. To illustrate my point:
///Models ////////////////////////////
public interface IGizmo{}
public class Gizmo:IGizmo{}
public class SuperGizmo : IGizmo {}
public class SuperDuperGizmo : IGizmo { }
//////////////////////////////////////
public interface IGizmoViewModel<out T>
{
T GetModel();
}
public abstract class GizmoViewModelBase : IGizmoViewModel<IGizmo>
{
protected GizmoViewModelBase(IGizmo model)
{
_Model = model;
}
private readonly IGizmo _Model;
public IGizmo GetModel()
{
return _Model;
}
}
public class GizmoViewModel : GizmoViewModelBase
{
public GizmoViewModel(Gizmo model)
: base(model) { }
}
public class SuperDuperGizmoViewModel : GizmoViewModelBase
{
public SuperDuperGizmoViewModel(SuperDuperGizmo model)
: base(model){}
}
Your repository of Models will be updated on whatever updates it get from the ViewModel as long as you passed the same instance. So there is no need to have a repository of ViewModels to get the updates.
Reading your code, I think there is something of a mixup regarding your ViewModel and Model separation.
So, as I understand it, when your ObservableCollection of GizmoViewModel's changes, you are trying to add the Gizmo instance of the new item back to your Model?
I would approach this differently. You should create your Gizmo instances inside your Model layer, and when you do this you should add it to the Repository.
Otherwise, you haven't provided enough information - or rather, you have provided too much but it is the wrong sort of information. You need to describe the situation in which you want to do this, where these GizmoViewModels are created, etc.
From what I can see here, your GizmoViewModel has a dependency to your Repository<T>, so why not pass in the repository when you create your view model?
public class GizmoViewModel
{
private IRepository<Gizmo> _Repo;
//Underlying model (Doesn't implement INotifyPropertyChanged)
private Gizmo _Model;
//Wrapping properties
public int MyProperty
{
get { return _Model.Property; }
set
{
_Model.Property = value;
NotifyOfPropertyChange();
}
}
...
public GizmoViewModel(IRepository<Gizmo> repo)
{
_Repo = repo;
}
public void AddToRepo()
{
_Repo.Add(_Model);
}
...
It would be even better if these methods are inside the RepositoryViewModel base class. You can really go crazy with inheritance here. Perhaps something like this:
var gvm = added as IRepositoryViewModel;
if (gvm != null)
gvm.AddToRepo();
You can then simply call AddToRepo when you need to add the view model's underlying model to the repository.
Perhaps not the most elegant solution, however if encapsulation is what's worrying you, then you need to ensure that your dependencies are properly managed.
"If the Model-inside-ViewModel is public, it violates encapsulation"
Your assertion above is completely wrong and is killing your code.
By setting the Model property in ViewModel as private, you are forced to repeat your self ( code smells ), as you will need to define in your ViewModel, the same properties as you did for your Model, effectively transforming it into a Model class that mimics the Model it is supposed to expose to the View.
In MVVM the ViewModel role is to provide the View with all the presentation data and logic that it needs and for sure the Model is fundamental part of this data, by hidding it from the View you are killing MVVM.
Related
I'm in a scenario to reuse a view with two completly independent view models.
For example you can think a generic list view to show apples somewhere and somewhere else to show cars. Doesn't really matter.
In Prism.Forms for Xamarin im able to glue a view with a viewModel like this.
Container.RegisterTypeForNavigation<PageA, ViewModelA>("PageA1");
Container.RegisterTypeForNavigation<PageA, ViewModelB>("PageA2");
I can't find an equivalent in Prism WPF, can someone help me out?
The link that #AdamVincent posted and the "missing" methods are very useful for normal view/viewmodel navigation using the ViewModelLocationProvider. However when trying to use two view models for the same view they don't work. This is because inside the extension method there is a call that registers the viewmodel to the view for use by the ViewModelLocationProvider.
private static IUnityContainer RegisterTypeForNavigationWithViewModel<TViewModel>(this IUnityContainer container, Type viewType, string name)
{
if (string.IsNullOrWhiteSpace(name))
name = viewType.Name;
ViewModelLocationProvider.Register(viewType.ToString(), typeof(TViewModel));
return container.RegisterTypeForNavigation(viewType, name);
}
Internally, ViewModelLocationProvider.Register uses a dictionary to store the association between view models and views. This means, whe you register two view models to the same view, the second will overwrite the first.
Container.RegisterTypeForNavigation<PageA, ViewModelA>("PageA1");
Container.RegisterTypeForNavigation<PageA, ViewModelB>("PageA2");
So with the above methods, when using the ViewModelLocationProvider, it will always create an instance of ViewModelB because it was the last one to be registered.
Additionally, the next line calls RegisterTypeForNavigation which itself ultimately calls Container.RegisterType, is only passing the viewType.
To resolve this, I tackled it a different way using an Injection property. I have the following method to bind my viewmodel to my view
private void BindViewModelToView<TView,TViewModel>(string name)
{
if (!Container.IsRegistered<TViewModel>())
{
Container.RegisterType<TViewModel>();
}
Container.RegisterType<TView, TViewModel>(name,new InjectionProperty("DataContext", new ResolvedParameter<TViewModel>()));
}
We know each view will have a DataContext property, so the Injection property will inject the viewmodel directly into the DataContect for the view.
When registering the viewmodels, instead of using RegisterTypeForNavigation, you would use the following calls:
BindViewModelToView<PageA,ViewModelA>("ViewModelA");
BindViewModelToView<PageA,ViewModelB>("ViewModelB");
To create the view, I already have a method that I use to inject the appropriate view into my region, and it works using the viewname as the key to obtain the correct viewmodel instance.
private object LoadViewIntoRegion<TViewType>(IRegion region, string name)
{
object view = region.GetView(name);
if (view == null)
{
view = _container.Resolve<TViewType>(name);
if (view is null)
{
view = _container.Resolve<TViewType>();
}
region.Add(view, name);
}
return view;
}
Which I simply call with
var view = LoadViewintoRegion<PageA>(region,"ViewModelA");
and
var view = LoadViewintoRegion<PageA>(region,"ViewModelB");
So for normal single View/Viewmodels, I use the ViewModelLocationProvider.AutoWireViewModel property and where I have multiple viewmodels, I use this alternative approach.
2022/01/15 Update
First of all thanks a lot for Jason's answer, his answer is great, and I implemented it perfectly with reference to his design, but because of Prism version update, I made some changes
I have an single view with multiple viewmodel
Step 1
register your view region
<ContentControl prism:RegionManager.RegionName="{x:Static hard:RegionNames.PanelPosCameraRegion}"></ContentControl>
Step 2
coding your viewmodel
private IRegionManager _RegionManager;
private IUnityContainer _UnityContainer;
public ICommand LoadedCommand { get; set; }
public RoboticPageVM(IRegionManager regionManager, IUnityContainer unityContainer)
{
_RegionManager = regionManager;
_UnityContainer = unityContainer;
LoadedCommand = new DelegateCommand(LoadedCommandHandle);
}
private void LoadedCommandHandle()
{
BindViewModelToView<PanelPosMultiplexView, PanelPosCameraVM>("Camera");
BindViewModelToView<PanelPosMultiplexView, PanelPosAxisVM>("Axis");
LoadViewIntoRegion<PanelPosMultiplexView>(RegionNames.PanelPosCameraRegion, "Camera");
LoadViewIntoRegion<PanelPosMultiplexView>(RegionNames.PanelPosAxisRegion, "Axis");
}
private void BindViewModelToView<TView, TViewModel>(string registerName)
{
if (!_UnityContainer.IsRegistered<TViewModel>())
{
_UnityContainer.RegisterType<TViewModel>();
}
_UnityContainer.RegisterType<TView>(registerName, new InjectionProperty(nameof(UserControl.DataContext), new ResolvedParameter<TViewModel>()));
}
private void LoadViewIntoRegion<TView>(string regionName, string registerName)
{
IRegion region = _RegionManager.Regions[regionName];
object? view = region.GetView(registerName);
if (view == null)
{
view = _UnityContainer.Resolve<TView>(registerName);
}
if (!region.Views.Any(v => v.GetType() == typeof(TView)))
{
region.Add(view, registerName);
}
}
Should it be like this
public class DataAccess
{
MyDbContext ctx;
public DataAccess()
{
ctx = new MyDbContext();
}
public List<entity> GetAll()
{
return ctx.entities.ToList();
}
}
or this
public List<entity> GetAll()
{
using (var ctx = new MyDbContext())
{
return ctx.entities.ToList();
}
}
Thanks a lot and if there is a problem in my questions, please inform me so that I may improve it.
The optimal lifetime of a DbContext depends on your requirements and may vary from case to case but in general you should prefer short-lives contexts (the second code snippet in your sample code):
Entity Framework’s contexts are meant to be used as short-lived instances in order to provide the most optimal performance experience ...
https://msdn.microsoft.com/en-gb/data/hh949853.aspx#9
if i have a viewmodel for adding new item and another viewmodel for displaying all or filtered items (the second viewmodel's view must always reflect any changes in the collection source), should the change be communicated from viewmodel to viewmodel directly, or viewmodel to repository to the second viewmodel?
The first view model could for example add the new item to an ObservableCollection of the second view model that the view binds to. So it should add the new item to the database using the respository and also add the new entity object to the source collection of the second view model one way or another.
I'm currently making an application with WPF and MVVM Light toolkit.
I have this view model :
public class MainViewModel : ViewModelBase
{
// Instance of service which is used for sending email.
private IEmailService _emailService;
// Get/set instance of service which is used for sending email.
public IEmailService EmailService
{
get
{
return _emailService;
}
set
{
Set("EmailService", ref _emailService, value);
}
}
public MainViewModel()
{
_emailService = new ServiceLocator.Current.GetInstance<IEmailService>();
}
}
Email service is a service which handles sending/processing emails. When user interacts with an element on the screen, email service is called (this has been registered in ServiceLocator)
I wonder if my implement is correct with MVVM design pattern or not. And are there any better ways to inject service into view model (the current approach takes a lot of time declaring initializing property)
I wonder if my implement is correct with MVVM design pattern or not.
Dependency injection has nothing to do with the MVVM pattern really. MVVM is about separation of concern between user interface controls and their logic. Dependency injection enables you to inject a class with any objects it needs without the class having to create these objects itself.
And are there any better ways to inject service into view model (the current approach takes a lot of time declaring initializing property)
If it makes no sense for the view model class to exist without a reference to the service, you should use constructor dependency injection instead of injecting the dependency through a property. You can read more about this here:
Dependency injection through constructors or property setters?
Using your current implementation it is possible to use the view model class without the service:
MainViewModel vm = new MainViewModel();
vm.EmailService = null;
A better implementation would be something like this:
public class MainViewModel : ViewModelBase
{
// Instance of service which is used for sending email.
private readonly IEmailService _emailService;
public MainViewModel(IEmailService emailService = null)
{
_emailService = emailService ?? ServiceLocator.Current.GetInstance<IEmailService>();
if(_emailService is null)
throw new ArgumentNullException(nameof(emailService));
}
}
This makes sure that the view model class always has a valid reference to an IEmailService. It also makes it possible to inject it with any implementation of the IEmailService interface when you construct the object.
I am new to WPF and C# im trying to understand how can I update a UI element from a BL class (to keep a seperation between the logic and the UI) the bl gets periodic updates from a c++ network component and should update the form once a new argument comes in (I read on the msdn website but I want to see some concrete examples to make sure I got it right)
Because of your gets periodic updates from a c++ network component comment, I am assuming that you already have a system to update your property. I would expose that property from your business class in a view model class, a class with public properties and ICommand functions designed specifically to supply all of the required data to a view, or UserControl.
To be honest, I wouldn't have that (or any) functionality in a business class (depending what you mean by business class)... I'd personally put it straight into the view model, or have a manager/service class that exposed it.
If you insist on keeping it where it is, you'll have to implement either an event or a delegate in your business class so that users of that class can be alerted as to when the value changes. Then you could simply attach a handler to your event/delegate from the view model class and easily update the exposed property whenever the actual property changes.
So it would go a little something like this... in your business class (I am assuming that your value is an int, but you can change this if it is incorrect... the principal is the same):
public delegate void FieldUpdate(int value);
public FieldUpdate OnFieldUpdate { get; set; }
...
private int field;
public int Field
{
get { return field; }
set
{
if (value != field)
{
field = value;
if (OnFieldUpdate != null) OnFieldUpdate(field);
}
}
}
Then in your view model:
private YourBusinessClass instance = new YourBusinessClass();
public YourBusinessClass Instance
{
get { return instance; }
set { instance = value; NotifyPropertyChanged("Instance"); }
}
Attach a handler:
instance.OnFieldUpdate += OnBusinessClassFieldUpdate;
...
public void OnBusinessClassFieldUpdate(int value)
{
Instance = value;
}
Now whenever the field is updated in the business class, the view model (and data bound UI controls) will automatically update via the delegate.
I am implementing a WPF based application using MVVMfor the UI.
I have a ViewModel that wraps each editable Model that can be edited. The VM contains all the logic for handling error notifications, "is dirty" management and so forth ..
This design supports well CRUD schenarios for simple domain Model objects that are anemic, that is, do not contain any logic.
Now, I am facing a more tricky problem cause I have a domain Model that contains logic and that logic can change the internal state of the domain Model.
Do someone have already faced this scenario ? If so, do you have some advices to handle this correctly ?
Riana
Here is how I usually deal with it:
The ViewModel layer is made of types that belong to this layer, meaning I don't ever directly use my business objects inside of a ViewModel. I map my business objects to ViewModel objects that may or may not be the exact same shape minus the behaviors. It can be argued that this violates Don't Repeat Yourself, but doing so allows you to adhere to the Single Responsibility Principle. In my opinion, SRP should usually trump DRY. The ViewModel exists to serve the view, and the model exists to serve business rules / behavior.
I create a facade/service layer that takes and returns ViewModels as arguments, but maps the ViewModels to-and-from their corresponding business object versions. This, way the non-anemic objects won't impose non view logic on the ViewModel
The dependencies would look like this:
ViewModel <--> Facade/ServiceLayer --> Business Objects
I think it is important to keep this in mind if you want to unleash the full potential of MVVM: The ViewModel is the model/abstraction of the view, not the model presented to the view.
Try using Command pattern. Your screen should be design not to edit an entity but to perform an action (command) on an entity. If you follow that principle when designing your screens, your ViewModel will have properties that should be mapped to a command object. Then, the command will be send to an (remote) facade of the domain model.
ViewModels for displaying the data could be mapped directly to the database (bypassing the domain model altogether) so that you don't need to put nasty getters in the domain model classes.
If the domain model is non-anemic, you will need to use events to communicate internal changes in the Model back to the ViewModel. That way you don't have to worry about keeping track of what operations could potentially make your VM out-of-sync with the model.
Here's a simple example:
First, a sample model:
public class NonAnemicModel
{
private string _name;
public string Name
{
get { return _name; }
set
{
if (_name == value)
return;
_name = value;
OnNameChanged(EventArgs.Empty);
}
}
public event EventHandler NameChanged;
protected virtual void OnNameChanged(EventArgs e)
{
if (NameChanged != null)
NameChanged(this, e);
}
public void PerformNameCalculation(int chars)
{
//example of a complex logic that inadvertently changes the name
this.Name = new String('Z', chars); //makes a name of Z's
}
}
And here's a sample ViewModel:
public class MyViewModel : INotifyPropertyChanged
{
private NonAnemicModel _model;
public NonAnemicModel Model
{
get { return _model; }
set
{
_model = value;
_model.NameChanged += (sender, args) => NotifyPropertyChanged("UserName");
}
}
public string UserName
{
get { return this.Model.Name; }
set { this.Model.Name = value; }
}
//this command would call out to the PerformNameCalculation method on the Model.
public ICommand PerformNameCalculation { get; private set; }
}
Notice that the PropertyChanged event is raised when the Name on the model changes. That way, regardless of whether the UserName setter was used, or the PerformNameCalculation command was used, the ViewModel stays in sync. The big downside to this is that you have to add many more events to your Model, but I've found that having these events in place is usually very helpful in the long run. Just be careful about memory leaks with events!