Click here to Skip to main content
15,867,453 members
Articles / Desktop Programming / WPF

Catel - Part 1 of n: Data handling the way it should

Rate me:
Please Sign up or sign in to vote.
4.64/5 (21 votes)
25 Nov 2010CPOL20 min read 70.9K   923   56   9
Catel is not just another Extension Methods library, nor just an MVVM-framework, but it is a combination of basic data handling, useful controls, and an MVVM-framework.

Catel is a brand new framework (or enterprise library, just use whatever you like) with data handling, diagnostics, logging, WPF controls, and an MVVM Framework. So, Catel is more than "just" another MVVM Framework or some nice Extension Methods that can be used. It's more like a library that you want to include in all the (WPF) applications you are going to develop in the near future.

This article will explain the data handling of the framework.

Article browser

Table of contents

  1. Introduction
  2. Features
  3. Background
  4. Using the classes
  5. What else is possible with these classes?
  6. History

1. Introduction

Welcome to the introduction of Catel. Catel is a brand new framework (or enterprise library, just use whatever you like) with data handling, diagnostics, logging, WPF controls, and an MVVM-framework. So, Catel is more than "just" another MVVM-framework or some nice Extension Methods that can be used. It's more like a library that you want to include in all the (WPF) applications you are going to develop in the near future.

The framework is developed using C# (.NET Framework 3.5 SP1).

There will be a series of articles that will explain the several features of Catel in detail. Below is an overview of the articles:

  1. Catel (1/n): Data handling the way it should
  2. Catel (2/n): Using WPF controls and themes
  3. Catel (3/n): The MVVM framework
  4. Catel (4/n): Unit Testing with Catel
  5. Catel (5/n): Building a WPF example application with Catel in 1 hour

It's important to realize that Catel is not just another Extension Methods library, nor only an MVVM-framework, but it is a combination of basic data handling, useful controls, and an MVVM-framework.

1.1. Why another framework?

You might be thinking: why another framework, there are literally thousands of them out there. Well, first of all, thousands of them is quite a lot, let's just say there are hundreds of them. A few years ago, the lead developer of Catel was using serialization to serialize data from/to disk. But, as he noticed, he had to take care of different versions after every release. After every release, he had to take care of the serialization and backwards compatibility. Also, he had to implement some very basic interfaces (such as INotifyPropertyChanged) for every data object. Then, he decided to write a base class for data handling which can take care of different versions and serialization by itself, and implements the most basic interfaces of the .NET Framework out of the box. The article was published on CodeProject as DataObjectBase.

Then, he was working on a WPF project with five other developers and needed an MVVM-framework since the application was not using MVVM at the moment. Writing an MVVM-framework was no option because there were so many other frameworks out there. But, after looking at some Open-Source MVVM-frameworks (such as the excellent Cinch framework, which was the best one we could find), none of them seemed to be a real option. Creating the View Models was too much work, and the View Models still contained lots of repetitive code in, for example, the property definitions. After taking a closer look at the source code of Cinch and other frameworks, the lead developer thought: if we use the DataObjectBase published before as the base for a View Model class, it should be possible to create a framework in a very short amount of time.

Then, all other developers of the team he was working on the project got enthusiastic, and then the whole team decided to merge their personal libraries into one big enterprise library, and Catel was born.

1.2. Why use this framework?

Before reading any further, it's important to know why you should use the framework. Below are a few reasons why Catel might be interesting for you:

  • Catel is Open-Source. This way, you can customize it any way you want. If you want a new feature request, and the team does not respond fast enough, you can simply implement it yourself.
  • The codebase for Catel is available on CodePlex. This way, you have the option to either download the latest stable release, or live on the edge by downloading the latest source code.
  • Catel uses unit tests to make sure that new updates do not break existing functionality.
  • Catel is very well documented. Every method and property has comments, and the comments are available in a separate reference help file. There is also a lot of documentation available on CodePlex, and in the future, in-depth articles will be written.
  • Catel is developed by a group of talented software developers, and is heavily under development. This is a great advantage because the knowledge is not at just one person, but at a whole group. The developers of Catel all have more than three years of development experience with WPF, and are using Catel in real life applications for at least a year.

1.3. Basics of the framework

At the moment of writing, the framework consists of two projects:

  • Catel.Core - This is the core of the framework, and contains the most important class of Catel: DataObjectBase. Most of the data handling and MVVM-framework fully rely on this class. The core also contains Extension Methods, and features such as diagnostics, Reflection, logging, and more.
  • Catel.Windows - This is the WPF part of Catel. It includes Extension Methods, WPF controls, and most importantly, the MVVM-framework.

The goal of the framework is to minimize the repetitive writing of boring code and to concentrate on the actual (functional) development of an application. By using Catel, the development time of an application can be reduced by at least 50%, but if you become more experienced with Catel, the effect might be even better. As an example, writing a window with an OK and Cancel button can be accomplished by simply using the DataWindow that ships with Catel. The DataWindow also includes validation automatically. This way, you can immediately start focusing on the actual behavior of the window instead of the OK and Cancel buttons and the data validation.

In part 4 of this article series, an example application will be written using most parts of Catel.

1.4. What is to be expected in the near future?

The team is currently polishing the code of the core and the WPF library. As soon as it is stable, version 1.0 will be released (to be expected before the end of the year). Then, the following points/ideas might be realized:

  • Add web (MVC) library - Some members of the team are also experienced in developing MVC applications. In the near future, their personal library will be documented and tested, and then made public as part of Catel as well.
  • Add more WPF controls and themes - As time flies by, new controls will be introduced. There are also plans to add new color schemes to support several themes for Catel out of the box.
  • Add full support for Silverlight - At the moment, support for Silverlight is not a top-priority (however, the MVVM-framework should be fully compatible). When version 1.0 is released, the team will look into the support for Silverlight in detail.

1.5. Data handling

One of the problems every developer faces is data persistence, and that’s what the first article is all about. Most software that is written requires the ability to save objects to disk, or serialize them (binary or XML) to use .NET Remoting or Web Services. The .NET Framework supports a lot of interfaces to implement this:

But, you also want to be notified when a property of the data object changes, and now that you are writing this piece of software, you decide to implement data validation as well. The following .NET interfaces are required:

What happens is that you need to write a lot of (redundant) code to support all these interfaces for all your data classes. Then you haven't even thought of versioning in binary serialized objects that are really difficult to deal with. Also, when (de)serializing objects, you will find yourself writing a lot of custom (repetitive) code which actually does the same things and is very hard to maintain.

Catel has the solution to all the problems described below: the DataObjectBase class.

2. Features

The DataObjectBase class is a generic base class that can be used for all your data classes.

  • Fully serializable - It is now really easy to store objects on disk or serialize them into memory, either binary or in XML. The data object supports this out of the box, and automatically handles the (de)serialization.
  • Support property changed notifications - The class supports the INotifyPropertyChanging and INotifyPropertyChanged interfaces so this class can easily be used in WPF and MVC applications to reflect changes to the user.
  • Backwards compatibility - When serializing your objects to binary, it is hard to maintain the right versions. When you add a new property to a binary class, or change a namespace, the object cannot be loaded any longer. The data object base takes care of this issue and supports backwards compatibility.
  • Validation - The class implements the IDataErrorInfo interface so it is possible to validate the data object and check the errors. This way, no custom validation code needs to be written outside the data class.
  • Backup & revert - The class implements the IEditableObject interface which makes it possible to create a state of the object. Then all properties can be edited, and finally, the changes can be applied or cancelled.

3. Background

If you are not interested in the background and how the DataObjectBase works internally, you should skip this part of the article.

3.1. DataObjectBase

The DataObjectBase class is the most important class of Catel. The class is pretty complicated, but the basics are explained in this article. The class itself only has properties that are required for the basics of the class. The actual properties of deriving classes are stored in an internal dictionary. These properties can be accessed from derived classes by using the SetValue and GetValue methods. These methods internally access the dictionary of properties, but also take care of the events that should occur during a property change such as the PropertyChanging event, the PropertyChanged event, and validation. If the DataObjectBase contains properties that support the INotifyPropertyChanged interface, it will automatically subscribe to these objects. This way, derived objects will be notified in case a registered property changes as well.

Properties can be registered by using one of the static RegisterProperty methods. The method returns a PropertyData object which contains the information about the property, such as the name, the type, and the default value. Then, when the class is constructed, it uses Reflection to find all the PropertyData objects. This way, it knows what properties are registered on the class and registers the type on the PropertyDataManager (more on this later).

Validation is supported out of the box. It was a deliberate choice not to use attributes for validation (lots of frameworks use attributes to validate properties), because error and warning messages cannot be localized via satellite assemblies. Also, most frameworks support a few basic attributes, but when a more complex business rule comes into play, the attributes won't be enough and the user still has to implement custom validation. Therefore, the DataObjectBase supports two important methods:

  • ValidateFields - Field rules are rules for specific fields. An example is that a person cannot have a negative age.
  • ValidateBusinessRules - Business rules are rules that span multiple properties. An example is that a red car (Color property) older than 10 years (Age property) cannot be sold. None of the fields themselves are valid, but the combination is.

Cloning of objects is supported out of the box by DataObjectBase by the implementation of the IClonable interface. When the Clone method is called, a deep copy is created. This means that the class does not simply make a copy of the first-level properties, and thus still holds a reference to the child objects. The DataObjectBase serializes the current state of the object and then deserializes the state into a new object. This way, a completely new object is created without any references to the original object.

The DataObjectBase class also implements the IEditableObject interface. When a call to BeginEdit is invoked, the class serializes the current state of the object into memory and holds the state in an internal property. Then, when the user wants to cancel the editing of the properties, the old values are restored in the internal dictionary that holds the actual values of the properties.

Last but not least, the class also implements the IDisposable interface. When the object is disposed, it cleans up any references that it holds to make sure it causes no memory leaks. Derived classes get the chance to clean up unmanaged memory as well.

3.2. Support classes

There are several support classes that are used by the DataObjectBase class. These will not be explained in detail, but they are still important enough to be noticed.

  • PropertyData - The PropertyData class contains information about a property, and can only be constructed by a call to the protected RegisterProperty method. This class is used to get default values, type information, and registered callback methods of a property.
  • PropertyDataManager - The PropertyDataManager class is responsible for the registration of the properties per type. Besides the actual registration of the types, it is also responsible for holding the XML mappings for all the properties.

3.3. SavableDataObjectBase

SavableDataObjectBase is a class that derives from the DataObjectBase class. The class diagram below shows the extensions that the SavableDataObjectBase provides:

image003.jpg

The class diagram makes clear that the SavableDataObjectBase is capable of saving itself from/to disk and memory. The class is serializable in two modes:

  • Binary - For binary serialization, the class relies on the BinaryFormatter class. By default, the binary formatter of the .NET Framework will break if the version of the assembly changes (even when the type itself does not change). This is due to the fact that during binary serialization, the version number of the assembly is stored as well. And, at deserialization time, the type cannot be found (since the version has changed) and the deserialization will fail.

    To solve the version changes issue, one of the Load method overrides accepts the enableRedirects parameter. When redirects are enabled, the class will create a custom SerializationBinder to strip the version from the type descriptors. It then tries to load the type (which should succeed if the class is located in the assembly). When a type is completely moved to another assembly, the RedirectTypeAttribute can be used. This way, the RedirectSerializationBinder will use the new assembly and type name to redirect an old-style type found in the serialized data to a new type which is available in the latest version.

  • Xml - For XML serialization, the class implements the IXmlSerializable interface. The reason that the class does not rely on the default XmlSerializer class is due to the fact that the default serializer included lots of trash, such as unwanted namespaces. The IXmlSerializable implementation still internally uses the XmlSerializer class, but makes sure that the XML output is correctly formatted.

4. Using the classes

First of all, it is very important to realize that you shouldn't bore yourself with writing all the code below yourself. Catel contains lots of code snippets that allow you to create data objects very easily in a short amount of time.

4.1. Creating your first data object

Explanation

This example shows the simplest way to declare a data object using the DataObjectBase class. By using a code snippet, the class is created in just 10 seconds.

Code snippets

  • dataobject - Declares a data object based on the DataObjectBase class

Steps

  1. Create a new class file called FirstDataObject.cs.
  2. Inside the namespace, use the dataobject codesnippet and fill in the name of the class, in this case FirstDataObject.

Code

C#
/// <summary> 
/// FirstDataObject Data object class which fully supports serialization, 
/// property changed notifications,
/// backwards compatibility and error checking.
/// </summary> 

[Serializable]
public class FirstDataObject : DataObjectBase<FirstDataObject>
{
    #region Variables
    #endregion

    #region Constructor & destructor
    /// <summary> 
    /// Initializes a new object from scratch.
    /// </summary> 
    public FirstDataObject()
    { }

    /// <summary>
    /// Initializes a new object based on <see cref="SerializationInfo"/>.
    /// </summary> 
    /// <param name="info"><see cref="SerializationInfo"/> that contains the information.
    /// </param> 
    /// <param name="context"><see cref="StreamingContext"/>.</param> 
    protected FirstDataObject(SerializationInfo info, StreamingContext context)
        : base(info, context) { }
    #endregion 

    #region Properties
    // TODO: Define your custom properties here using the propdata code snippet
    #endregion 

    #region Methods
    /// <summary> 
    /// Validates the fields.
    /// </summary> 
    protected override void ValidateFields()
    {
        // TODO: Implement any field validation of this object. 
        // Simply set any error by using the SetFieldError method
    }

    /// <summary> 
    /// Validates the business rules.
    /// </summary> 
    protected override void ValidateBusinessRules()
    {
        // TODO: Implement any business rules of this object. 
        // Simply set any error by using the SetBusinessRuleError method
    }
    #endregion 
}

4.2. Declaring properties

4.2.1. Simple property

Explanation

This example shows how to declare the simplest property. In this example, a string property with a default value will be declared with the use of a code snippet.

Code snippets

  • dataprop - Declares a simple property on a data object

Steps

  1. Open FirstDataObject.cs created in the previous example.
  2. In the Properties region, use the code snippet dataprop, and use the following values:
Code snippet item Value

description

Gets or sets the simple property

type string
name SimpleProperty

defaultvalue

“Simple property”

Code

C#
/// <summary> 
/// Gets or sets the simple property.
/// </summary> 
public string SimpleProperty
{
    get { return GetValue<string>(SimplePropertyProperty); }
    set { SetValue(SimplePropertyProperty, value); }
}

/// <summary> 
/// Register the SimpleProperty property so it is known in the class.
/// </summary> 
public static readonly PropertyDataSimplePropertyProperty = 
    RegisterProperty("SimpleProperty", typeof(string), "Simple property");

4.2.2. Property with property changed callback

Explanation

Sometimes you need to know when a property has changed. You can do this by overriding the OnPropertyChanged method and checking if the specific property has changed, but it’s even simpler to register a callback that is only invoked when that specific property has changed.

Code snippets

  • datapropchanged - Declares a simple property on a data object with a property changed callback

Steps

  1. Open FirstDataObject.cs created in a previous example.
  2. In the Properties region, use the code snippet datapropchanged, and use the following values:
Code snippet item Value
description Gets or sets the callback property
type string
name CallbackProperty
defaultvalue “Callback property”

Code

C#
/// <summary> 
/// Gets or sets the callback property.
/// </summary> 
public string CallbackProperty
{
    get { return GetValue<string>(CallbackPropertyProperty); }
    set { SetValue(CallbackPropertyProperty, value); }
}

/// <summary> 
/// Register the CallbackProperty property so it is known in the class.
/// </summary> 
public static readonly PropertyDataCallbackPropertyProperty = 
    RegisterProperty("CallbackProperty", typeof(string), "Callback property", 
    (sender, e) => ((FirstDataObject)sender).OnCallbackPropertyChanged());

/// <summary> 
/// Called when the CallbackProperty property has changed.
/// </summary> 
private void OnCallbackPropertyChanged()
{
    // TODO: Implement logic
}

4.3. Adding validation

Explanation

This example shows how to use the integrated validation of the DataObjectBase class. It creates a new object, declares two different properties to show both the warning and error types, and shows how to validate. This example does not include business rule validation, but it can be used exactly the same.

A field error is mapped to the IDataErrorInfo.Item property, a business error is mapped to the IDataErrorInfo.Error property.

Code snippets

  • dataobject - Declares a data object based on the DataObjectBase class
  • dataprop - Declares a simple property on a data object

Steps

  1. Create a new class file called ValidatingObject.cs.
  2. Inside the namespace, use the dataobject codesnippet and fill in the name of the class, in this case ValidatingObject.
  3. In the Properties region, use the code snippet dataprop, and use the following values:
    Code snippet item Value
    description Gets or sets the field warning property
    type string
    name FieldWarning
    defaultvalue “Invalid field value”
  4. In the Properties region, use the code snippet dataprop, and use the following values:
    Code snippet item Value
    description Gets or sets the field error property
    type string
    name FieldError
    defaultvalue“Invalid field value”
  5. Now that all properties are declared, it’s time to validate the fields. Add the following code to the body of the ValidateFields method:
    C#
    // Check warnings
    if (FieldWarning == "Invalid field value")
    {
        SetFieldWarning(FieldWarningProperty, 
            "Property 'FieldWarning' is probably wrong");
    }
    
    // Check errors
    if (FieldError == "Invalid field value")
    {
        SetFieldError(FieldErrorProperty, "Property 'FieldError' is wrong");
    }

Code

C#
/// <summary> 
/// ValidatingObject Data object class which fully supports serialization, 
/// property changed notifications,
/// backwards compatibility and error checking.
/// </summary> 

[Serializable]
public class ValidatingObject : DataObjectBase<ValidatingObject>
{
    #region Variables
    #endregion 

    #region Constructor & destructor
    /// <summary> 
    /// Initializes a new object from scratch.
    /// </summary> 
    public ValidatingObject()
    { }

    /// <summary> 
    /// Initializes a new object based on <see cref="SerializationInfo"/>.
    /// </summary> 
    /// <param name="info"><see cref="SerializationInfo"/> 
    /// that contains the information.</param> 
    /// <param name="context"><see cref="StreamingContext"/>.</param> 
    protected ValidatingObject(SerializationInfo info, StreamingContext context)
        : base(info, context) { }
    #endregion 

    #region Properties
    /// <summary> 
    /// Gets or sets the field warning property.
    /// </summary> 
    public string FieldWarning
    {
        get { return GetValue<string>(FieldWarningProperty); }
        set { SetValue(FieldWarningProperty, value); }
    }

    /// <summary> 
    /// Register the FieldWarning property so it is known in the class.
    /// </summary> 
    public static readonly PropertyDataFieldWarningProperty = 
           RegisterProperty("FieldWarning", 
           typeof(string), "Invalid field value");
 
    /// <summary> 
    /// Gets or sets the field error property.
    /// </summary> 
    public string FieldError
    {
        get { return GetValue<string>(FieldErrorProperty); }
        set { SetValue(FieldErrorProperty, value); }
    }

    /// <summary> 
    /// Register the FieldError property so it is known in the class.
    /// </summary> 
    public static readonly PropertyDataFieldErrorProperty = 
    RegisterProperty("FieldError", typeof(string), "Invalid field value");
    #endregion 

    #region Methods
    /// <summary> 
    /// Validates the fields.
    /// </summary> 
    protected override void ValidateFields()
    {
        // Check warnings
        if (FieldWarning == "Invalid field value")
        {
            SetFieldWarning(FieldWarningProperty, 
              "Property 'FieldWarning' is probably wrong");
        }

        // Check errors
        if (FieldError == "Invalid field value")
        {
            SetFieldError(FieldErrorProperty, "Property 'FieldError' is wrong");
        }
    }

    /// <summary> 
    /// Validates the business rules.
    /// </summary> 
    protected override void ValidateBusinessRules()
    {
        // TODO: Implement any business rules of this object. 
        // Simply set any error by using the SetBusinessRuleError method
    }
    #endregion 
}

4.4. Saving objects to disk or memory

Explanation

Saving and loading objects out of the box has never been so easy. SavableDataObjectBase can automatically save/load objects in several ways, such as memory, file in different modes (binary and XML). This example shows that making your objects savable is very easy and does not take any time!

Code snippets

  • dataobject - Declare a data object based on the DataObjectBase class
  • dataprop - Declare a simple property on a data object

Steps

  1. Create a new class file called SavableObject.cs.
  2. Inside the namespace, use the dataobject codesnippet and fill in the name of the class, in this case SavableObject.
  3. Change the base class from DataObjectBase to SavableDataObjectBase.
  4. In the Properties region, use the code snippet dataprop, and use the following values:
    Code snippet itemValue
    description Gets or sets the name
    typestring
    name Name
    defaultvalue “MyName”
  5. You can now save the created object by using any of the Save methods. Loading can be done by using the static SavableObject.Load methods.

Code

C#
/// <summary> 
/// SavableObject Data object class which fully supports serialization, 
/// property changed notifications,
/// backwards compatibility and error checking.
/// </summary> 
[Serializable]
public class SavableObject : SavableDataObjectBase<SavableObject>
{
    #region Variables
    #endregion 

    #region Constructor & destructor
    /// <summary> 
    /// Initializes a new object from scratch.
    /// </summary> 
    public SavableObject()
    { }

    /// <summary> 
    /// Initializes a new object based on <see cref="SerializationInfo"/>.
    /// </summary> 
    /// <param name="info"><see
    ///     cref="SerializationInfo"/> that contains the information.
    /// </param> 
    /// <param name="context"><see
    //       cref="StreamingContext"/>.</param> 
    protected SavableObject(SerializationInfo info, StreamingContext context)
        : base(info, context) { }

    #endregion 

    #region Properties
    /// <summary> 
    /// Gets or sets the name.
    /// </summary> 
    public string Name
    {
        get { return GetValue<string>(NameProperty); }
        set { SetValue(NameProperty, value); }
    }

    /// <summary> 
    /// Register the Name property so it is known in the class.
    /// </summary> 
    public static readonly PropertyDataNameProperty = 
        RegisterProperty("Name", typeof(string), "MyName");
    #endregion 

    #region Methods
    #endregion 
}

4.5. Backwards compatibility

Explanation

The class seems nice, but what if you already have all the serialization built into your software and custom objects? No problem, Catel fully supports backwards compatibility, and allows you to add custom deserialization in case the object cannot be deserialized by the DataObjectBase itself.

This way, you can safely migrate to using Catel, and you don’t have to worry about serialization in the next versions of your software any longer.

Code snippets

  • dataobject - Declare a data object based on the DataObjectBase class
  • dataprop - Declare a simple property on a data object

Steps

  1. Create a new class file called BackwardsCompatibleObject.cs.
  2. Inside the namespace, use the dataobject codesnippet and fill in the name of the class, in this case BackwardsCompatibleObject.
  3. Change the base class from DataObjectBase to SavableDataObjectBase.
  4. In the Properties region, use the code snippet dataprop, and use the following values:
    Code snippet item Value
    description Gets or sets the name
    typestring
    nameName
    defaultvalue“Unknown”
  5. Let’s assume the old version of the object serialized before has serialized the Name property as _name in the serialization info. Override the GetDataFromSerializationInfo method and add the following code. As you can see, if the deserialization did not succeed, the information is retrieved manually from the old object. This only needs to be done once; in future, the DataObjectBase class will know how to deserialize the object correctly.
C#
/// <summary> 
/// Retrieves the actual data from the serialization info.
/// </summary> 
/// <param name="info"><see cref="SerializationInfo"/>.</param> 
/// <remarks> 
/// This method is called from the OnDeserialized method, thus all child objects
/// are serialized and available at the time this method is called.
/// Only use this method to support older serialization techniques. When using this class 
/// for new objects, all serialization is handled automatically.
/// </remarks> 
protected override void GetDataFromSerializationInfo(SerializationInfo info)
{
    // Check if deserialization succeeded 
    if (DeserializationSucceeded) return;

    // Handle deserialization by ourselves
    Name = SerializationHelper.GetString(info, "_name", 
            NameProperty.GetDefaultValue<string>());
}

Code

C#
/// <summary> 
/// BackwardsCompatibleObject Data object class which fully supports serialization, 
/// property changed notifications,
/// backwards compatibility and error checking.
/// </summary> 
[Serializable]
public class BackwardsCompatibleObject : SavableDataObjectBase<BackwardsCompatibleObject>
{
    #region Variables
    #endregion 

    #region Constructor & destructor
    /// <summary> 
    /// Initializes a new object from scratch.
    /// </summary> 
    publicBackwardsCompatibleObject()
    { }

    /// <summary> 
    /// Initializes a new object based on <see cref="SerializationInfo"/>.
    /// </summary> 
    /// <param name="info"><see
    //        cref="SerializationInfo"/> that contains the information.
    /// </param> 
    /// <param name="context"><see
    //        cref="StreamingContext"/>.</param> 
    protected BackwardsCompatibleObject(SerializationInfo info, StreamingContext context)
        : base(info, context) { }
    #endregion 

    #region Properties
    /// <summary> 
    /// Gets or sets the name.
    /// </summary> 
    public string Name
    {
        get { returnGetValue<string>(NameProperty); }
        set { SetValue(NameProperty, value); }
    }

    /// <summary> 
    /// Register the Name property so it is known in the class.
    /// </summary> 
    public static readonly PropertyDataNameProperty = 
        RegisterProperty("Name", typeof(string), "Unknown");
    #endregion 

    #region Methods
    /// <summary> 
    /// Retrieves the actual data from the serialization info.
    /// </summary> 
    /// <param name="info"><see
    ///      cref="SerializationInfo"/>.</param> 
    /// <remarks> 
    /// This method is called from the OnDeserialized method, thus all child objects
    /// are serialized and available at the time this method is called.
    /// Only use this method to support older serialization techniques. 
    /// When using this class 
    /// for new objects, all serialization is handled automatically.
    /// </remarks> 
    protectedoverridevoidGetDataFromSerializationInfo(SerializationInfo info)
    {
        // Check if deserialization succeeded
        if (DeserializationSucceeded) return;
 
        // Handle deserialization by ourselves
        Name = SerializationHelper.GetString(info, "_name", 
               NameProperty.GetDefaultValue<string>());
    }
    #endregion 
}

4.6. Supporting moved/renamed types and properties

Explanation

This example shows how to use RedirectTypeAttribute. The attribute can be used to inform DataObjectBase that a type has moved or renamed and should be correctly redirected to the new type. This way, you can safely move/rename objects and still be able to deserialize your old objects.

For this example, you should assume that a previous class named PreviousTypeName existed in the namespace Catel.Articles. But since that was completely wrong, it is decided that the class is now named RenamedObject and is located in a new namespace.

Note: For the sake of simplicity, no properties are declared on this class since it would only cause overhead for the example.

Code snippets

  • dataobject - Declare a data object based on the DataObjectBase class
  • dataprop - Declare a simple property on a data object

Steps

  1. Create a new class file called RenamedObject.cs.
  2. Inside the namespace, use the dataobject codesnippet and fill in the name of the class, in this case RenamedObject.
  3. Change the base class from DataObjectBase to SavableDataObjectBase.
  4. Add the RedirectType attribute with the right values on top of the class declaration:
    C#
    [RedirectType("Catel.Articles", "PreviousTypeName")]
  5. During deserialization, the type Catel.Articles.PreviousTypeName will automatically be directed to Catel.Articles._02__Data_handling.Models.RenamedObject.

Code

C#
/// <summary> 
/// RenamedObject Data object class which fully supports serialization, 
/// property changed notifications,
/// backwards compatibility and error checking.
/// </summary> 
[Serializable]
[RedirectType("Catel.Articles", "PreviousTypeName")]
public class RenamedObject : DataObjectBase<RenamedObject>
{
    #region Variables
    #endregion 

    #region Constructor & destructor
    /// <summary> 
    /// Initializes a new object from scratch.
    /// </summary> 
    public RenamedObject()
    { }

    /// <summary> 
    /// Initializes a new object based on <see cref="SerializationInfo"/>.
    /// </summary> 
    /// <param name="info"><see
    ///      cref="SerializationInfo"/> that contains the information.
    /// </param> 
    /// <param name="context"><see cref="StreamingContext"/>.</param> 
    protected RenamedObject(SerializationInfo info, StreamingContext context)
        : base(info, context) { }
    #endregion 
}

5. What else is possible with these classes?

Since the base class DataObjectBase implements all of the most commonly used interfaces, the possibilities are (almost) endless. For example, this class also serves as a base class for the ViewModelBase class that ships with the MVVM-framework of Catel. But, the classes can also be used in MVC to add validation.

6. History

  • 25 November, 2010: Added article browser and brief introduction summary
  • 23 November, 2010: Removed disclaimer, added link to CodePlex
  • 13 September, 2010: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Netherlands Netherlands
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralNice, I too toyed with a more DP like snytax Pin
Sacha Barber6-Jan-11 21:28
Sacha Barber6-Jan-11 21:28 
GeneralRe: Nice, I too toyed with a more DP like snytax Pin
Geert van Horrik6-Jan-11 21:30
Geert van Horrik6-Jan-11 21:30 
GeneralRe: Nice, I too toyed with a more DP like snytax [modified] Pin
Sacha Barber6-Jan-11 21:44
Sacha Barber6-Jan-11 21:44 
GeneralRe: Nice, I too toyed with a more DP like snytax [modified] Pin
Member 263085627-Mar-12 8:19
Member 263085627-Mar-12 8:19 
GeneralRe: Nice, I too toyed with a more DP like snytax [modified] Pin
Geert van Horrik27-Mar-12 8:23
Geert van Horrik27-Mar-12 8:23 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.