As described in my earlier post Creating Proxy-Classes for ViewModel’s (MVVM) by using DynamicObject with DynamicObject’s you have the ability to easily extend and override members of you DomainModel for binding purposes without changing it in any way. You can use that approach in ASP.NET MVC to write easy readable templates, without creating @helper’s for ViewModel related data manipulation.
Domain Model
Let me explain that approach by a simple example. Assuming that we have to build a catalog system based on an existing DomainModel where we have some Articles, n corresponding Prices and due to normalization corresponding price PriceCampaigns to label and specify the Prices.
Creating some Objects
Let's use the following mocking for our examples:
var listPriceCampaign = new PriceCampaign() { Name = "list" };
var retailPriceCampaign = new PriceCampaign() { Name = "retail" };
var article = new Article() { OrderNumber = "4202.9", Description = "Doomsday Machine" };
var listPrice = new Price() { Value = 99.99m, Article = article, Campaign = listPriceCampaign };
article.Prices.Add(listPrice);
listPriceCampaign.Prices.Add(listPrice);
var retailPrice = new Price() { Value = 17.99m, Article = article, Campaign = retailPriceCampaign };
article.Prices.Add(retailPrice);
retailPriceCampaign.Prices.Add(retailPrice);
Template Usage
We are now able to produce the following output by using our DomainModel:
OrderNumber: 4202.9
Description: Doomsday Machine
Price: $17.99
ListPrice: $99.99
To map the prices to the Razor template we can do it either the old fashioned way:
@{
// bad code for determining list- and retail price
var listPrice = 0.00m;
var retailPrice = 0.00m;
foreach(var price in Model.Prices)
{
if (price.Campaign.Name == "list")
listPrice = price.Value;
else if (price.Campaign.Name == "retail")
retailPrice = price.Value;
}
}
OrderNumber: @Model.OrderNumber
Description: @Model.Description
Price: $@retailPrice
ListPrice: $@listPrice
Or we can do it by using LINQ:
@{ // good code (still bad approach) for determining list- and retail price }
OrderNumber: @Model.OrderNumber
Description: @Model.Description
Price: $@Model.Prices.Where(p => p.Campaign.Name == "retail").First().Value
ListPrice: $@Model.Prices.Where(p => p.Campaign.Name == "list").First().Value
ViewModel-Proxy
Now let’s create the simplified ViewModel-Proxy by using DynamicObject as described in Creating Proxy-Classes for ViewModel’s (MVVM) by using DynamicObject.
public abstract class BaseViewModelProxy<T> : DynamicObject
{
protected T _domainModel;
private PropertyInfo[] _objectProperties;
private PropertyInfo[] objectProperties
{
get
{
if (objectProperties == null)
this._objectProperties = typeof(T).GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
return this._objectProperties;
}
}
public BaseViewModelProxy(T domainModel)
{
this._domainModel = domainModel;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
var pi = this._objectProperties.FirstOrDefault((p) => p.Name == binder.Name);
if (pi != null)
{
result = this._domainModel != null ? pi.GetValue(this._domainModel, null) : null;
return true;
}
else
return base.TryGetMember(binder, out result);
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
var pi = this._objectProperties.FirstOrDefault((p) => p.Name == binder.Name);
if (pi != null)
{
if (this._domainModel != null)
pi.SetValue(this._domainModel, value, null);
return true;
}
else
return base.TrySetMember(binder, value);
}
}
We also create a BaseCollectionDictionaryModel<T> and BaseModelCollectionDictionaryModel<T, E> that implements IDictionary<string, T> so we are able access collection items by associative indices for collection items in our template.
public abstract class BaseCollectionDictionaryModel<T> : DynamicObject, IDictionary<string, T>, IList<T>
{
private IList<T> _domainModelCollection;
private List<PropertyInfo> _keyFieldPropertyInfoStack;
public BaseCollectionDictionaryModel(IList<T> domainModelCollection, string keyFieldPath)
{
this._domainModelCollection = domainModelCollection;
this._keyFieldPropertyInfoStack = new List<PropertyInfo>();
buildPropertyInfoStack(keyFieldPath, typeof(T));
}
private void buildPropertyInfoStack(string path, Type type)
{
var stack = path.Split(new char[] { '.' });
var pi = type.GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(p => p.Name == stack[0]);
this._keyFieldPropertyInfoStack.Add(pi);
if (stack.Length > 1)
buildPropertyInfoStack(path.Substring(stack[0].Length + 1), pi.PropertyType);
}
public T this[string key]
{
get
{
// we could also use LINQ Dynamic Expressions instead
foreach (T o in this._domainModelCollection)
{
Object r = o;
foreach (PropertyInfo pi in this._keyFieldPropertyInfoStack)
r = pi.GetValue(r, null);
if ((string)r == key)
return o;
}
throw new KeyNotFoundException();
}
set { throw new NotImplementedException(); }
}
// insert some more IDictionary<string, T>, IList<T> implementations here
}
public abstract class BaseModelCollectionDictionaryModel<T, E> : BaseCollectionDictionaryModel<E>, IDictionary<string, T>, IList<T>
{
public BaseModelCollectionDictionaryModel(IList<E> domainModelCollection, string keyFieldPath)
: base(domainModelCollection, keyFieldPath)
{
}
public virtual List<T> getModelCollection(IEnumerable<E> items)
{
throw new NotImplementedException();
}
public virtual T getModel(E item)
{
throw new NotImplementedException();
}
public ICollection<string> Keys
{
get { return base.Keys; }
}
public ICollection<T> Values
{
get { return getModelCollection(base.Values); }
}
public T this[string key]
{
get { return getModel(base[key]); }
set { throw new NotImplementedException(); }
}
// insert some more IDictionary<string, T>, IList<T> implementations here
}
Now to the simple part, the implementation. We've to create some ViewModel classes based on our abstract class BaseViewModelProxy<T>. We also "override" the collection properties so we can use them as IDictionary<string, T> in our template, they will also return our proxified types.
public class ArticleModel : BaseViewModelProxy<Article>
{
public ArticleModel(Article article)
: base(article)
{
}
public PriceModelCollectionDictionaryModel Prices
{
get { return new PriceModelCollectionDictionaryModel(this._domainModel.Prices); }
}
}
public class PriceModel : BaseViewModelProxy<Price>
{
public PriceModel(Price price)
: base(price)
{
}
public ArticleModel Article
{
get { return new ArticleModel(this._domainModel.Article); }
}
public PriceCampaignModel Campaign
{
get { return new PriceCampaignModel(this._domainModel.Campaign); }
}
}
public class PriceCampaignModel : BaseViewModelProxy<PriceCampaign>
{
public PriceCampaignModel(PriceCampaign priceCampaign)
: base(priceCampaign)
{
}
public PriceModelCollectionDictionaryModel Prices
{
get { return new PriceModelCollectionDictionaryModel(this._domainModel.Prices); }
}
}
public class PriceModelCollectionDictionaryModel : BaseModelCollectionDictionaryModel<PriceModel, Price>
{
public PriceModelCollectionDictionaryModel(IList<Price> prices)
: base(prices, "Campaign.Name")
{
}
}
public class PriceCampaignModelCollectionDictionaryModel : BaseModelCollectionDictionaryModel<PriceCampaignModel, PriceCampaign>
{
public PriceCampaignModelCollectionDictionaryModel(IList<PriceCampaign> priceCampaigns)
: base(priceCampaigns, "Name")
{
}
}
New Template Usage
The new template usage is much more simpler and that's how all the hard work pays out:
OrderNumber: @Model.OrderNumber
Description: @Model.Description
Price: $@Model.Prices["retail"].Value
ListPrice: $@Model.Prices["list"].Value