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

RuntimeExtensionManagement

Rate me:
Please Sign up or sign in to vote.
5.00/5 (8 votes)
5 Nov 2012CPOL35 min read 20.5K   265   15  
Extend your objects at run-time and create really loosely-coupled applications.

Background

First I wrote an article about Attributes vs the Single Responsibility Principle. Then I wrote the Converters article in which I presented a generic converter class that conforms with such principle and is really loosely-coupled, allowing users to register their own conversion to any types, making it possible to add conversions to types that do not implement IConvertible, do not declare a [TypeConverter] on their declarations or that are simple from unrelated assemblies.

Then I wrote an article about Actionless frameworks. In it I presented why WPF and my own Converters framework were a kind of actionless framework, but I promised to present a full-featured actionless framework later as even the Converters framework (now renamed to ConversionManagement framework) was not complete in my actionless proposition.

Now, it is the time to present a full featured actionless framework, and it is the RuntimeExtensionManagement actionless framework.

What is an actionless framework

To those who didn't read the other article, here is a brief introduction of an actionless framework. An actionless framework is a framework that:

  • Is fully configurable, so all of its default actions can be completely replaced or eliminated (and it can even start with no action at all);
  • Has support for global, thread local and instance local configurations;
  • Has a last chance event that let you provide a value when one was not previously registered;
  • Well, there are other small details but I think those are the key ones.

Why use the attached code?

Before starting with the article itself, I will try to sell the product.

Using the RuntimeExtensionManagement + the WPF components that are presented in this article you will be immediately able to:

  • Edit any record in your application by doing a simple RecordEditor.Edit() call;
  • Add localization support by simple registering a new text searcher (some defaults are included);
  • Change the type of your record, add new properties and change their property-types without have to worry about an User-Interface that stops working;
  • For server applications, allow different users connected at the same time to use different configurations (like different localization settings);
  • Allow your application to register better editors at run-time and even allow them to be loaded from external assemblies easily.

What is the RuntimeExtensionManagement?

The RuntimeExtensionManagement is the namespace and also the name of an actionless framework that allows users to extend any object at run-time.

As the .Net types themselves also have their objects and you can get all their members from there you can add new information to types, properties, fields and to specific instances.

Maybe you already do register additional data to your objects at run-time thanks to a Dictionary or a ConditionalWeakTable, but as already presented, the actionless framework must follow some rules, like having global and local configurations, which can become a must on web-services.

What kind of extensions can be done?

Any. But as I know it is hard to simple think with that abstract kind of information, I will start by the ones I am actually using:

  • DisplayNames: I can add better naming to enum values and properties, so factories can use a name that has spaces and symbols even if the property name is not allowed to have those;
  • Descriptions: By the same principle, I can add descriptions (usually presented by ToolTips) with multi-line explanations of enum items and properties;
  • IsImmutable, IsReadOnlyNotifier, IsReadOnlyVerifier: To know if objects are editable or not I have three extensions. The IsImmutable extension is used over types and tells that all instances of that type are always read-only. Then there is the IsReadOnlyNotifier, that is used to notify when the read-only status of an instance was changed (like when a Freezable is Frozen) and finally there is the IsReadOnlyVerifier, which simple checks if an object is read-only at the moment of the call;
  • IsRemotable: I have my own remoting framework which is using this extension instead of "guessing" if a type is remotable or not;
  • Configurable "global" exception handlers: Most application exception handlers usually only show the message of the exception or are polluted with a lot of is and as casts. With this framework it is possible to create configurable exception handlers, so your application always shows exceptions with a small amount of code, but different components may register how their exceptions must be presented;
  • Factories: My new factories implementation is based on extensions to the Type class, allowing me to add value-editors, record-editors, record-searchers and others to Types.

Localization

Image 1

Image 2

In the presentation I said that you could add text localization easily. So, are the DisplayNames and Descriptions localizable?

The answer is both yes and no. The framework only comes initialized with searchers that look for the member name and the [DisplayName]/[Description] attributes. So, without any extra configuration, it is not localizable.

But as an actionless framework you can set/replace the extensions for the items, so you can set better DisplayNames and Descriptions at runtime, and you can add better searchers, capable of looking for those items in the database, resource files or even text files, making all the places that use such extensions completely localizable. It is enough that you always use the RuntimeExtensionManager to get those texts and the components that will be presented do exactly that.

Garbage Collection

Another question that some users may ask is how I am keeping those RuntimeExtensions in memory.

Well, when adding data to objects at run-time we are inevitably using some kind of dictionary. But that can be global, so it needs a static variable.

The problem then is: If we put a value for an item inside a static dictionary, both (the item and the value) will only be collected when closing the application.

That's why the implementation uses a ConditionalWeakTable. While the main object is alive, the additional info will be kept alive. If the original item is collected, the extra info is also able to be collected (if there are no other references).

But when adding extra info to value types we don't have such chance, as each time we receive a value-type as object we are receiving a new boxed value. So, to solve those cases, if the main object is a value type, a normal Dictionary is used. In this case, RuntimeExtensions made to value types are never collected (if added globally).

That's what happens to DisplayNames set to enums, for example. But, in that case, I really consider it is not a problem as we have a limited number of enum-values. But take care not to add extra info to all possible integer values.

MemberInfos: I always believed that the Types and MemberInfos were always kept in memory once loaded. I was wrong and in another application I discovered such behavior, that became a bug to my library. So, extensions to enums and to MemberInfos are stored in a normal Dictionary. This way, there is no risk of attributes put on FieldInfos and PropertyInfos to be collected.

Using the framework in WPF

If you are only interested in creating your own runtime extensions, you can jump to: Using the framework - Creating your own extensions.

The entire idea of this framework is to create really loosely coupled applications as it allows to search DisplayNames, Descriptions and to register factories for data-types freely.

But allowing to register all kinds of editors is one thing. Creating and registering those editors is another thing. Using them is still another.

And to use it I already created many components that can get the right controls, show the right display names and descriptions and, as I see it, this is the easy part of the framework and the one that will be used most of the time. So I will start by presenting the easy part but, before starting, I want so say something about RAD.

RAD (Rapid Application Development), Wizards and Code Generators

I think that I should talk about RAD a little. The reason is simple. I usually see RAD as a bad thing. Before using Delphi (and later Windows Forms, which is very similar in concept) I created my user-interfaces by code.

It could be harder, but at the same time that made me think in ways to automate the repetitive work and usually that gave me better aligned, resizable and localizable controls. It was simple, the code to create new controls could store some info (like the position of the next control, the place where search texts from etc) and then use it when creating the controls.

But the IDE from Delphi was surely easier to use to create a new window really fast and many new programmers never created controls by code. They simple didn't know what to do if they needed to.

So, a thing that was helping from one side (creating a new form really easy and even placing the controls at exact positions), was making things harder at another side (as directly placed controls were not resizable, direct written labels were not localizable and so on).

Then there were the Wizards and Code Generators. In special, if you selected some table fields and drag-dropped them to the window, the "right" DBAware controls were automatically created and put one below the other on the window. But, after, they were there, so you were free to move them and create a better layout but at the same time, if the data-type changed, they stayed there, and I often saw people deleting controls to drag-and-drop the field again only to get the new right control.

And I can go one step further and say that in some cases, the default DBEdit or DBCheckBox was not the right control but looked like the right one. What if the right control was the MyDBEdit?

The other extreme

To try to avoid those problems (or simple because it seems better to make everything automatic), I also saw some enterprises working with run-time only generated UIs. Any data-type change and any new columns were automatically put into the forms as all the controls were generated at run-time by some kind of table analysis.

With some simple rules, the application was fully localizable, there were no tables without an editor form... but it was a chaotic situation when manually edited forms were needed, as everything was simple automatic, with almost no space for manual editing. Some events were created to try to solve the situation, but that meant that the form was created with the wrong controls and the event was responsible for finding them by name (or even index), deleting them, creating the new controls and usually it was also needed to change the position of the other controls.

So, even if the RAD with wizards were bad, the automatic UI generation was even worse. It could help in the creation of 50 simple forms, but that one complex form was so hard and time-consuming to create that it would be better to create all the forms by hand.

RAD as I expect it

In my opinion a good RAD is one that allows the developer to immediately create all forms of the application with little or no effort at all, exactly like those run-time generated UIs.

It should also automatically reflect changes in data-types or table structures. Again, as those automatically generated UIs.

But it should allow the developer to easily create specific forms, avoiding some of (or even all) those automations. And that's what I tried to do.

My idea is that, by default, all records use a default editor that simple puts one control below the other, already using the DisplayName and Description searchers that can be localizable.

If that's not good, you can replace a specific record editor, but you are not forced to do the entire "form" by hand. You can put some controls at their right places by hand, but still ask them to use the right runtime data-type editors. Then, if you want, you put another control that will edit all the remaining properties using that automatic rule, so future properites will be immediately editable.

In my opinion, this makes the developer free to use the automatic features, never having a table without an editor (for example) and also free to create specific record editors if the default ones are not good looking or simple hard to use.

An extra feature is that when a new specific record editor is created, all the places that were calling the generic editor should start calling the new editor without changes on how the call is done, avoiding boring and error prone search and replaces like new GenericRecordEditor<Person>(record) by new PersonEditor(record).

Well, that's what I tried to do, so let's see how it works.

Returning to the WPF framework

Now I will show how you can use the framework in a WPF application. It is RAD but I really believe it is nearer the right way of doing things.

Edit this record - I don't care how

By a simple call to RecordEditor.Edit(someRecord) you can edit any object. If there is a specific editor registered, it will be used:

Image 3

If there is not, a more generic one will be used:

Image 4

Using the RecordEditor.Edit() is the preferred way to edit a record. The caller simple does not need to care what is the type of the record. It is not forced to know that a Person is edited by the PersonEditor, and that an Enterprise uses the generic editor. Simple do the call and let the right editor be used, be it an automatically generated one or a hand-crafted one.

Edit this record, here

By default when you edit a record a new window is created. That default action can be replaced, but that's not the case now. For some reason you may want to put a specific record editor inside your window without replacing the default behavior.

That's the purpose of the RecordEditorControl.

Image 5

As a control, you can put it anywhere. It is enough to say the data-type to be edited and the right editor control is used. Again, be it automatic or hand-crafted.

To use it, you can use a code like this:

XAML
<PfzWpf:RecordEditorControl Type="{x:Type App:SimpleCalculator}" IsEditorToolbarVisible="False"/>

The Type property is type of the record to be edited and the IsEditorToolbarVisible, which is true by default, tells if the record editor should show the editor toolbar (usually the OK/Cancel button) or not.

Controls to create a record editor

Well, you know that you can edit a record anywhere by a simple call to RecordEditor.Edit() but that default editor is not good. So you want to create your own record editor.

Later I will explain how to register that editor, but now let's see the controls you will probaly use to do the job.

ObjectBoundControl

This control is bound to an entire object, and this is the one that simple puts one property below the other, using the right data-type editor.

In it you can tell which properties to avoid, making it prepared to edit future created properties without requiring to change the control definition to include such properties or you can tell exactly what properties you want it to edit.

By creating some of those controls in your record editor you can already create a better layout but, if you want to create an ObjectBoundControl to edit a single property, then it is better to use the PropertyBoundControl.

You can use it like this:

XAML
<PfzWpf:ObjectBoundControl
  Type="{x:Type App:SampleClass}" 
  Properties="{PfzWpf:Properties Enum;String;LabelledString}" 
  Mode="ShowSelectedProperties"
/>
  • Type: Tells the record-type to which this control is bound and that's the type from which the properties will be get;
  • Properties: Those are the properties to avoid (the default) or to show. I needed to create that {PfzWpf:Properties} markup extension to be able to get the Type from the object before returning the properties. I started by creating a TypeConverter, but it only worked at run-time, making the control not functional at design-time and forcing me to create such extension.
  • Mode: As already explained, it will tell if the listed Properties are those to be avoided or those to be shown.

PropertyBoundControl

This control is bound to a single property and the ObjectBoundControl simple creates many instances of this control, binding each one to a different property.

When bound to a property, it is capable of finding the DisplayName, the Description and the right value-editor to present those informations and also edit the property value. It is also capable of detecting if the property or the record being edited is read-only.

If you don't want to use the default DisplayName or Description you can always set such properties on the control directly, replacing those defaults.

With this control you are free to create your layout as you want but, still, use the right data-type editor. If the property type is changed in the future you may need to correct some code, but the UI will be ready to use the new property type.

Here is a sample declaration:

XAML
<PfzWpf:PropertyBoundControl
  Type="{x:Type App:SampleClass}" 
  Property="{PfzWpf:Property Int}"
/>
  • Type: Tells the Type from which to get the Property to be edited.
  • Property: Tells the Property to be edited. As happens with the ObjectBoundControl, the {PfzWpf:Property} markup extension was created to have access to the Type from which the property is get at design-time.
  • DisplayName: If set on the control, replaces the default DisplayName that is get from the property.
  • Description: If set on the control, replaces the default Description that is get from the property.

ValueEditorControl

The PropertyBoundControl is great to edit a property value but for some reason you simple want to edit a value without creating a property. You know the data-type, but you don't want to put the specific editor directly as it may change in the future.

So, put a ValueEditorControl and set its Type ot its default Value directly. It will find the right editor to edit such value. It is also capable of presenting a DisplayName and a Description and, as you may have already figured it out, this control is used by the PropertyBoundControl.

{DisplayNameWatcher} and {DescriptionWatcher}

The PropertyBoundControl and consequently the ObjectBoundControl are already prepared to search the right DisplayName and Description for properties they are bound to, which can be localizable.

Following that idea you may want to create other texts that are localizable (or could become localizable in the future). By using the {DisplayNameWatcher} and {DescriptionWatcher} XAML extensions you can achieve that.

You will need to inform a Type and a field-name (not a property) to use as the source to get the right text. As enums values become fields, you can declare your texts like this:

C#
public enum MyTexts
{
  [DisplayName("Quit without saving")]
  [Description("Quits the application losing any changes you've made.")]
  QuitWithoutSaving
}

Then in your XAML you will use something like:

XAML
{PfzWpf:DisplayNameWatcher {x:Type App:MyTexts}, QuitWithoutSaving}

or

XAML
{PfzWpf:DescriptionWatcher {x:Type App:MyTexts}, QuitWithoutSaving}

And your code will be prepared to be localized if a localizable Displayname or Description is registered.

Using the framework in WPF - The not so easy part

You already started to use the RecordEditor.Edit() in your application and it is saving you a lot of work (at least I hope it is). Instead of creating 100+ editors to edit simple objects, they are all being edited by the default editor.

But the Person type has a Photo (to which the default implementation is saying it can't find an editor for byte[]) and many document numbers that are simple presented by unformatted textboxes. You want to show the photo, which a Capture button at the bottom and to format and validate the document numbers.

What should you do now?

Two solutions

Before continuing, it is important to understand that there are two main approaches to solve the problem.

My recommended approach is to create new types for everything that has a specific single-field validation or presentation. So, a Photo should not be of type byte[], it should be of type Photo or, at least, of type Image. By the same principle, each document type will also have a specific type instead of using string/int. So, a driver-license is not of type string, but of type DriverLicense.

This helps the automatic record editors find the right value editor controls by looking at the data-type. In this case, your main focus should be to create value editors (IValueEditorControls). You may still want to create entire record editors (IRecordEditorControls), not because the default record editor was not formatting or validating something, but because you want a better layout or specific functionalities in the editor.

But having a new type to do the validations or to havethe initial knowledge of which value editor to use may not please you or it may not work with your ORM. Maybe you use an ORM that is simple incapable of dealing with a Photo type, it should use a byte[] type and you don't want to create wrappers objects, as that will increase the amount of work to be done and, in that case, you will be only moving the problem from one place (the editor, which you need to do only once) to another (requiring to create a wrapper before editing, everytime). In that case, you will really need to implement a new record editor control.

IValueEditorControl vs IRecordEditorControl, when to use each one?

Maybe you are already in doubt, maybe you will get in doubt at the moment you are about to implement an editor or maybe you simple got it from the first moment.

But sometimes people get confused if they should use/implement a value editor or a record editor. For example, the actual Person editor is not good, and you want to create a new one. Which one should you implement?

The answer can be related to inner data. Do you already have a Person object and you want to edit its properties? Or do you want to create a lookup, maybe?

An IRecordEditorControl is responsible for editing the inner content of an already existing record. Maybe it does a search too, but that's not its main purpose. On the other hand, an IValueEditorControl does not care about the inner content. It always gives you an entire new object, even if it is an object already in memory.

So, for a Person you may want to create a value editor that is a Lookup (used to edit properties of type Person) and a record editor that will edit an actual Person instance contents.

Creating an IValueEditorControl - The first approach

To illustrate the situation, Colors are edited using a text-box by default, simple presenting their hexadecimal values:

Image 6

But you are free to create and register a better editor:

Image 7

A value editor control has the purpose of editing data of some type and it return new values instead of changing an already existing object. It can be used to edit strings, booleans, Colors or even a DriverLicense doing the required formatting and validations.

To be a valid value editor control to this framework, it should implement the IValueEditorControl interface.

Such interface requires the control to support IsReadOnly (many of my implementations simple use the reversed IsEnabled value), Clear() and the Value property (inherited from IValueContainer).

The interface does not have an way to tell this, but for WPF applications the editor must be a FrameworkElement sub-class. I always create an UserControl, make it implement the IValueEditorControl interface and fill its Content with the appropriate editor (usually they already exist, like a TextBox, a CheckBox and so on).

Ok... you did it and now you have the impulse to put the control directly on the window where you need it.

Well, don't do it! That will be a direct reference and you should avoid direct references. You should now register the control on the ValueEditorControlFactoryManager.

I know, it is a long name, but I tried to make it clear: it is the extension manager of factories that create value editor controls. So you should create a factory too. Creating the factory is easy, simple inherit a class from the ValueEditorControlFactory and implement the OnCreate method to return a new instance of your control.

Then you should register that factory into the ValueEditorControlFactoryManager for the right data-type. If you simple want to register it to the entire application, do:

C#
ValueEditorControlFactoryManager.GlobalRegister(typeof(YourDataType), new YourValueEditorFactory());

Note 1: Do not inherit from TextBox (or other similar control) directly, as the control templates registered at application level will not be valid to the sub-classes. Always create an UserControl and put the existing control (the TextBox, for example) as the content.

Note 2: In my implementations I usually create a singleton instance of the factory and I really consider that to be the right approach to avoid creating many instances of a type that has no data or if you want to unregister the factory later, but to make things simpler here I used the new YourValueEditorFactory().

Creating an IRecordEditorControl - The second approach or you want a better layout, drag-and-drop and other functionalities

A record editor control is a control capable of editing an entire record, and it usually changes the content of that record instead of creating new instances.

As happens with the value editor controls, I always create a new UserControl, but in this case I make it implement the IRecordEditorControl interface.

Such interface has the following traits:

  • Record - This should get the actual record or set the record to be edited. There is not a strong rule here, but it is expected that the control creates a clone if needed to avoid changing the original object.
  • ApplyChanges - When the control is put inside another window, it is possible that the other window wants the control to apply the changes it could have made to the record and then return it. Most of my implementations simple call the PropertyBoundControl.ApplyChanges(DataContext) to do the job.
  • IsEditorToolbarVisible - The Save and Cancel button, for example, are considered the toolbar. The record editor should be able to show those buttons (at least the save one). The property exists because when the editor is put inside another control/window that other window may already control the saving, so the editor toolbar should be invisible. My implementations in general always return if the Visibility of a control is Visible and on the set method will use true as Visible and false as Collapsed.
  • Finished - Usually when the user clicks the Save or the Cancel button, the editor form should be closed. But as this is a control, not a form, it can't "close itself". It is not a good practice to find the window to close it either, as the control may be shown using alternative means. So, the window where the control was added will fill the Finished event to close itself when a result is given but the control must be able to call such event when it finishes.
  • Title - Very simple, if the place where this control is presented wants, it can ask for a title. This is not exactly the display name of the item, so the control must give its title (if it wants to search for a display name and put some extra info, it is free to do so).

In the XAML part of the control you will probably use a lot of PropertyBoundControls and maybe some ObjectBoundControls. Then, after finishing the control, the process is again very similar to the IValueEditorControl.

You will need to create a RecordEditorControlFactory, which will create your control, and you should register it with a line similar to this:

C#
ValueEditorControlFactoryManager.GlobalRegister(typeof(YourRecordType), new YourRecordEditorFactory());

How does this framework integrates with MVVM?

Lately I consider impossible to hear WPF without thinking in MVVM, so it is natural to want to know if this framework is MVVM friendly. And the first answer is no, but don't worry.

You can still use MVVM when you are creating your IRecordEditorControls but when I say that the first answer is no, I am refering to the main purpose of MVVM and the main purpose of these components.

MVVM is all about having independent View and Model and the components I am presenting are about creating the View automaticaly from the Model and, if the automatic is not good, then manually creating a better view for that model. So, at the first moment there is no MVVM at all, and it should not be. The purpose is to reduce the amount of code written to do simple things.

Also, another thing that I question myself is the need for a ViewModel in many situations. In a correct MVVM implementation you should never put a binding converter on the View, as the view should avoid anything that can be seen as code related, and you should put the properties with the View expected type on the ViewModel, doing a conversion from a Model property if necessary. For example, a property IsValid (of type bool) may be presented on the ViewModel as OkVisibility (of type Visibility).

In such case I would love to replace the default Binding converter. But, as I can't do that, I can simple tell that every binding should be a ConvertBinding. As the designers will not need to know what Converter to use, I can say they will not be doing programming related stuff. If a conversion does not work, then it is the responsibility of a programmer to register the right conversion on the framework. Of course it will be still better if I could replace WPF default converter, but at least I am avoiding that a designer needs to know what conversion to put in the view, and I am also avoiding rework if the property type changes as the ConvertBinding will search a conversion for the new type.

So, what is the purpose of the ViewModel in that case? If you still need it because you want to present an enum as three different boolean properties, then OK, create it. Maybe if you simple need to invert a boolean value (like HasErrors instead of IsValid), then you will still need to create it. If your Model does not implement INotifyPropertyChanged and you want to implement it in a ViewModel instead of the Model itself, then do it. If not, you can simple bind things directly to the model and let those components deal with the right types and conversions at run-time. If later you need to create a ViewModel, I am sure you will be implementing your own record editor too, so make the record editor instantiate the right ViewModel for that Record (which is the effective Model) and everything will work as expected.

My final word is: You will only need to care about MVVM if you need to create different Views (IRecordEditorControls) and, even then, you may have the option to start without ViewModel, only adding it only if it becomes a need.

Using the framework in WPF - The Hard Part - Try to do only once

Creating controls and registering them may not be real hard, but it is not as easy as simple putting an existing control on the window. But by simple using the framework you will see that any record is edited by a default editor. What if you want to replace that default editor?

Or, what if you want to create a new editor that is valid for a type and any of its sub-types? For example the default editor continues to exist and edits any objects that are not DatabaseRecords, and you want a default editor for DatabaseRecords which is capable of saving the records before returning them.

Also, I talked about the localization a lot. So, how is it possible to add localization in this framework?

To all those questions the answer is: Searchers.

The searchers are invoked when a specific extension (be it a DisplayName, a Description, a record editor factory, etc) is not found for a given item.

The searcher is then responsible for analyzing that item and, if possible, giving a result.

So, considering you have a DatabaseRecordEditor that already uses an ObjectBoundControl to edit all the properties of a record and also has database specific tasks, considering that you already have the factory for it, now you want to register it as the default editor for all the DatabaseRecord sub-types.

A naive implementation will simple register the same factory for each record-type. But then you will need to do a new registration each time a new record type is created. A better implementation will create and register a searcher.

The searcher should:

  • Check the item, doing any casts that are necessary, without throwing exceptions (so the as cast operator should be used instead of normal casts);
  • Will probably need to use some reflection to see if the item conforms with the expected rule (in the exemplified case something like: type.IsSubClassOf(typeof(DatabaseRecord));
  • And, if it conforms, must call the SetResult method, giving the necessary value (independent if it created the value or read it from somewhere else).

I consider this both the hardest and the less problematic part. Usually, for large systems, such things will only happen once for extension type.

So, a localizable searcher for DisplayNames and Descriptions will be created. A searcher to edit database records in general will be created. And it's done.

The default searchers have low priorities (the ones that always generate values have a int.MinValue priority) so any other registered searcher will take precedence over them.

Default Configuration

We can resume the default configuration as the following:

  • bools are presented as checkboxes, enums are presented as comboboxes and all the other types are presented by textboxes considering there are conversions to and from string;
  • On the part of conversions, there are specific conversions from string to int, to long and to decimal, but most conversions will work because TypeConverters are used as the last resort;
  • DisplayNames become a groupbox around the element (except for booleans, which already present DisplayNames) and Descriptions become a ToolTip;
  • The DisplayNames are got from the [DisplayName] attribute, then by the member Name;
  • The Descriptions are got only from the [Description] attribute;
  • The generic IRecordEditorControl edits any object by using an ObjectBoundControl;
  • And the Presenters simple create a Window to show the given object. For most data types this will end-up using the WPF content-templates to give the proper visualization.

The first thing I did in my application was a call to RecordEditor.Edit() but I got an exception!

There is a little catch on how the WPF components work, and I simple can't get rid of it.

The .Net framework does not initialize a library before it is used for the first time and, even if the RecordEditor class seems to be part of my WPF components, it is not, it is part of the base library.

The Pfz.WpfControls library, when initialized, registers the default WPF controls to edit records and values. So, if you already used a DisplayNameWatcher, a DescriptionWatcher or some control of the Pfz.WpfControls library, everything will work. If not, you should initialize the library by yourself.

It is very simple, put the following line on the App class and everything will be fine:

C#
Pfz.WpfControls.DefaultConfiguration.Initialize();

If you want to try, you can replace the App.cs code on the sample project by the following code:

C#
using System;
using System.Windows;
using Pfz.FactoryManagement;

namespace RuntimeExtensionsSample
{
  public partial class App : Application
  {
    static App()
    {
      Pfz.WpfControls.DefaultConfiguration.Initialize();
        RecordEditor.Edit
        (
          new 
          {
            Name="Test", 
            Address="Unknown",
            Apartment=123,
            Smokes=true,
            PreferredDateTimeKind=DateTimeKind.Local
          }
        );
    }
  }
}

And you should see a maximized window that looks like this:

Image 8

And yes, it is editing an anonymous type, which is read-only and, if you noticed, the title is the name of such anonymous type.

Performance

Maybe you are already worried about performance. Maybe you are thinking that if you create a big system using all those run-time extensions it will become extremely slow.

Well, I don't have something to show, but I can talk about some things:

  1. Even if the searchers are slow, after a result is found for a specific item, it is cached. So the next search for the same item will be extremely fast. Surely, the fast is your searcher implementation, the better will be the initial search performance;
  2. Recreating thousands of controls using a normal searchers takes less than one second. And considering you are not loading thousands of windows at the same time, you will usually have a very limited number of dynamic controls.

The Sample

The sample application registers and unregisters many of the default configurations only to show that the same declared window can greatly change by some configuration changes.

It also adds application specific configurations showing, for example, that it is possible to present editors as normal/modal windows or to incorporate them into the main window and how it is possible to apply localization in your application.

But, as a sample, it is very incomplete. I didn't do the translation of every text in the application. Also, I complained about RAD and I did the same thing: some texts are written directly into the main window, without using the {DisplayNameWatcher} so they are simple not localizable.

Finally, in this application I am always registering every configuration globally. For web servers/services, at least the translation searchers must be registered locally, so different users can have different translations.

Yet I hope it is useful to show how the framework can be used.

Some extras

I was already criticized sometimes because I put my personal library as part of the download. And I am doing it again.

In this case, you will also benetif from the BinarySerializationManagement namespace, an actionless framework that you can use to create binary serialization of any object, as long as it is possible to serialize it (serializing a ReaderWriterLock is not a good idea) and the ConversionManagement namespace, another actionless framework that you can use to convert objects from one type to another, which is already used by the WPF controls.

Yet, there is more. In some cases the extra classes and namespaces may be easy to cut-off. For example, the Remoting may not be of interest to you, and it is really easy to remove as the actionless frameworks don't depend on it. But the Caching or the Threading namespaces are not so easy, as the implementation of the actionless frameworks themselves use them. But I really hope that's not a problem. I never used most of the classes in the System.dll but I still use the full version of the library.

Using the framework - Creating your own extensions

The WPF sample is great (at least I think it is) but you don't want to use WPF, you don't want my factories but you are still interested in adding run-time extensions to your objects.

So, how do you do that?

  1. Create a new class.

    Your extension should be a new class. Even if I did not force a specific base type, creating an extension of type string is bad. Even if the important info is an string, you should still create a type to contain that info.

    That's why I have the DisplayName and Description classes. They both contain a string, but with their type I know exactly what kind of extension I am getting.

  2. How many properties are you adding?

    If you simple want to add a boolean flag over your items, like my IsRemotable info, then don't put any attribute in your type, and make it have a singleton instance.

    If the extension is added, then the value is true. If the value is not added, then the value is false. Simple, isn't?

    If on the other hand you want to put many properties, then add all those properties to your extension type.

  3. Third step: Start using the RuntimeExtensionManager<YourExtension>

    You can already start putting your extension over other objects by using the RuntimeExtensionManager generic type, using your extension type as the generic parameter.

    You can add extensions to already existing instances, be it simple objects, types or anything else.

    But you will probably consider the RuntimeExtensionManager type too generic. Maybe you created your extension having in mind it will be put over types, not over entire instances. Also, writing RuntimeExtensionManager<YourExtensionType> is a lot of work.

    So, here is the moment you will probably want to create your own types. The ones I propose are:

    • YourExtensionManager: You can create it by inheriting from the ExtensionManager itself. This will help reducing the amount of code written to have access to your runtime extension manager, even if you want to call the static methods. That's what I've done, but I will say that it will be even better if you don't inherit from the RuntimeExtensionManager itself and instead write methods that redirect to it, using only the valid types as input (so, instead of registering items to any object, you register it only over Types, for example);
    • YourExtensionSearcher: Another thing to reduce the amount of text written, but can also serve to provide a default implementation. Inherit a class from the RuntimeExtensionSearcher<YourExtension>, but keep it abstract. So, if people want to implement a new searcher, they can inherit from your base class (which can also do some pre-validations if needed);
    • YourExtensionWatcher: If you think that people may be interested about changes on the extension applied to their objects, you will probably want to inherit from the RuntimeExtensionWatcher<YourExtension> and provide a Value property. When changes are done, the watcher generates a PropertyChanged event passing that the changed property is the Value. It does not has a Value property because you may want to get an inner item of your extension as the default result (for example, in the DisplayName case, I return the DisplayName.Value directly).

Version History

  • November 5, 2012: MemberInfos are now stored in normal dictionaries to correct a bug in which the MemberInfos were collected, effectively losing the RuntimeExtensions when they were needed in a future call;
  • November second, 2012: Added the Default Configuration topic;
  • November first, 2012: First 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 (Senior) Microsoft
United States United States
I started to program computers when I was 11 years old, as a hobbyist, programming in AMOS Basic and Blitz Basic for Amiga.
At 12 I had my first try with assembler, but it was too difficult at the time. Then, in the same year, I learned C and, after learning C, I was finally able to learn assembler (for Motorola 680x0).
Not sure, but probably between 12 and 13, I started to learn C++. I always programmed "in an object oriented way", but using function pointers instead of virtual methods.

At 15 I started to learn Pascal at school and to use Delphi. At 16 I started my first internship (using Delphi). At 18 I started to work professionally using C++ and since then I've developed my programming skills as a professional developer in C++ and C#, generally creating libraries that help other developers do their work easier, faster and with less errors.

Want more info or simply want to contact me?
Take a look at: http://paulozemek.azurewebsites.net/
Or e-mail me at: paulozemek@outlook.com

Codeproject MVP 2012, 2015 & 2016
Microsoft MVP 2013-2014 (in October 2014 I started working at Microsoft, so I can't be a Microsoft MVP anymore).

Comments and Discussions

 
-- There are no messages in this forum --