Friday, 13 January 2012

Advanced Proxy-Classes for Razor-Templating by using DynamicObject

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.

DomainModel

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
posted on Friday, 13 January 2012 16:24:12 (GMT Standard Time, UTC+00:00)  #    Comments [0]
Tuesday, 04 October 2011

Creating Proxy-Classes for ViewModel’s (MVVM) by using DynamicObject

The Model-View-ViewModel (MVVM) design pattern is a common approach when it comes to WPF, Silverlight and ASP.NET MVC development. The separation of DomainModel, View and ViewModel removes virtually all “code-behind” from the View layer. That makes the application highly modular. If you have some experience in creating multilayer applications and creating database layers, the benefits of decoupling Model and View should be obvious. However, the benefits of creating a ViewModel layer might require some explanation.

The ViewModel

The ViewModel provides a view-specific representation of the data. So it’s a subset or transformation of the DomainModel. It is a “Model made for a View” - a common approach is to create one ViewModel for every particular View and keep them distinct from the DomainModel types. In most cases that goes with the principle that the View dictates the design of the ViewModel and to pass only required and optimal formatted information's to the View.

Let’s say we have the following DomainModel, containing information's about an Article we get from our database:

public class Article
{
  public int Oid { get; set; }
  public string Articlenumber { get; set; }
  public string Description { get; set; }
  public decimal Price { get; set; }
  public DateTime PublicationDate { get; set; }
}

Furthermore we want to display detail information’s about an Article inside an ArticleDetail view, so we have to create a ViewModel to pass:

public class ArticleDetailModel
{
  public string Articlenumber { get; set; }
  public string Description { get; set; }
  public string Price { get; set; }
  public string PublicationDate { get; set; }
}

The ViewModel contains all information’s we want to display inside of the ArticleDetail view. In addition the data provided is already formatted (Price, PublicationDate) to fit the needs of the view.

That's how the instantiation of the ViewModel may appear in some kind of spaghetti code:

ArticleDetailModel m = new ArticleDetailModel();

m.Articlenumber = article.Articlenumber;
m.Description = article.Description;
m.Price = article.Price.ToString(CultureInfo.CurrentUICulture);
m.PublicationDate = article.PublicationDate.ToString(CultureInfo.CurrentUICulture);

If we now choose to add an ArticleEdit view for editing an existing Article, we have to create a new ViewModel, most likely including the “Oid” property as reference to the record.

Proxy Properties

If you might doubt, that approach results in having many different ViewModel’s for every View we create and many lines of code to handle the instantiation of the ViewModel’s and the formatting of data passed.

There are different approaches to make that procedure more elegant. A solution might be the AutoMapper library, that provides the functionality to map the DomainModel to the ViewModel.

Another approach is the implementation of proxy behavior for each property provided by the ViewModel. Proxy properties usually looks like this:

public string Price
{
  get { return this._domainModel.Price.ToString(CultureInfo.CurrentUICulture); }
  set { this._domainModel.Price = decimal.Parse(value); }
}

At this point I suggest you to take a look at the DynamicProxy library of the Castle Project. It provides functionality for generating .NET proxies on the fly at runtime and may fit your requirements in that case too.

DynamicObject Proxy

My solution of implementing the ability of proxy properties is to use the dynamic objects of the .NET Framework 4.0. DynamicObject’s allow you to determine at run time how to handle property requests and let you “extend” your DomainModel that way. With the power of dynamic objects you are also able to “route” all existing properties to the underlying DomainModel, so you may only have to touch the ones that require explicit formatting. You will be also able to flatten existing relations inside of your DomainModel by using this approach.

The implementation of the DynamicObject Proxy could look like this:

public abstract class ViewModelProxy<T> : DynamicObject, INotifyPropertyChanged
{
  public event PropertyChangedEventHandler  PropertyChanged;

  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 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);
    
      if(PropertyChanged != null)
      PropertyChanged(this, new PropertyChangedEventArgs(binder.Name);

      return true;
    }
    else
      return base.TrySetMember(binder, value);
  }
}

All ViewModel’s are now able to inherit the ViewModelProxy<T> class to provide access to all properties of our DomainModel type. We are also able to implement new properties, or “override” existing ones.

That’s how the example ArticleDetailModel would look like:

public class ArticleDetailModel : ViewModelProxy<Article>
{
  public ArticleDetailModel(Article article)
  {
    this._domainModel = article;
  }

  public string Price
  {
    get { return this._domainModel.Price.ToString(CultureInfo.CurrentUICulture); }
    set { this._domainModel.Price = decimal.Parse(value); }
  }
  public string PublicationDate
  {
    get { return this._domainModel.PublicationDate.ToString(CultureInfo.CurrentUICulture); }
    set { this._domainModel.PublicationDate = DateTime.Parse(value); }
  }
}
posted on Tuesday, 04 October 2011 00:04:06 (GMT Daylight Time, UTC+01:00)  #    Comments [0]