2009-07-28

SimplePropertyPath: A Poor Man's Binding

This post illustrates one of several utility classes that are in the Nito.MVVM library: SimplePropertyPath.

SimplePropertyPath is used to create a very simple binding using only INotifyPropertyChanged and not using DependencyProperty or DependencyObject. The System.Windows.Data.Binding class is much more powerful, but is dependent on the WPF-specific dependency property/object system. SimplePropertyPath only uses INotifyPropertyChanged, which gives it two advantages:

  • It can be used in non-WPF environments with only minor changes, e.g., using the MVVM pattern on a compact device.
  • It allows all ViewModel classes to be POCO (utilizing INotifyPropertyChanged) instead of forcing them to be derived from DependencyObject. (In my mind, at least, DependencyObject or FrameworkElement-derived classes are more of a View class).

SimplePropertyPath merely propagates an existing INotifyPropertyChanged implementation "forward" to other listeners, and propogates writes "back" to the original property. It is only capable of understanding a "simple" property path: one comprised entirely of member accessors.

Some examples will help clarify how this class can be used; the following "fake ViewModel" class is used by these examples. It just has two properties: "int Value" and "FakeVM Child", and implements INotifyPropertyChanged:

private sealed class FakeVM : INotifyPropertyChanged
{
    private int value;
    private FakeVM child;

    public int Value
    {
        get { return this.value; }
        set
        {
            this.value = value;
            this.OnPropertyChanged("Value");
        }
    }

    public FakeVM Child
    {
        get { return this.child; }
        set
        {
            this.child = value;
            this.OnPropertyChanged("Child");
        }
    }

    private void OnPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

The API of SimplePropertyPath is rather simple:

SimplePropertyPath Class Diagram

The two properties "Root" and "Path" are used to define the SimplePropertyPath. The "Value" property is used to read or write the nested property.

A simple example to start off with; to read or write the "Value" property on a FakeVM object:

FakeVM obj = new FakeVM { Value = 13 };
SimplePropertyPath path = new SimplePropertyPath { Root = obj, Path = "Value" };

Assert.AreEqual(13, path.Value);

path.Value = 17;
Assert.AreEqual(17, obj.Value);

Nothing too difficult there. The next example exercises reading and writing to a longer path:

FakeVM obj = new FakeVM { Child = new FakeVM { Value = 10 } };
SimplePropertyPath path = new SimplePropertyPath { Root = obj, Path = "Child.Value" };

Assert.AreEqual(10, path.Value);

path.Value = 17;
Assert.AreEqual(17, obj.Child.Value);

Now it's starting to act more like a real binding. Invalid property paths will result in a Value of null (writing errors to PresentationTraceSources.DataBindingSource just like WPF data binding does):

FakeVM obj = new FakeVM { Value = 13 };
SimplePropertyPath path = new SimplePropertyPath { Root = obj, Path = "value" };

Assert.IsNull(path.Value);

path.Value = 17;
Assert.AreEqual(13, obj.Value);
Assert.IsNull(path.Value);

The "childmost" property is not the only one that is being monitored; SimplePropertyPath will monitor INotifyPropertyChanged for each object along the path:

FakeVM obj = new FakeVM { Child = new FakeVM { Value = 100 } };
SimplePropertyPath path = new SimplePropertyPath { Root = obj, Path = "Child.Value" };

Assert.AreEqual(100, path.Value);

obj.Child = new FakeVM { Value = 113 };
Assert.AreEqual(113, path.Value);

Finally, SimplePropertyPath will raise its own INotifyPropertyChanged for its Value property every time it changes, whether it was caused by a change in the "childmost" property or any of the objects along the path.

SimplePropertyPath is used by the Nito.MVVM library as a building block to construct some of the more advanced classes, such as MultiProperty and MultiCommand. However, it can be useful in its own right.

[Note: all of the examples above were copied almost verbatim from the unit tests in the Nito.MVVM library].

2009-07-21

First CodeProject Article on Nito.MVVM

The first Nito.MVVM CodeProject article is up! This article describes the ICommand implementations that are in the Nito.MVVM library, along with some comparisions to existing work.

The Nito.MVVM library is still a ways away from its first release, but what's there should be stable. Unit tests are included. :)

2009-07-16

Interpreting NotifyCollectionChangedEventArgs

If you've ever consumed INotifyCollectionChanged.CollectionChanged, then you've run into some inadequate documentation for NotifyCollectionChangedEventArgs. I've added the following information to the MSDN "community extensions" (my first contribution), but others have had problems with the stability of community extensions, so the results of my research and Reflector spelunking are also in this blog entry.

In short, the value of the Action property determines the validity of other properties in this class. NewItems and OldItems are null when they are invalid; NewStartingIndex and OldStartingIndex are -1 when they are invalid.

If Action is NotifyCollectionChangedAction.Add, then NewItems contains the items that were added. In addition, if NewStartingIndex is not -1, then it contains the index where the new items were added.

If Action is NotifyCollectionChangedAction.Remove, then OldItems contains the items that were removed. In addition, if OldStartingIndex is not -1, then it contains the index where the old items were removed.

If Action is NotifyCollectionChangedAction.Replace, then OldItems contains the replaced items and NewItems contains the replacement items. In addition, NewStartingIndex and OldStartingIndex are equal, and if they are not -1, then they contain the index where the items were replaced.

If Action is NotifyCollectionChangedAction.Move, then NewItems and OldItems are logically equivalent (i.e., they are SequenceEqual, even if they are different instances), and they contain the items that moved. In addition, OldStartingIndex contains the index where the items were moved from, and NewStartingIndex contains the index where the items were moved to. Note that a Move operation is logically treated as a Remove followed by an Add, so NewStartingIndex is interpreted as though the items had already been removed.

If Action is NotifyCollectionChangedAction.Reset, then no other properties are valid.

Sources

There are two blog entries that helped me get started: Making Sense of NotifyCollectionChangedEventArgs and An Alternative to ObservableCollection. However, more details were still lacking; fortunately, Reflector saved the day! The primary .NET sources were: [WindowsBase.dll, 3.0.0.0] System.Collections.ObjectModel.ObservableCollection<T> - SetItem, RemoveItem, MoveItem, etc. and [PresentationFramework.dll, 3.0.0.0] System.Windows.Data.CollectionView - ProcessCollectionChanged, AdjustCurrencyFor*.

2009-07-10

Nito.WeakReference and Nito.WeakCollection

A Replacement for System.WeakReference

There are two minor problems with the System.WeakReference class: type safety and garbage collection.

The first one is a simple problem; System.WeakReference only deals with instances of type object. However, it is not difficult to write a type-safe wrapper for System.WeakReference that provides type safety.

The second problem is a little more subtle. A weak reference is a wrapper around a System.Runtime.InteropServices.GCHandle. The core GCHandle type is both powerful and dangerous; it is halfway unmanaged. System.WeakReference provides a safer class that only uses GCHandle as a weak reference.

The problem is that a GCHandle actually represents an entry in the runtime's GCHandle table. This table interacts with the garbage collector, preventing some objects from being GC'ed or moved. It is also used to create and track weak references. Logically, allocated GCHandles are unmanaged resources, and System.WeakReference does clean up its GCHandle correctly in its finalizer.

However, System.WeakReference does not implement System.IDisposable. This is not the end of the world, but it does place additional pressure on the garbage collector. It forces every System.WeakReference into an extra generation, holds the GCHandle table entry longer than necessary (possibly causing extra work for the garbage collector, since it must update the GCHandle table entry when that object is collected), and requires the finalizer thread to actually release the resource.

Nito.WeakReference<T> is a strongly-typed, disposable replacement for System.WeakReference. [Note, however, that Nito.WeakReference<T> does not support resurrection, which is a complex use case that is only required in very rare situations.]

A Simple Weak Collection

A weak collection (or more properly, a collection of weak references) at any given time may contain references to both live (referenced) and dead (garbage collected) objects. Removing all the dead objects is called a "purge". It's convenient to be able to enumerate the collection both with and without a purge. The Nito.IWeakCollection<T> defines an interface for enumerable collections of weak references, and includes methods for enumerating or counting the collection both with and without purging. Some of its members include:

  • CompleteList - Gets a complete sequence of objects from the collection (null entries represent dead objects).
  • LiveList - Gets a sequence of live objects from the collection, causing a purge.
  • CompleteCount - Gets the number of live and dead entries in the collection. O(1).
  • LiveCount - Gets the number of live entries in the collection, causing a purge. O(n).

Nito.WeakCollection<T> is a List-based implementation of a weak collection. It is used by the Nito MVVM library to track weak events, but it should be useful in other situations as well.

2009-07-01

XML over TCP/IP

(This post is part of the TCP/IP .NET Sockets FAQ)

XML is a popular choice when designing communications protocols, since XML parsers are ubiquitous. The phrase "XML over TCP" makes a good executive summary, but this FAQ entry is concerned with how to actually make it work. One should always write an application protocol specification document to clearly define the actual communication.

Anyone designing an XML protocol should have a good understanding of the terms. Familiarization with the XML standard is a bonus; this FAQ entry will define most terms along the way, but the official definitions are in the relevant standards.

When used as a communications protocol, XML does not usually use processing instructions or entities (except for the normal "escaping entities" such as &lt;, etc.). It also does not normally use XSDs or DTDs; XML used for communication is well-formed but not valid (see the XML standard for the definitions of these terms).

This FAQ entry is laid out in a sequence of steps, but they do not necessarily have to be done in this sequence; in fact, the last step is often done first. However, they each must be addressed in an application protocol specification, so they should be viewed more as a "checklist" than a "timeline". This advice should be considered additional to the general advice given in Application Protocol Specifications.

Step 1: Encoding

XML documents are just a sequence of Unicode characters. However, TCP/IP works with streams of bytes. A translation must be made between Unicode characters and byte sequences, and this translation is called the encoding.

Encoding: The translation used when converting a Unicode string to or from a byte sequence. The most common encodings are UTF-16 (which uses 2 bytes for most characters; it is recognizable because every other byte is "00" for normal English text) and UTF-8 (which uses 1 byte for normal English characters, but most characters take 2 or more bytes).

The encoding may be detected one of several ways (if it is not specified in the application protocol document). A byte order mark may be used to detect the encoding in some situations.

Byte Order Mark (BOM): A special Unicode character that is sometimes inserted at the beginning of a document as it is encoded. The UTF-16 encoding has little-endian and big-endian versions, so it requires a BOM. UTF-8, however, does not require a BOM.

The XML file itself may include a prolog, which may specify the encoding being used. This is more difficult to work with, since the prolog itself must be interpreted heuristically by guessing at the encoding. Note that if an XML prolog exists that specifies an incorrect encoding, Microsoft's XML parsers may get confused (this often happens when reading XML from a string or writing XML to a string).

XML Prolog: The "<?xml ... ?>" element at the beginning of some XML files, specifying the XML version and (optionally) the encoding used.

Normally, the application protocol document specifies the encoding; this is simpler than dealing with automatically detecting the encoding. If it does specify the encoding, it should also specify whether a BOM should be present, or if a prolog is allowed to specify the encoding. A common choice is "UTF-8 without BOM or prolog", which makes the encoding always UTF-8 without a byte order mark or XML prolog.

Working with the System.Text.Encoding class

The encoding for an output may be specified by using the XmlWriterSettings class. This is the preferred way to create XmlWriters. Encodings for inputs may also be forced; this is done if the remote side sends incorrect XML prologs, causing problems with XmlReaders.

Always translate XML to and from byte arrays, not strings. The System.String class actually uses an encoding itself (UTF-16). Using strings as an intermediary between byte arrays and XML classes may cause problems because of this additional encoding; encoding the string to and from a byte array will not update the XML prolog. An encoding may be given to an XmlWriterSettings class, but as the documentation for XmlWriterSettings.Encoding states, any encoding on a stream will override that setting. For this reason, it is better to use XmlWriters with a MemoryStream, which does not have an encoding.

Each instance of an Encoding object must make another choice: whether to throw an exception on invalid input bytes or silently replace them (with the Unicode replacement character U+FFFD, which looks like '?' in a diamond). The default is to silently replace, but this is not generally recommended when implementing application protocols. The example code in this FAQ entry will always use encodings that throw exceptions.

The following sample code takes an XElement and an Encoding, and translates the XML to a byte array (not including an XML prolog):

byte[] ConvertXmlToByteArray(XElement xml, Encoding encoding)
{
    using (MemoryStream stream = new MemoryStream())
    {
        XmlWriterSettings settings = new XmlWriterSettings();
        // Add formatting and other writer options here if desired
        settings.Encoding = encoding;
        settings.OmitXmlDeclaration = true; // No prolog
        using (XmlWriter writer = XmlWriter.Create(stream, settings))
        {
            xml.Save(writer);
        }
        return stream.ToArray();
    }
}

When reading XML from a byte array, one may either use an XmlReader or a StreamReader. Using an XmlReader allows incoming XML to use any encoding; however, it will get confused if the XML has an incorrect encoding specified in its prolog. Using a StreamReader will force the byte array to be interpreted according to a particular encoding, but does not allow any other encodings.

If the application protocol specification does not specify an encoding, then the XmlReader must be used. Incorrect XML prologs cannot be allowed. The following sample code allows any encoding:

XElement ConvertByteArrayToXml(byte[] data)
{
    // Interpret the byte array allowing any encoding
    XmlReaderSettings settings = new XmlReaderSettings();
    // Add validation and other reader options here if desired
    using (MemoryStream stream = new MemoryStream(data))
    using (XmlReader reader = XmlReader.Create(stream, settings))
    {
        return XElement.Load(reader);
    }
}

If the application protocol specification does specify a particular encoding, then using a StreamReader will allow incorrect XML prologs. The following sample code forces a specific encoding:

XElement ConvertByteArrayToXml(byte[] data, Encoding encoding)
{
    // Interpret the byte array according to a specific encoding
    using (MemoryStream stream = new MemoryStream(data))
    using (StreamReader reader = new StreamReader(stream, encoding, false))
    {
        return XElement.Load(reader);
    }
}

The default Encoding.Unicode (UTF-16 little endian), Encoding.BigEndianUnicode (UTF-16 big endian), and Encoding.UTF8 instances always write out BOMs and never throw on errors. A better choice is to create instances of UnicodeEncoding (UTF-16) or UTF8Encoding that are more suitable for application protocols. For example, "new UTF8Encoding(false, true)" will create a UTF-8 encoding without BOM that throws on invalid characters.

Step 2: Message Framing

Message framing is highly recommended for XML messages. Technically, message framing is not strictly necessary, but reading XML from a socket without message framing is extremely difficult (the message must be considered complete when the root node is closed).

When sending, message framing is actually applied after the encoding, so the message framing wraps a byte array. Similarly, when receiving, message framing is applied directly to the received data, and each message (consisting of a byte array) is then passed through the encoding to produce XML.

Step 3: Keepalives

Keepalive messages are usually necessary. Having a keepalive message defined in the application protocol specification often removes the need for separate timers when implementing the protocol.

XML keepalive messages (e.g., "<keepalive/>") are not normally used. Usually, a keepalive message may be sent by using the message framing to send an empty (zero-length) message.

Step 4: Messages

Once the encoding, message framing, and keepalive options have all been chosen, then there is a framework over which XML messages may be exchanged. For each XML message, the following information should be included:

  • When the message is meaningful (e.g., a "Response" should only be sent in response to a "Request").
  • Which attributes and elements are required and which are optional. This includes complex relations (e.g., a "Log" element must contain at least one "Message" element and exactly one "Source" element). Be sure to use terms with specific definitions ("at least one", "exactly one", etc).
  • The format of non-string data such as dates, booleans, and integers. Often, this type of "formatting data" is defined globally near the top of the application protocol specification, and applies to each possible message type.

Many protocols are based on a request/response or subscription/event model. One thing to watch out for is if the protocol elements begin looking like generic or object-oriented function calls, as though one side is accessing a remote object (e.g., "<CallMethod ObjectID='37' MethodName='GetData'/>"). At this point, the protocol will devolve into something eventually looking like SOAP. There's nothing wrong with SOAP, but there's no need to re-invent the wheel. If that level of abstraction is truly necessary, then just use SOAP instead of creating a separate protocol.

Recommendations

A choice must be made for encoding, message framing, and keepalives.

  • The encoding is usually "any" or "UTF-8 without BOM or prolog", but could be anything.
  • A common choice for message framing is "4-byte (un)signed little-endian integer length prefix". Delimeter-based message framing may also be used for XML by appointing illegal XML characters to be the delimiters, and scanning for them during decoding (however, this is very difficult to do if the encoding is "any").
  • Since most XML protocols use length prefixing, most of them also choose "length prefix of 0 with no message" for keepalives.

One final note: nothing helps track down interfacing errors like verbose logging. It's recommended to log the byte arrays sent and received as well as the actual XML messages. Logging is your friend. :)

(This post is part of the TCP/IP .NET Sockets FAQ)