In last post, you saw how to use the ExpandoObject. We will extend this now with the DynamicObject class. The difference between the ExpandoObject and the DynamicObject is that the first one gives you the ability to create dynamic types on the fly, with custom added properties, methods, ... for each object you create. The DynamicObject class however gives you the opportunity to extend the basic behaviour(s) you want your dynamic objects to have. You can also add some logic for adding dynamic behaviour.
You create your own dynamic objects by inheriting from DynamicObject. You can add extra methods to this class and you can override existing methods of the base class. it is by overriding the existing methods that you actually give your objects dynamic behaviour.
I will give you a practical example of this. A while back a colleague of mine was writing a Silverlight dashboard application on top of a TFS server, using the TFS APIs. This implied writing a WCF service the Silverlight application could use for quering the TFS server. Now, while the TFS APIs are quite extensive, they don't implement the ISerializable interface for all data type objects.
Facing this problem, my colleague started writing DTO classes for all of these types that weren't serializable. But I started thinking if it wasn't possible to just leverage the intrinsic capabilities of dynamic objects. After all, the only thing needed for these TFS objects to be send from and to the service were methods to serialize them to and from XML. And why not make these objects dynamic on the client side, hence excluding the need for referencing or redefining all these TFS objects on the server and client side.
First thing I needed for this, was a way to get any object on the server side serialized to XML. For this I will use the DynamicObject class. I want to be able to use my DynamicObject the same way I used the ExpandoObject class, by adding properties as needed. The DynamicObject for this has two important methods an inheriting class can override. These methods are TryGetMember and TrySetMember. The TryGetMember method gets called whenever you are accessing a property or method of your DynamicObject. The TrySetMember method gets called whenever you want to assign a value to a property or method of your DynamicObject.
I will explain this in more detail for you in a bit. I will start by getting information from our TFS server. I use a simple query and a couple of foreach loops to get information about all workitems in a certain project.
TeamFoundationServer tfsServer =
new TeamFoundationServer("http://serverurl");
WorkItemStore store =
Once I have a WorkItem from TFS, I want to be able to build a new DynamicObject, using it the same way as an ExpandoObject, adding properties as I go along.(WorkItemStore) tfsServer.GetService(typeof (WorkItemStore)); foreach (Project project in store.Projects) { foreach (WorkItemType wit in project.WorkItemTypes) { WorkItem theItem = new WorkItem(wit); //dynamic code coming up } }
dynamic dyn = new DynamicElement(theItem.ToString()); dyn.ChangedDate = theItem.ChangedDate.ToString(); dyn.ChangedBy = theItem.ChangedBy; dyn.Description = theItem.Description;
The DynamicElement class is the class I have inherit from DynamicObject. It is this class that contains the TryGetMember and TrySetMember overrides. Every time I dynamically add a property to my DynamicElement object (like I do for ChangedDate, ChangedBy and Desciption in the example above), the TrySetMember override gets called. In this override I create a sort of dictionary of all the property names and property values the user of DynamicElement has thus far added. This is actually quite similar to what an ExpandoObject does out of the box. The difference is that I can now choose how I store these dynamically added properties and instead of using a dictionary, I use an XElement. I know I have to be able to serialize the entire object to XML, so better make my life simple.
Here you see part of the DynamicElement class that inherits from DynamicObject.
You can see I use an XElement datamember, which I use to build the XML to return from a WCF service call (I didn't make the datamember private, to keep the example short, I need access to it, later on in the example, you should never do this in real life). I also have a constructor overload I can use to set the root name of the XElement. The TrySetMember does nothing more than building the XML. The SetMemberBinder parameter can be used to see which property (or method) names the user of your DynamicElement used (in this example ChangedDate, ChangedBy and Desciption). Once the XML is build, the ToString method can be used to serialize the entire object to XML.public class DynamicElement : DynamicObject { public XElement actualElement; public DynamicElement() { } public DynamicElement(XName name) { this.actualElement = new XElement(name); } public override bool TrySetMember(SetMemberBinder binder, object value) { string name = binder.Name; if (actualElement == null) { actualElement = new XElement(binder.Name); return true; } actualElement.Add(new XElement(name, value)); return true; } public override string ToString() { return actualElement.ToString(); //actualElement.Value; } }
In using this DynamicElement for my WorkItems, I also want to add project info to them:
dynamic dyn = new DynamicElement(theItem.ToString()); dyn.ChangedDate = theItem.ChangedDate.ToString(); dyn.ChangedBy = theItem.ChangedBy; dyn.Description = theItem.Description; dynamic dynProj = new DynamicElement("Project"); dynProj.Name = theItem.Project.Name; dynProj.Id = theItem.Project.Id; dyn.Project = dynProj.actualElement.Descendants(); return dyn.ToString();
For the project I use a second DynamicElement. Once the project element is build, I add the entire XML tree to a new Project property of the dynamic workitem object. Eventually I call the ToString method which serializes the object to XML. This way of working makes my service methods quite simple.
[ServiceContract] public interface IService1 { [OperationContract] string GetWorkItem(); }
So far for the server side of things. We still need a client that can parse the generated XML. For this, again, I will use a DynaimcElement (actually the same one as above, but with extra code added). First of all the incoming XML needs to be understood. A simple solution for this is adding an extra constructor to our DynamicElement class that takes a XElement as a parameter (being the entire XML tree).
public DynamicElement(XElement actualElement) { this.actualElement = actualElement; }
Every time now the code wants the value of one of the properties, the TryGetMember override of DynamicObject gets called, so lets add this.
public override bool TryGetMember(GetMemberBinder binder, out object result) { string name = binder.Name; var elements = actualElement.Elements(name); int numElements = elements.Count(); if (numElements == 0) return base.TryGetMember(binder, out result); if (numElements == 1) { result = new DynamicElement(elements.First()); return true; } return false; }
The code again uses the Name property of the GetMemberBinder, which gives us the name of the property we are trying to get the value of. Next we look for an element with this name in the XML tree. If we can't find this element, maybe the base class can find it (which it probably won't). If we find 1 element with this name, we return it as a DynamicElement. This way we can use the ToString method on it, which will return the Value of this element, and we can also dot further into subproperties of the object (eg. for the Project property of a workitem). We can now write source code like this:
I can also add code to the service to return some random XML. I added another service method for this.TFSService.Service1Client client = new TFSService.Service1Client(); string result = client.GetWorkItem(); dynamic workitem = new DynamicElement(XElement.Parse(result)); Console.WriteLine(workitem.ChangedDate); Console.WriteLine(workitem.ChangedBy); Console.WriteLine(workitem.Description); dynamic project = workitem.Project; Console.WriteLine(project.Name);
public string GetSomeXml()
{
return "<test><child1>firstchild</child1>
<child2>secondchild</child2><child3>
<sub1>firstsub</sub1><sub2>decondsub</sub2>
The client can also print out the values of this XML:</child3></test>"; }
This way it is quite easy to build objects on both client and server without the need to provide classes for each one of them. You just take the 'one time effort' to write up a DynamicObject class and you are done.result = client.GetSomeXml(); dynamic someXml = new DynamicElement(XElement.Parse(result)); Console.WriteLine(someXml.child1); Console.WriteLine(someXml.child2); Console.WriteLine(someXml.child3); Console.WriteLine(someXml.child3.sub1); Console.WriteLine(someXml.child3.sub2);
What I still want to test with this code, is whether it is possible to get this code working together with a tool like AutoMapper (on the server side). It is just a bit cumbersome to copy over all the values of your dynamic objects.
At least I hope this gives you an idea of a practical use of dynamic objects in .Net. It is not the most obvious example and I expect most uses of dynamic lie in working together with COM and JavaScript for SilverLight applications. Other than that I haven't seen many practical examples popping up on the Internet (except for this one, of course).
Great post and a nice example of a valid usage of dynamic code outside the "classics". Although I shiver with the idea on turning a datetime into a string outside your "serializer" (cfr ChangedDate). But then again it would unnecessarily spice the demo code in here.
ReplyDeleteYes, I know, but then again, it is a quick and dirty example. There are some other things in there I am not entirely happy with, and that I would clean up in real life production code.
ReplyDeleteThx for liking, btw :)