Search
Friday, November 21, 2008 ..:: Articles » Declarative Programming » Writing Declarative-Friendly Assemblies ::.. Register  Login
 Articles By Category
Concurrent Programming
Tutorials
Imaging And Graphics
XML
Declarative Programming
Controls
Lists And Collections
Unit Testing
Design And Architecture
Performance
Client/Server
Fun Stuff

  

 Writing Declarative-Friendly Assemblies Minimize

Writing A Declarative-Friendly Assembly

The article describes the rules to follow for writing assemblies and their classes in a declartive-programming friendly manner.

Namespaces

XML allows you a single default namespace which allows you to reference classes in the tag without a prefix.  Everything else needs a namespace, which is going to make the markup really ugly if you have a lot of different namespaces.  So choose your namespaces carefully.  For example, in MyXaml, the namespace is extracted from the XmlNode NamespaceURI property:

public object CreateControl(object parent, XmlNode element,
object eventTarget) { string controlName=element.LocalName; string nameSpace=element.NamespaceURI; object ctrl=InstantiateControl(nameSpace, controlName); ... }

Classes

Classes must have default constructors.  The parser doesn't know what parameters the constructor is going to need, so you have to provide a default constructor.  In MyXaml, classes are instantiated like this:

protected object InstantiateControl(string nameSpace, string name)
{
  // construct the control based on the namespace information
  string qualifiedName=StringHelpers.LeftOf(nameSpace, ',')+"."+name;
  if (StringHelpers.RightOf(nameSpace, ',') != String.Empty)
  {
    qualifiedName=qualifiedName+","+StringHelpers.RightOf(nameSpace, ',');
  }
  object ctrl=InstantiateControl(qualifiedName);
  return ctrl;
}

protected object InstantiateControl(string qualifiedName)
{
  object ctrl=null;
  Type t=Type.GetType(qualifiedName);
  if (t != null)
  {
    ctrl=Activator.CreateInstance(t);
    ...
  }
}
Given the namespace, there's a little bit of massaging to combine the namespace with the class name in order to create a fully qualified name.  The parser then attempts to obtain the type and instantiate the class. No parameters are passed to the constructor. (BTW, the word "Control" is going to be deprecated soon).

Properties

Write property setters, at a minimum, for all you publicly settable fields.  The exception to this is collections.  The basic concept is very simple: given the object just instantiated and XML attribute name, attempt to set the property to the XML attribute value.  In MyXaml, you'll find some code like this:

PropertyInfo pi=obj.GetType().GetProperty(propertyName);
...
// Thanks to Leppie for showing me the TypeConverter class
TypeConverter tc = TypeDescriptor.GetConverter(pi.PropertyType);
if (val is String)
{
  if (tc != null && tc.CanConvertFrom(typeof(string)))
  {
    // changed from ConvertFromString, as per CPian tditiecher, to support
// different culture formats
object objConv = tc.ConvertFromInvariantString((string)val); // The Form.MainMenu property returns true for CanConvertFrom, but null
// when the conversion takes place!
if (objConv != null) { try { pi.SetValue(obj, objConv, null); ... } ... } } }

"New" Keyword On Properties

You should avoid using the "new" keyword on a property that is defined in a base class but whose behavior you want to change.  Instead, use a different and unique property name.  Often, the properties in base classes aren't virtual, yet, the "new" keyword can be used to replace the functionality of the base class and even allow you to call the base class method.  This is usually done when changing the return type of a property, but not necessarily.  When using reflection, the parser cannot easily resolve which property should be set--the base class or the new derived class.  Therefore, this feature of the C# language should be avoided.

Type Converters

For custom types, write a type converter that converts a string to your internal property type.  Notice in the above code that the type converter is always used.

Collections

Collections should be derived from an IList interface.  Collections can be read-only (make sure you instantiate an empty collection in your constructor!).  In MyXaml, if the parent object is of type IList, it automatically adds the child object to the collection:

if (obj is IList)
{
  string nameSpace=node.NamespaceURI;
  string qualifiedName=StringHelpers.LeftOf(nameSpace, ',')+"."+propertyName+
", "+StringHelpers.RightOf(nameSpace, ','); object ctrl=InstantiateControl(qualifiedName); if (ctrl != null) { ((IList)obj).Add(ctrl); isProperty=true; } }

Events

If you're implementing a UI element, consider writing an event for every action that your control handles from the .NET framework.  If your class is not a UI element (or even if it is), consider implementing an event whenever a property setter is called, even if the property value doesn't change.  You will make lots of friends by taking the time to really think through what event your users are going to be interested in.

Events are also important for data binding to work.  Any property that can be data bound should also implement an appropriate event, or used the property changed mechanism supported in .NET 2.0.

The code for the parser looks like this:

protected bool SetEvent(object obj, string propertyName, string val,
object eventTarget) { // if it's not a property, see if it's an event EventInfo ei=GetEventInfo(obj, propertyName); bool isEvent=ei != null; if (isEvent) { try { string methodName=val; Delegate dlgt; eventTarget=SearchForEventTarget(ei, ref methodName, out dlgt); ei.AddEventHandler(obj, dlgt); } catch(Exception e) { ... } } else { // the property is not an event. ... } return isEvent; }

The actual search function is a bit too long to put here.  Regardless of whether the user is using MyXaml or Xamlon or another parser, it is important that you, then implementer, provide the appropriate events.

Tags and Names

As I'm writing some XAML for a demonstration MDI application, I'm realizing the importance of tags and names.  As I wrote in my blog: "XAML makes your applications very event-centric.  Events are the primary mechanism for communicating outside things to the inside (your application).  Your events operate on things that they can get access to via the parser."  But what this means is that your event handlers are going to need help acquiring information relevant to the object that fired the event.  Tags are where this information can be conveniently stored, and it provides a poor-man's way of communicating useful information between objects and between the declarative markup and the application code.  Names are useful when you need to search through a collection for a particular instance (or look up an instance by its name).

For example, the MenuItem class is missing both the Tag property and the Name property.  This is really inconvenient, to the point where using .NET's MenuItem class is pretty much useless.  For the MDI demonstration (still working on it), what I've done is taken Chris Beckett's Menu object and extended it with some useful properties to make it XAML-friendly.  You might even want to use the following interface to help remember this golden rule:

interface IXaml
{
  string Name
  {
    get;
    set;
  }

  object Tag
  {
    get;
    set;
  }
}

Wrapping It Up

So those are the eight golden rules to a XAML friendly assembly:

  1. not too many namespaces
  2. public, default constructors
  3. properties for everything you want to expose to the outside world
  4. do not use the "new" keyword on properties that exist in the base class
  5. type converters when .NET can't handle the conversion itself
  6. collections derived from IList and instantiated at construction time if read-only
  7. implement events for UI actions and property setters, where appropriate
  8. every class should provide a Name and a Tag property

  

Copyright 2005 Marc Clifton   Terms Of Use  Privacy Statement
Portal engine source code is copyright 2002-2008 by DotNetNuke. All Rights Reserved