Below is how I'm solving the problem of converting between data and presentation types, I'd like to know if that's a good way to go about it, and if not, what would be a better way to go about it.
I already had an IViewModel interface:
/// <summary>
/// An interface for a ViewModel.
/// </summary>
public interface IViewModel : INotifyPropertyChanged
{
/// <summary>
/// Notifies listener that the value of the specified property has changed.
/// </summary>
void NotifyPropertyChanged<TProperty>(Expression<Func<TProperty>> property);
/// <summary>
/// Notifies listener that the value of the specified property has changed.
/// </summary>
void NotifyPropertyChanged(string propertyName);
}
So I added an IViewModel<T> interface that extends it:
/// <summary>
/// An interface for a ViewModel that encapsulates an entity type.
/// </summary>
/// <typeparam name="T">The entity interface type.</typeparam>
public interface IViewModel<T> : IViewModel where T : class
{
/// <summary>
/// A method that returns the encapsulated entity interface.
/// </summary>
/// <returns>Returns an interface to the encapsulated entity.</returns>
T ToEntity();
}
Then to facilitate usage, I implemented it in a base class:
/// <summary>
/// Base class to derive ViewModel implementations that encapsulate an Entity type.
/// </summary>
/// <typeparam name="T">The entity type.</typeparam>
public abstract class ViewModelBase<T> : IViewModel<T> where T : class
{
protected readonly T EntityType;
/// <summary>
/// Initializes a new instance of the <see cref="ViewModelBase{T}"/> class.
/// </summary>
/// <param name="entityType">An instance of the entity type to encapsulate.</param>
protected ViewModelBase(T entityType)
{
EntityType = entityType;
ReflectTypeProperties();
}
public T ToEntity()
{
return EntityType;
}
#region INotifyPropertyChanged implementation
/// <summary>
/// Occurs when a property value changes.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Notifies listener that the value of the specified property has changed.
/// </summary>
/// <param name="propertyName">The name of the property to notify about.</param>
public void NotifyPropertyChanged(string propertyName)
{
Action notify;
_propertyNotifications.TryGetValue(propertyName, out notify);
if (notify != null) notify();
}
/// <summary>
/// Notifies listener that the value of the specified property has changed.
/// </summary>
/// <typeparam name="TProperty">The type of the property (inferred).</typeparam>
/// <param name="property">An expression that selects a property, like <c>() => PropertyName</c>.</param>
public void NotifyPropertyChanged<TProperty>(Expression<Func<TProperty>> property)
{
NotifyPropertyChanged(PropertyName(property));
}
private void NotifyPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (PropertyChanged != null) PropertyChanged(sender, e);
}
private IDictionary<string, Action> _propertyNotifications;
/// <summary>
/// Loads the names of all properties of the most derived type into a
/// Dictionary where each entry (property name) points to a delegate that
/// calls <see cref="NotifyPropertyChanged"/> for the corresponding property.
/// </summary>
private void ReflectTypeProperties()
{
var viewModelProperties = GetType().GetProperties().Where(p => p.CanWrite); // uses reflection (slow)
_propertyNotifications = viewModelProperties
.Select(property => new KeyValuePair<string, Action>(property.Name,
() => NotifyPropertyChanged(this, new PropertyChangedEventArgs(property.Name))))
.ToDictionary(kv => kv.Key, kv => kv.Value);
}
/// <summary>
/// Returns the name of a property in a LINQ Expression such as '<code>() => Property</code>'.
/// Used for strongly-typed INotifyPropertyChanged implementation.
/// </summary>
protected static string PropertyName<TProperty>(Expression<Func<TProperty>> property)
{
var lambda = (LambdaExpression)property;
MemberExpression memberExpression;
var body = lambda.Body as UnaryExpression;
if (body == null)
memberExpression = (MemberExpression)lambda.Body;
else
{
var unaryExpression = body;
memberExpression = (MemberExpression)unaryExpression.Operand;
}
return memberExpression.Member.Name;
}
#endregion
}
This leaves me with clean & focused ViewModel classes that only expose what's meant to be displayed, while retaining the knowledge of the precious encapsulated Id:
/// <summary>
/// Encapsulates a <see cref="ISomeEntity"/> implementation for presentation purposes.
/// </summary>
public class SomeEntityViewModel : ViewModelBase<ISomeEntity>, ISelectable, IDeletable
{
/// <summary>
/// Encapsulates specified entity in a presentation type.
/// </summary>
/// <param name="poco">The entity to be encapsulated.</param>
public SomeEntityViewModel(ISomeEntity poco) : base(poco) { }
/// <summary>
/// A short description for the thing.
/// </summary>
public string Description
{
get { return EntityType.Description; }
set { EntityType.Description = value; NotifyPropertyChanged(() => Description); }
}
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set { _isSelected = value; NotifyPropertyChanged(() => IsSelected); }
}
private bool _isDeleted;
public bool IsDeleted
{
get { return _isDeleted; }
set { _isDeleted = value; NotifyPropertyChanged(() => IsDeleted); }
}
}
Bonus question: is my implementation of INotifyPropertyChanged overkill?
SomeEntityclass and that CR guidelines are against such "placeholders", however the review I'm requesting is more about the base class and the structure of it all - it just might be any entity in my project, which one it is that I'm showing is perfectly irrelevant... \$\endgroup\$