In the previous post we discussed how we can use DynamicObject to add dynamically generated properties to our types. This post would continue the discussion but here we are going to discuss ICustomTypeDescriptor to add dynamically generated properties. We will also discuss how we can use these properties for WPF Binding. In the last part of our discussion we will be adding the missing bit to support change notifications for these properties.
System.ComponentModel has a default implementation of ICustomTypeDescriptor. This provides the interface implementation requirements in the form of virtual methods which can be overridden in your custom type. We will be using this implementation as part of our discussion. Basically the following methods from the interface do all the magic. We can provide implementation of these methods to introduce our custom properties in order to fake the client that the type already has these properties.
This is also more to do with WPF Binding System. If the DataContext implements ICustomTypeDescriptor then WPF Binding system uses the Type Descriptor instead of relying on reflection. From msdn, this discussion is very useful to learn how binding references are resolved.
http://msdn.microsoft.com/en-us/library/bb613546.aspx
Both of the overloads of GetProperties() methods return PropertyDescriptorCollection. It is a collection of PropertyDescriptor. Overriding these methods and adding further items to the collection can give the impression to the WPF Binding system of having these properties by the type itself. Let's provide a descriptor inheriting from PropertyDescriptor.
In the above property descriptor, please look at GetValue and SetValue methods. The type is holding a field called propertyValue. These methods operate on this field. We don't need these methods for working with WPF Binding system and need them to work with the additional properties in our code. Let's use this descriptor to create a Type implementing ICustomTypeDescriptor. We are maintaining an internal collection _myProperties to hold the additional properties which are not provided by the type itself. This would create perception of dynamically generated properties. An additional method, called AddProperty, is provided to add an item to the collection. The method is generic to support a property with any supported type. The type is also implementing INotifyPropertyChanged interface providing change propagation support. This would raise familiar PropertyChanged event when a value is changed using SetValue method.
Now let's use this type and introduce a new type called StudentViewModel. It maintains a property called FirstName. Now here is the intersting part. We are adding another property LastName using AddProperty method of the base type ViewModelBase.
Similarly, we add a new type called MainViewModel. Here also we are adding a property called MainTitle. We are setting the value of this property as "Main App Window". It is maintaining a collection holding a collection of StudentViewModel. Here we are adding members to the collection. We are setting the property of FirstName directly. We are setting the other property LastName using SetPropertyValue method. Another property SelectedStudent is provided to hold the currently selected student.
Now let's use these view models for WPF Binding. We are using MainViewModel as the DataContext of MainWindow. The view mainly has a ListBox and DataGrid to show items from Students collection in the DataContext. There is no difference in the use of FirstName and LastName properties from StudentViewModel even when LastName is being dynamically generated. You can also notice MainTitle being used for App banner.
Since the basic setup is done. Now let's run the application now. We should see the following display:
Supporting Change Notifications
This looks perfect but this has one limitation. This doesn't support change notifications for newly added properties. Since we are using PropertyDescritptor to add dynamic properties, we can easily add this behavior using OnValueChanged from the type. We first need to update the ViewModelBase so that whenever a property is added, we add the ValueChanged to the property descriptor and call OnPropertyChanged from ViewModelBase similary to the existing properties from the inheriting types.
Next we need to add the support in the inheriting PropertyDesriptor type i.e. CustomPropertyDescriptor. This would cause the ValueChanged to be used when SetValue method is called. In turn, PropertyChanged from ViewModelBase would be raised for the same property.
Download
Showing posts with label TypeDescriptor. Show all posts
Showing posts with label TypeDescriptor. Show all posts
Saturday, September 29, 2012
Dynamically Generated Properties using CustomTypeDescriptor for WPF Binding
Sunday, March 18, 2012
Associated Metadata (Buddy) classes in C#
Partial classes allow us to define a class in different parts. At compile time, all these pieces are joined together and a type is generated. Now if we need to add something else to the class definition then we add another part and it is automatically added to type definition. Partial classes allow us to add new members to the same types. It does not directly allow us to be adding further description of already existing members.
Consider our example of WCF Service. It is exposing a list of students. Student type is provided to the client as a DataContract. It has two properties, FirstName and LastName. Both are of string type and decorated with DataMember attribute.
http://www.shujaat.net/2012/03/visual-studio-hosting-wcf-services.html
What if we want to allow the client of this service to be able to provide additional members. That is why we are providing it as a partial class. We might want to further decorate the FirstName and LastName properties with additional attributes. We can then do data validation based on these Data Annotation attributes. This can be done with another feature of .net framework, Associated Metadata classes. Developers also refer to it as Buddy Classes.
Let's add a service reference of InstituteDataService to our console application as follows:
We add a new class to the client. Let's call it StudentMetadata. Here we are just defining additional data annotation attributes to the same property names as the original partial class that we want additional attributes to be added to.
Please remember that if we access the information of PropertyInfo for the Student type, it would still return the same information. This is because this is not part of type definition but separate description of type is added. Let's add the following code:
Please note that it has just considered DataMemberAttribute which is the definition of type from service reference. Framework developers can use the type description for generating proxies as a decoration of these types based on these additional type description attributes.
Download Code:
Consider our example of WCF Service. It is exposing a list of students. Student type is provided to the client as a DataContract. It has two properties, FirstName and LastName. Both are of string type and decorated with DataMember attribute.
namespace Institute.Data { using System.Runtime.Serialization; [DataContract] public partial class Student { [DataMember] public string FirstName { get; set; } [DataMember] public string LastName { get; set; } } }This is the same service as we defined in a previous post. You can find the definition of service here:
http://www.shujaat.net/2012/03/visual-studio-hosting-wcf-services.html
What if we want to allow the client of this service to be able to provide additional members. That is why we are providing it as a partial class. We might want to further decorate the FirstName and LastName properties with additional attributes. We can then do data validation based on these Data Annotation attributes. This can be done with another feature of .net framework, Associated Metadata classes. Developers also refer to it as Buddy Classes.
Let's add a service reference of InstituteDataService to our console application as follows:
We add a new class to the client. Let's call it StudentMetadata. Here we are just defining additional data annotation attributes to the same property names as the original partial class that we want additional attributes to be added to.
public sealed class StudentMetadata { [Required] public string FirstName { get; set; } [StringLength(10)] public string LastName { get; set; } }In order to add these attributes, we need to add System.ComponentModel.DataAnnotations assembly reference. Now we need to map this buddy class [StudenMetadata] to the Student class. We need to add MetadataTypeAttribute to the Student class definition and specifying StudentMetadata as the buddy class as below:
[MetadataType(typeof(StudentMetadata))] public partial class Student { }We also need to add the mapping to the Type Descriptor definitions. This code can be executed at the startup of the client code.
TypeDescriptor.AddProvider( new AssociatedMetadataTypeTypeDescriptionProvider( typeof(Student) ), typeof(Student));Now we just need to see how the description of the properties is affected after this.
foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(typeof(Student))) { Console.WriteLine("*******************************"); Console.WriteLine(string.Format("Property Name: {0}", descriptor.Name)); foreach (Attribute attribute in descriptor.Attributes) { Console.WriteLine(string.Format("Attribute Name: {0}", attribute)); } }The above code iterates through the property descriptors of all the properties of Student type. It then writes all the attributes to the console. This can result in the following output:
Please remember that if we access the information of PropertyInfo for the Student type, it would still return the same information. This is because this is not part of type definition but separate description of type is added. Let's add the following code:
PropertyInfo[] properties = typeof(Student).GetProperties(); foreach (var propertyInfo in properties) { Console.WriteLine("*******************************"); Console.WriteLine("Propery Name: {0}", propertyInfo.Name); foreach (Attribute attrib in propertyInfo.GetCustomAttributes(false)) { Console.WriteLine("Attribute: {0}", attrib); } }This would result in the following output:
Please note that it has just considered DataMemberAttribute which is the definition of type from service reference. Framework developers can use the type description for generating proxies as a decoration of these types based on these additional type description attributes.
Download Code:
Subscribe to:
Posts (Atom)