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?
SomeEntity
class 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\$