AutoMapper Ignore an item in a collection based on a property value in the source - silverlight

I'm mapping an ApplianceViewModel to a ApplianceDTO. Each Appliance has a collection of ActionViewModels which are mapped to ActionDTO. What I'd like to do is configure the mapper to ignore ActionViewModels whose IsPersisted value is False.
My ViewModel classes ...
public interface IApplianceViewModel : INotifyPropertyChanged
{
ObservableCollection<IActionViewModel> Actions { get; set; }
// other properties removed for simplicity
}
public interface IActionViewModel : INotifyPropertyChanged
{
bool IsPersisted { get; set; }
// other properties removed for simplicity
}
My DTO classes ...
public class ApplianceDTO
{
public IEnumerable<ActionDTO> Actions { get; set; }
// other properties removed for simplicity
}
public class ActionDTO
{
// properties removed for simplicity
}
I set up my mapping like this ...
Mapper.CreateMap<IApplianceViewModel, ApplianceDTO>();
Mapper.CreateMap<IActionViewModel, ActionDTO>()
var appliance = new ApplianceViewModel {
Actions = new ObservableCollection<IActionViewModel>(
new List<IActionViewModel> {
new ActionViewModel { IsPersisted = true },
new ActionViewModel { IsPersisted = false }
}};
var applianceDTO = Mapper.Map<IApplianceViewModel, ApplianceDTO>(applianceDTO);
Currently my applianceDTO will have two items in it's Actions collection, but I'd like to set up my mapping so that the ApplianceActionViewModel with the IsPersisted property set to false isn't mapped. Can I do this?
Update
Omu's comment lead me to a solution using a ValueResolver to map the collection of Actions. I'm not really happy with this solution but its the best option available.
First I created a custom ValueResolver.
public class IsPersistedCollectionResolver : ValueResolver<IApplianceViewModel, IEnumerable<ActionDTO>>
{
protected override IEnumerable<ActionDTO> ResolveCore(IApplianceViewModel source)
{
return Mapper.Map<IEnumerable<IActionViewModel>, IEnumerable<ActionDTO>>(source.Actions.Where(x => x.IsPersisted));
}
}
Then I modified my code to use it in the mapping configuration.
Mapper.CreateMap<IApplianceViewModel, ApplianceDTO>()
.ForMember(dest => dest.Actions, opt => opt.ResolveUsing<IsPersistedCollectionResolver>());
Mapper.CreateMap<IActionViewModel, ActionDTO>();

have you tried doing something like :
Mapper.map(objects.Where(o => o.IsPersisted == true))

Related

How to update a string property in the Model from an aggregated ViewModel

I'm working on a GUI application in WPF/MVVM. Let's say i have a Model class which is populated deserializing a (third-party) XML file
class Person
{
public string Name { get; set; }
public string Address { get; set; }
public string ReservationId { get; set; }
}
and a ViewModel which exposes to its View a lot of properties and commands to manipulate the Model, in this case strings (such as ReservationId):
class StringManipulatorViewModel
{
string modelString; //here's the problem
public StringManipulatorViewModel(string modelString)
{
this.modelString = modelString;
}
//Stuff to let the view manipulate the string
}
StringManipulatorViewModel is highly reusable and it is used by a lot of ViewModels, e.g.
class PersonViewModel
{
Person model;
public StringManipulatorViewModel ReservationManipulatorVM; //aggregated ViewModel
public StringManipulatorViewModel AddressManipulatorVM; //aggregated ViewModel
public PersonViewModel(Person model)
{
this.model = model;
ReservationManipulatorVM = new StringManipulatorViewModel(model.ReservationId); //here's the problem
AddressManipulatorVM = new StringManipulatorViewModel(model.Address); //here's the problem
}
}
Obviously passing the string as "model" to the ViewModel isn't effective, and C# doesn't seem to allow string references as fields.
What is the best/right way to let member ViewModels manipulate the Model when dealing with string types?
Thank you
Your problem is that you are trying to reference a property, not a string field.
But you can pass a delegate to the setter of the property.
If you also change the modelString field to a property, you can call this delegate automatically when the string is changed.
class StringManipulatorViewModel
{
private string modelString
{
get { return _modelString; }
set { _modelString = value; if (SetModelString != null) SetModelString(value); }
}
private string _modelString;
Action<string> SetModelString;
public StringManipulatorViewModel(string initialValue, Action<string> setModelString)
{
this.modelString = initialValue;
SetModelString = setModelString;
}
//Stuff to let the view manipulate the string
}
You initiate the StringManipulatorViewModel in PersonViewModel like this:
ReservationManipulatorVM = new StringManipulatorViewModel(model.ReservationId, value => model.ReservationId = value); //here's the problem
Here are some other ideas when you want to pass a property.
Passing properties by reference in C#
I can't think of a "right" way to manipulate the string as a reference inside the StringManipulatorViewModel, once you pass the string as a value it has nothing to do with the model.
But a way to legitimately change the model string value whenever the StringManipulatorViewModel manipulates it, is by raising an Event in the view model when it manipulates the string and then add an event handler to update the model with the new value:
class StringManipulatorViewModel
{
string modelString;
public event EventHandler<string> StringManipulated;
public StringManipulatorViewModel(string modelString)
{
this.modelString = modelString;
}
public ManipulateString()
{
// Manipulate the string
StringManipulated?.Invoke(this, modelString);
}
}
And in the PersonViewModel constructor:
class PersonViewModel
{
Person model;
public StringManipulatorViewModel ReservationManipulatorVM;
public StringManipulatorViewModel AddressManipulatorVM;
public PersonViewModel(Person model)
{
this.model = model;
ReservationManipulatorVM = new StringManipulatorViewModel(model.ReservationId);
AddressManipulatorVM = new StringManipulatorViewModel(model.Address);
ReservationManipulatorVM.StringManipulated += (sender, e) => model.ReservationId = e;
AddressManipulatorVM.StringManipulated += (sender, e) => model.Address = e;
}
}

How to use ReactiveList so UI is updated when items are added/removed/modified

I'm creating a WinForms application with a DataGridView. The DataSource is a ReactiveList. Adding new items to the list however does not update the UI.
ViewModel
public class HomeViewModel: ReactiveObject
{
public ReactiveCommand<object> AddCmd { get; private set; }
ReactiveList<Model> _models;
public ReactiveList<Model> Models
{
get { return _models; }
set { this.RaiseAndSetIfChanged(ref _models, value); }
}
public HomeViewModel()
{
Models = new ReactiveList<Model>() { new Model { Name = "John" } };
AddCmd = ReactiveCommand.Create();
AddCmd.ObserveOn(RxApp.MainThreadScheduler);
AddCmd.Subscribe( _ =>
{
Models.Add(new Model { Name = "Martha" });
});
}
}
public class Model
{
public string Name { get; set; }
}
View
public partial class HomeView : Form, IViewFor<HomeViewModel>
{
public HomeView()
{
InitializeComponent();
VM = new HomeViewModel();
this.OneWayBind(VM, x => x.Models, x => x.gvData.DataSource);
this.BindCommand(VM, x => x.AddCmd, x => x.cmdAdd);
}
public HomeViewModel VM { get; set; }
object IViewFor.ViewModel
{
get { return VM; }
set { VM = (HomeViewModel)value; }
}
HomeViewModel IViewFor<HomeViewModel>.ViewModel
{
get { return VM; }
set { VM = value; }
}
}
The view always show "John".
Debugging Subscribe show added items.
Tried it with ObservableCollection same result.How to use ReactiveList so UI is updated when new items are added
Tried it with IReactiveDerivedList same result. Does ReactiveUI RaiseAndSetIfChanged fire for List<T> Add, Delete, Modify?
I think what you want is a ReactiveBindingList rather than a ReactiveList. This is a WinForms specific version of the ReactiveList for binding purposes.
You should use BindingList.
reference :
"If you are bound to a data source that does not implement the IBindingList interface, such as an ArrayList, the bound control's data will not be updated when the data source is updated. For example, if you have a combo box bound to an ArrayList and data is added to the ArrayList, these new items will not appear in the combo box. However, you can force the combo box to be updated by calling the SuspendBinding and ResumeBinding methods on the instance of the BindingContext class to which the control is bound."
https://learn.microsoft.com/en-us/dotnet/desktop/winforms/controls/how-to-bind-a-windows-forms-combobox-or-listbox-control-to-data?view=netframeworkdesktop-4.8
Or
ReactiveBindingList
It work fine for me. !!!

WPF binding Datasource is updated through reference

is it possible to set a Datasource through a reference?
public partial class GraphView : UserControl
{
public ObservableCollection<ChartCollection<long>> signals { get; set; }
public GraphView()
{
UCGraph.DataSource = this.signals;
}
}
and if I set the signals property should it update the Datasource?
MyGraphUC.signals = mySignals;
It doesn't seem to be working for me. Why?
No you can't directly because the variables UCDataGraph.DataSource and signals are not connected by any means. They just happen to point to the same instance after you assign them in your constructor (actually they will both point to null which is not an instance at all). That being said, you can leverage the setter to do your bidding like so:
public partial class GraphView : UserControl
{
private ObservableCollection<ChartCollection<long>> _signals
public ObservableCollection<ChartCollection<long>> signals
{
get
{
return _signals;
}
set
{
this._signals = value;
UCGraph.DataSource = this._signals;
}
}
public GraphView()
{
UCGraph.DataSource = this.signals;
}
}
Alternatively you can just .Clear() the observable collection and refill it with the elements instead of changeing the collection itself if that is an feasible scenario for you.

Runtime datacontext with Castle Windsor

I'm working on adding a Windsor IoC container to an existing WinForms application that uses an MVP UI design pattern. I'm trying to determine a good approach to resgistering a datacontext that depends on a connection string supplied at runtime. The problem is that I cannot create a datacontext until the user selects a database, i.e. a 'connection string' after the application has loaded. Granted only one datacontext is generally used, but sometimes a user need to switch to a different database, i.e. creating a differnet datacontext. This leads to additional runtime dependencies as well.
public interface IProductsView
{
event EventHandler<ProductSelectedEventArgs> ProductSelectedEvent;
event EventHandler<StringEventArgs> ProductStatusEvent;
void ClearProductList();
void DisplayProductList(IList<Product> products);
Control Control { get; }
IProductsPresenter Presenter { get; set; }
}
public class ProductsPresenter : IProductsPresenter
{
public IProductsView View { get; set; }
private IProductRepository Repository { get; set; }
public ProductsPresenter(IProductsView view, IProductRepository repository)
{
View = view;
View.Presenter = this;
Repository = repository;
}
public void ProductSelected(IList<Product> products)
{
throw new NotImplementedException();
}
public void ShowProductList(string name)
{
IList<Product> productList;
if (string.IsNullOrEmpty(name))
productList = Repository.GetProducts();
else
productList = Repository.GetProductsByName(name);
View.DisplayProductList(productList);
}
}
public class ProductDao : IDisposable, IProductRepository
{
private MeasurementDataContext dataContext;
public ProductDao(MeasurementDataContext context)
{
dataContext = context;
}
public List<Product> GetProducts()
{
return dataContext.Products.Select(p => Mapper.Map(p)).ToList().OrderBy(x => x.Name).ToList();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
if (dataContext != null)
{
dataContext.Dispose();
dataContext = null;
}
}
~ProductDao()
{
this.Dispose(false);
}
}
So this means that the Presenter in my View is null until the IProductRepository is created, which in turn depends on creating a MeasurementDataContext. I have these component regisitered in a IWindsorInstaller like so:
container.Register(Component.For<IProductsView>()
.ImplementedBy<ViewProductsControl>());
container.Register(Component.For<IProductsPresenter>()
.ImplementedBy<ProductsPresenter>());
Do I need to use Named and DependsOn which supply a unique name and connectionString argument for each datacontext?
What I currently do to register the data context at runtime after the user has selected a database
kernel.Register(Component.For<MeasurementDataContext>()
.UsingFactoryMethod(() => new MeasurementDataContext(conn)));
and then `Resolve' my Views and set their Presenters. I know this is not good design, but it's a brute force way of resolving my dependcies.
Thanks
UPDATE:
I changed the way I registered my datacontext's in the installer to the following:
container.Register(Component.For<DataContext>().ImplementedBy<MeasurementDataContext>().Named("Localhost").DependsOn(new { connectionString = conn }));
and then modified my model's constructor to:
public ProductDao(DataContext context)
{
dataContext = context as MeasurementDataContext;
}
All components will resolve with the right key:
kernel.Resolve<DataContext>(cbo.SelectedItem.ToString());
What about injecting a wrapper class to hold the connection string and have the datacontext objects use that? Something along these lines:
public class ConnectionStringProvider : IConnectionStringProvider
{
private string _value;
public event EventHandler ConnectionStringChanged;
public string ConnectionString
{
get { return _value; }
set
{
_value = value;
var del = ValueChanged;
if (del != null)
del(this, EventArgs.Empty);
}
}
}
Register this with and singleton lifestyle. This way your application can set or update the connection string on a single object and everyone who depends on it will be notified of the change.

Pros / Cons to Different Binding Approaches using MVVM and RIA Services

I have been building an application, which uses the LoadOperation's Entities to return an IEnumerable which becomes the source of a CollectionViewSource in my View Model. I am now discovering the potential pitfall to this approach, when adding Entities in my Silverlight client, I cannot see these entities, unless I either submit the New Entity, then reload, or Maintain a separate collection of items, which I am binding to.
What I really see as my options are:
Add an ObservableCollection to use as the Source of the CollectionViewSource property in my ViewModel - this way I can add to both the DomainContext and the ObservableCollection at the same time to keep the collections in sync.
Change the Binding to the EntitySet directly, and add a filtering event handler to provide the filtering on the CollectionViewSource.
If anyone has tips or thoughts about pros/cons of each, I would greatly appreciate it. In particular, I am wondering, if there are performance and/or programming benefits in favor of one or the other?
I am taking this one approach at a time. First, I am going to show a point of reference to dicuss this with, then I will highlight the different changes necessary to support each methodology.
The basis for my demo is a single, authenticated domain service which returns a single entity of Resource. I will expose 4 commands (save, undo, add, and delete), plus a Collection, and a Property to hold the SelectedResource.
2 Different classes implement this interface (1 for blending, 1 for production). The production is the only one I will discuss here. Notice the action(lo.Entities) in the GetMyResources function:
public class WorkProvider
{
static WorkContext workContext;
public WorkProvider()
{
if (workContext == null)
workContext = new WorkContext();
}
public void AddResource(Resource resource)
{
workContext.Resources.Add(resource);
}
public void DelResource(Resource resource)
{
workContext.Resources.Remove(resource);
}
public void UndoChanges()
{
workContext.RejectChanges();
}
public void SaveChanges(Action action)
{
workContext.SubmitChanges(so =>
{
if (so.HasError)
// Handle Error
throw so.Error;
else
action();
}, null);
}
public void GetMyResources(Action<IEnumerable<Resource>> action)
{
var query = workContext.GetResourcesQuery()
.Where(r => r.UserName == WebContext.Current.User.Name);
workContext.Load(query, LoadBehavior.MergeIntoCurrent, lo =>
{
if (lo.HasError)
// Handle Error
throw lo.Error;
else
action(lo.Entities);
}, null);
}
}
In the ViewModel, I have the following Implementation:
public class HomeViewModel : ViewModelBase
{
WorkProvider workProvider;
public HomeViewModel()
{
workProvider = new WorkProvider();
}
// _Source is required when returning IEnumerable<T>
ObservableCollection<Resource> _Source;
public CollectionViewSource Resources { get; private set; }
void setupCollections()
{
Resources = new CollectionViewSource();
using (Resources.DeferRefresh())
{
_Source = new ObservableCollection<Resource>();
Resources.Source = _Source;
Resources.GroupDescriptions.Add(new PropertyGroupDescription("Title"));
Resources.SortDescriptions.Add(new SortDescription("Title", ListSortDirection.Ascending));
Resources.SortDescriptions.Add(new SortDescription("Rate", ListSortDirection.Ascending));
}
}
void loadMyResources()
{
workProvider.GetMyResources(results =>
{
using (Resources.DeferRefresh())
{
// This is required when returning IEnumerable<T>
_Source.Clear();
foreach (var result in results)
{
if (!_Source.Contains(result))
_Source.Add(result);
}
}
});
}
Resource _SelectedResource;
public Resource SelectedResource
{
get { return _SelectedResource; }
set
{
if (_SelectedResource != value)
{
_SelectedResource = value;
RaisePropertyChanged("SelectedResource");
}
}
}
public RelayCommand CmdSave { get; private set; }
public RelayCommand CmdUndo { get; private set; }
public RelayCommand CmdAdd { get; private set; }
public RelayCommand CmdDelete { get; private set; }
void setupCommands()
{
CmdSave = new RelayCommand(() =>
{
workProvider.SaveChanges(() =>
{
DispatcherHelper.CheckBeginInvokeOnUI(() =>
{
System.Windows.MessageBox.Show("Saved");
});
});
});
CmdUndo = new RelayCommand(() =>
{
workProvider.UndoChanges();
// This is required when returning IEnumerable<T>
loadMyResources();
});
CmdAdd = new RelayCommand(() =>
{
Resource newResource = new Resource()
{
ResourceID = Guid.NewGuid(),
Rate = 125,
Title = "Staff",
UserName = "jsmith"
};
// This is required when returning IEnumerable<T>
_Source.Add(newResource);
workProvider.AddResource(newResource);
});
CmdDelete = new RelayCommand(() =>
{
// This is required when returning IEnumerable<T>
_Source.Remove(_SelectedResource);
workProvider.DelResource(_SelectedResource);
});
}
}
The alternate method would involve changing the WorkProvider class as follows (notice the action(workContext.Resources) that is returned:
public void GetMyResources(Action<IEnumerable<Resource>> action)
{
var query = workContext.GetResourcesQuery()
.Where(r => r.UserName == WebContext.Current.User.Name);
workContext.Load(query, LoadBehavior.MergeIntoCurrent, lo =>
{
if (lo.HasError)
// Handle Error
throw lo.Error;
else
// Notice Changed Enumeration
action(workContext.Resources);
}, null);
}
And the changes to the viewmodel are as follows (notice the removal of the _Source ObservableCollection):
public class HomeViewModel : ViewModelBase
{
WorkProvider workProvider;
public HomeViewModel()
{
workProvider = new WorkProvider();
}
public CollectionViewSource Resources { get; private set; }
void setupCollections()
{
Resources = new CollectionViewSource();
using (Resources.DeferRefresh())
{
Resources.Filter += (s,a) =>
{
a.Accepted = false;
if (s is Resource)
{
Resource res = s as Resource;
if (res.UserName == WebContext.Current.User.Name)
a.Accepted = true;
}
};
Resources.GroupDescriptions.Add(new PropertyGroupDescription("Title"));
Resources.SortDescriptions.Add(new SortDescription("Title", ListSortDirection.Ascending));
Resources.SortDescriptions.Add(new SortDescription("Rate", ListSortDirection.Ascending));
}
}
void loadMyResources()
{
workProvider.GetMyResources(results =>
{
using (Resources.DeferRefresh())
{
Resources.Source = results;
}
});
}
Resource _SelectedResource;
public Resource SelectedResource
{
get { return _SelectedResource; }
set
{
if (_SelectedResource != value)
{
_SelectedResource = value;
RaisePropertyChanged("SelectedResource");
}
}
}
public RelayCommand CmdSave { get; private set; }
public RelayCommand CmdUndo { get; private set; }
public RelayCommand CmdAdd { get; private set; }
public RelayCommand CmdDelete { get; private set; }
void setupCommands()
{
CmdSave = new RelayCommand(() =>
{
workProvider.SaveChanges(() =>
{
DispatcherHelper.CheckBeginInvokeOnUI(() =>
{
System.Windows.MessageBox.Show("Saved");
});
});
});
CmdUndo = new RelayCommand(() =>
{
workProvider.UndoChanges();
Resources.View.Refresh();
});
CmdAdd = new RelayCommand(() =>
{
Resource newResource = new Resource()
{
ResourceID = Guid.NewGuid(),
Rate = 125,
Title = "Staff",
UserName = "jsmith"
};
workProvider.AddResource(newResource);
});
CmdDelete = new RelayCommand(() =>
{
workProvider.DelResource(_SelectedResource);
});
}
}
While the second approach definately requires adding the filter event handler in the configuration of the CollectionViewSource, and could be seen as filtering data 2 times (1 time at the server, and the second time by the CollectionViewSource), it does off the following benefits: There is a single collection - which makes management of collection notifications simpler and easier. The collection is the actual collection which will be submitted to the server, which makes managing adds/deletes simpler, since there are not opportunities for forgetting to add/remove entities from the correct collection to initiate the add/delete function when submitting back.
The one last thing I need to confirm is the following: On a collectionviewsource, it is my understanding that you should use DeferRefresh() when making multiple changes that affect the view. This just prevents unnecessary refreshes from occuring when internal changes may cause refreshes such as configuring sorting, grouping, etc. It is also important to call .View.Refresh() when we expect the UI to process some update changes. The .View.Refresh() is probably more important to note than the DeferRefresh(), since it actually causes a UI update, as opposed to a prevent unexpected UI updates.
I don't know if this will help others, but I hope so. I definately spent some time working through these and trying to understand this. If you have clarifications or other things to add, please feel free to do so.
Ryan, it might be worth your while to take a look through this post on collection binding (and some of the related ones). Your implementation is certainly a reasonable one, but I can see it wrestles with a few of the issues that have already been resolved at the framework level.

Resources