Designer Serialization


Serialization is a process that translates objects from object form (in memory) into a format that can be persisted (for example, to a file) or transmitted over a network. Subsequently, the object can be reconstituted from the serialized form. The .NET Framework supports several serialization mechanisms, some of which are most appropriate for design-time and others of which are optimized for use at runtime.

In this section, we will be discussing the design-time serialization of activities. This is the kind of serialization that is utilized within design environments (such as Visual Studio) in order to allow the user to manipulate serialized representations of the components being authored. Design environments typically allow the user to manipulate components visually, but authors almost always find it convenient to view or edit the component in some textual format as well.

A component can have any number of different serialized formats including uncompiled code, various forms of XML markup, or any other format. This is depicted in Figure 7.5. By extension then, a WF program (a tree of activities, which are components) also can be serialized into multiple formats. We came across this fact in the earlier section on compilation, where we saw two equivalent WF programsone expressed using C# code and the other written entirely as XAML.

Figure 7.5. Isomorphism of activity tree formats


You are free to develop an entirely custom serialization capability for your activities, which can translate your WF programs to and from a custom serialization format of your choosing. More than likely, you will find the designer serialization infrastructure of the WF programming model sufficient (in both its assets and its extensibility) to meet your needs as an activity writer as it builds on the established patterns and types that are found in the System.ComponentModel.Design.Serialization namespace.

The WF serialization framework includes types that serialize activities to XAML and to code. Essentially, these are the two serialization formats you get for free as an activity writer. As we mentioned, though, it is perfectly reasonable to develop custom serializers for activities that translate to and from formats other than XAML and code. The WF types related to serialization of WF programs and activities are defined in the System.Workflow.ComponentModel.Serialization namespace.

At the outset of the chapter, we saw that the Activity class has three associated designer serializer components:

 [DesignerSerializer(typeof(ActivityMarkupSerializer),   typeof(WorkflowMarkupSerializer))] [DesignerSerializer(typeof(ActivityCodeDomSerializer),   typeof(CodeDomSerializer))] [DesignerSerializer(typeof(ActivityTypeCodeDomSerializer),   typeof(TypeCodeDomSerializer))] public class Activity : DependencyObject {   ... } 


As you can see, the DesignerSerializerAttribute type, which is defined in the System.ComponentModel.Design.Serialization namespace, is used to associate a designer serializer component with an activity. Other facets of the serialization process are also codified by attributes and services present in this .NET Framework namespace.

The constructor of DesignerSerializerAttribute accepts two arguments. The first argument indicates the designer serializer type that is being associated with the activity. The second argument indicates the base type from which the designer serializer inherits and can be thought of as answering the question "To what format does this serializer serialize?"

Thus, ActivityMarkupSerializer is responsible for serializing an activity to XAML, which is the format utilized by WorkflowMarkupSerializer. Both of these types are defined in the System.Workflow.ComponentModel.Serialization namespace.

ActivityCodeDomSerializer and ActivityTypeCodeDomSerializer, in turn, are responsible for serializing an activity to two different forms of CodeDOM, which are defined by CodeDomSerializer and TypeCodeDomSerializer.CodeDomSerializer and TypeCodeDomSerializer are standard .NET Framework serialization components defined in the System.ComponentModel.Design.Serialization namespace.

It should be clear that multiple designer serializers can be associated with an activity. The one restriction that is necessary to avoid ambiguity, though, is that the base type specified for these serializers (the second constructor argument for DesignerSerializerAttribute) must be unique among the serializers associated with the activity. For an entirely new serialization format, you simply need to associate a designer serializer component using a base serialization type that you implement:

 [DesignerSerializer(typeof(WidgetCustomFormatSerializer),   typeof(CustomFormatSerializer))] public class Widget : Activity { ... } 


Code Serialization

TypeCodeDomSerializer serializes a component into a new code type declaration with a well-known method (InitializeComponent) that contains its initialization logic. Because the Activity class is associated with a serializer that derives from TypeCodeDomSerializer, we can write a simple program to illustrate this kind of serialization for a WF program. This is shown in Listing 7.24.

Listing 7.24. Serialization Using TypeCodeDomSerializer

 using System; using System.CodeDom; using System.CodeDom.Compiler; using System.ComponentModel.Design; using System.ComponentModel.Design.Serialization; using System.IO; using System.Text; using System.Workflow.ComponentModel; using System.Workflow.ComponentModel.Serialization; using Microsoft.CSharp; using EssentialWF.Activities; class Program {   static void Main()   {     Sequence seq = new Sequence();     seq.Activities.Add(new ReadLine());     seq.Activities.Add(new WriteLine());     seq.SetValue(WorkflowMarkupSerializer.XClassProperty,       "Sequence1");     SerializeToCode(seq, new CSharpCodeProvider(),       "C:\\wfprogram.cs");   }   static void SerializeToCode(Activity rootActivity,     CodeDomProvider domProvider, string csharpFilePath)   {     CodeCompileUnit ccu = new CodeCompileUnit();     DesignerSerializationManager mgr =       new DesignerSerializationManager(new ServiceContainer());     using (mgr.CreateSession())     {       ActivityCodeDomSerializationManager codeMgr =         new ActivityCodeDomSerializationManager(mgr);       TypeCodeDomSerializer typeCodeDomSerializer =         codeMgr.GetSerializer(rootActivity.GetType(),           typeof(TypeCodeDomSerializer)) as TypeCodeDomSerializer;       ...       CodeTypeDeclaration activityTypeDeclaration =       typeCodeDomSerializer.Serialize(codeMgr, rootActivity,         allActivities);       CodeNamespace activityCodeNamespace = new CodeNamespace();       activityCodeNamespace.Types.Add(activityTypeDeclaration);       ccu.Namespaces.Add(activityCodeNamespace);     }     CodeGeneratorOptions options = new CodeGeneratorOptions();     options.BracingStyle = "C";     Stream temp = new FileStream(csharpFilePath, FileMode.Create,       FileAccess.Write, FileShare.Read);     using (StreamWriter sw = new StreamWriter(temp, Encoding.UTF8))     {       domProvider.GenerateCodeFromCompileUnit(ccu, sw, options);     }   } } 


We must set the WorkflowMarkupSerializer.XClassProperty on the Sequence activity object in order to indicate the namespace-qualified name of the type to be generated. The SerializeToCode method contains standard code for manipulating TypeCodeDomSerializer. The result of running our program is a C# code file:

 //wfprogram.cs //-------------------------------// <auto-generated> //     This code was generated by a tool. //     Runtime Version:2.0.50727.42 // //     Changes to this file may cause incorrect behavior //     and will be lost if the code is regenerated. // </auto-generated> //--------------------------------- public class Sequence1 : Sequence {     private ReadLine readLine1;     private WriteLine writeLine1;     public Sequence1()     {         this.InitializeComponent();     }     private void InitializeComponent()     {         this.CanModifyActivities = true;         this.readLine1 = new ReadLine();         this.writeLine1 = new WriteLine();         //         // readLine1         //         this.readLine1.Name = "readLine1";         //         // writeLine1         //         this.writeLine1.Name = "writeLine1";         //         // Sequence1         //         this.Activities.Add(this.readLine1);         this.Activities.Add(this.writeLine1);         this.Name = "Sequence";         this.CanModifyActivities = false;     } } 


As a custom activity writer, you generally do not need to associate custom CodeDOM serializer components with your activities. For situations where this is necessary, the .NET Framework documentation should be consulted.

XAML Serialization

XAML is a general-purpose markup format for initializing object trees. Consider the following code to initialize an object:

 Album album = new Album(); album.Artist = "Eric Clapton"; album.Title = "Crossroads"; 


The essence of XAML is that the preceding code can be expressed in markup like this:

 <Album Artist="Eric Clapton" Title="Crossroads" /> 


Earlier in the chapter, we saw how the WorkflowMarkSerializer class makes it easy to serialize and deserialize a tree of activity objects. The following code snippet illustrates XAML serialization with a simple example that uses the WriteLine and Sequence activities:

 Sequence seq = new Sequence(); seq.Name = "s1"; WriteLine w1 = new WriteLine(); w1.Name = "w1"; WriteLine w2 = new WriteLine(); w2.Name = "w2"; seq.Activities.Add(w1); seq.Activities.Add(w2); WorkflowMarkupSerializer serializer =   new WorkflowMarkupSerializer(); using (XmlWriter writer = new XmlTextWriter(   new StreamWriter("test.xaml"))) {   serializer.Serialize(writer, seq); } 


The WriteLine and Sequence activities do not define any custom XAML serializers; they inherit these components from Activity and CompositeActivity. Execution of the preceding code results in the following XAML:

 <ns0:Sequence x:Name="s1" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="http://EssentialWF/Activities">   <ns0:WriteLine Text="{x:Null}" x:Name="w1" />   <ns0:WriteLine Text="{x:Null}" x:Name="w2" /> </ns0:Sequence> 


We can easily deserialize the contents of the file test.xaml back into an in-memory activity tree by calling WorkflowMarkupSerializer.Deserialize:

 Sequence seq2 = null; using (XmlReader reader = new XmlTextReader(   new StreamReader("test.xaml"))) {   seq2 = serializer.Deserialize(reader) as Sequence; } 


As with CodeDOM serialization, you generally do not need to associate custom XAML serializer components with your activities. For situations where this is necessary, the .NET Framework documentation should be consulted.

Collection Serialization

Designer serializer components can establish and enforce their own rules and conventions governing serialization to the format they understand. Where possible, though, it is useful to utilize patterns and types already established in the .NET Framework. A good example of this is the way in which collections are serialized.

Properties that are collections are obviously serialized differently in CodeDOM and XAML formats. But the CodeDOM and XAML serializers both utilize, in a consistent way, the DesignerSerializationVisibilityAttribute type (which is defined in the System.ComponentModel namespace). This attribute can be used to control whether, and how, a collection property is serialized. Consider the following activity type:

 public class Widget : Activity {   private ArrayList list1 = new ArrayList();   private IList list2;   [DesignerSerializationVisibility(     DesignerSerializationVisibility.Content)]   public IList List1   {       get { return list1; }   }   [DesignerSerializationVisibility(     DesignerSerializationVisibility.Visible)]   public IList List2   {       get { return list2; }       set { list2 = value; }   } } 


The List1 property is read-only. It returns an internally instantiated ArrayList object that can be manipulated by the user of a Widget. But the List1 property cannot be assigned because it has no property setter. The List2 property has a getter and a setter, so the user of a Widget can (and is expected to) assign a new list in its entirety.

Here is some code that manipulates the two collection properties of a Widget:

  Widget widget = new Widget();  IList list = widget.List1;  list.Add("one");  list.Add("two");  StringCollection sc = new StringCollection();  sc.Add("three");  sc.Add("four");  widget.List2 = sc; 


If we use the WorkflowMarkupSerializer to serialize the activity to XAML, the markup we find illustrates the difference in the way the collections are treated (XML namespaces have been omitted for clarity):

 <Widget>     <Widget.List1>         <String>one</String>         <String>two</String>     </Widget.List1>     <Widget.List2>         <StringCollection>             <String>three</String>             <String>four</String>         </StringCollection>     </Widget.List2> </Widget> 


The items of the first list are represented as subelements of the MyActivity.List1 element. When deserialization of this XAML occurs, each of the strings "one" and "two" will be added to the IList object that is obtained using the List1 getter. Conceptually, the List1 collection has not been serialized; only its contents have been serialized, as indicated by the DesignerSerializationVisibility.Content option with which we attributed the property definition.

For the second list, things are different. The entire StringCollection object is serialized to markup. Upon deserialization, this StringCollection object will be realized, and the value of the List2 property will be set to be this StringCollection. This behavior reflects the fact that the serializer is using the value of the List2 collection property (as indicated by the DesignerSerializationVisibility. Visible option) instead of merely the contents of the collection.

If we choose to instead serialize a MyActivity object to code, the CodeDOM serializer will also use the DesignerSerializationVisibilityAttribute during its serialization to produce code that, for List1, adds items to the object obtained from the property getter and, for List2, assigns a StringCollection object using the property setter. This practice of sharing serialization semantics where possible reduces the proliferation of format-specific attributes on activity classes and their properties.

To summarize this section, the System.Workflow.ComponentModel.Serialization namespace builds upon patterns and types in the .NET Framework to provide serialization of activities to code and to XAML. This infrastructure is extensible, and allows for custom serializer components that translate activity objects to and from custom formats. Though we have focused on some of the details of the XAML and CodeDOM serializers, we would like to stress that these formats are in no way required by the WF programming model (or by the WF runtime which, as we have seen, is format agnostic).

In Chapter 3, we made the point that it is easy to write custom activities that function as domain-specific opcodes in WF programs. Now we can broaden that statement to say that you can express your WF programs in the domain-specific format of your choosing, so long as your activities are associated with serializer components that understand that format.




Essential Windows Workflow Foundation
Essential Windows Workflow Foundation
ISBN: 0321399838
EAN: 2147483647
Year: 2006
Pages: 97

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net