Regarding the Download
If you're interested in playing with the framework, after you download the code, please select Cx.Designer.App as the startup project.
A Special Thank You
I would like to thank Bill Woodruff for his incredible effort in proofreading this article and providing numerous suggestions to improve and clarify the content. Thank you Bill!
Introduction
Part II of this series on the Cx framework is really an investigation into putting together a simple wire-up tool (see Part I for what I mean about wire-up) using Cx itself, and the things I'm discovering about an Inversion of Control (IoC) or Dependency Injection (DI) framework. So this is really about architecture and application design, and the issues I discuss here are applicable to any of the popular IoC frameworks I mentioned in Part I. Pretty much, I'm just using Cx as a way of exploring IoC/DI architecture issues.
Why don't I just write a designer for CAB or Spring.NET and become famous? Well, when I'm done with this one, maybe it'll be easier to write one for the other frameworks--using Cx of course!
Prerequisites
The reader should be somewhat familiar with the Inversion of Control pattern (see below), Dependency Injection, XAML, Reflection, various Design Patterns that are used to create a clean separation between components, and have worked (or thought about) enterprise level application development (not to throw out a buzzword, but it is why one considers using an IoC framework).
What is Inversion of Control?
From Wikipedia:
In traditional programming, the flow is controlled by a central piece of code. Using Inversion of Control, this central control as a design principle is left behind. Although the caller will eventually get its answer, how and when is out of the control of the caller. It is the callee who decides to answer how and when. The principle of Inversion of Control is also known as the Hollywood Principle. Inversion of Control as a design guideline serves the following purposes:
- There is a decoupling of the execution of a certain task from the implementation.
- Every system can focus on what it is designed for.
- A system does not make assumptions about what other systems do or should do.
- Replacing a system will have no side effect on other systems.
What is Dependency Injection?
From Wikipedia:
Conventionally, if an object, in order to accomplish a certain task, needs a particular service, it will also be responsible for instantiating and disposing of (removing from memory, closing streams, etc.) the service, therefore making it more complex and hard to maintain. Ideally, this object would not need to manage its services life cycle but just have a reference to an implementation of the said services and invoke its relevant behaviors. Dependency Injection is a Design Pattern that can be applied to provide an object with its dependencies and move the code related to the service life cycle to a more appropriate place.
Why This Article?
This is an exploration of Inversion of Control (IoC) and Dependency Injection (DI) best practices that evolved by writing a dynamic composition framework and then using it to create a poor-man's designer to create the metadata.
After working with Microsoft's CAB and Spring.NET, I frankly don't see the point:
- it seems like re-inventing the services wheel all over again,
- but in a way that makes the application more difficult to debug because of the runtime composition,
- and requires you to work in XML instead of supplying you with a tool to define the metadata.
and I want to know why these frameworks are better than using a more traditional services approach. Certainly, what Wikipedia says above about IoC/DI: "to provide an object with its dependencies and move the code related to the service life cycle to a more appropriate place", could be easily corrected without creating a huge framework.
I also want to learn what the best practices are in using these frameworks--just because you have a snazzy framework doesn't mean your application is going to be better. So, for some of the questions I have, I decided I would write Cx, a dynamic composition framework, and learn about IoC and DI from the roots up, rather than work with someone else's architecture from the top, down. And, I want to use my framework to address my biggest complaint about the IoC frameworks out there: they don't have any design tools!
Best Practices
I put this section first for those that want to cut to the chase. This summarizes the best practices when working with an IoC framework, and is the result of many hours of working with Cx and writing the Cx Designer. If you want to read more about how and why I came up with these best practices, well, that's what the rest of the article is for.
Some of these sub-sections have only one or two points to them. That's fine for now, as I expect these sections to be filled out as I continue working with Cx and other IoC frameworks.
Before You Start Coding Practices
- Decide whether you want to use interfaces (method calls) for initiating activities or command patterns, and try to stick with one or the other for consistency. The determining factor is probably based on the anticipated complexity of the application and the number of developers and development teams working on the project.
- If you have to interact with a component directly, always use an interface. This makes everything easier. Let's say you want to mock the component, or you have several different implementations you need to instantiate depending on a context. You can't do that if you don't use an interface, so plan for the future when you write your classes.
- Look at how entangled your application becomes with your IoC/DI framework. Does it get so entangled that your code loses all re-usability outside of the context of the framework? If so, you may want to consider the repercussions of that choice.
- Do some planning and storyboarding so you can identify early, rather than later, interface methods, commands, producers and consumers, and so forth.
- Realize that with dependency injection of component instances, you are implicitly accepting a certain architectural paradigm (see Dependency Injection below). Make a conscious choice regarding when to use component instance injection and when to decouple components by using a command pattern.
Project Layout Practices
- Put your interfaces into a separate assembly. This is important for interfaces that represent "plug-ins", or what I've been calling components. Putting the interface in the same project as the implementing class sort of defeats the purpose of having an interface and separating it from the component.
- Keep your component implementation lightweight as much as possible, and put some thought into how you organize them into different assemblies. Think about putting your UI components into one or more assemblies and your view-model into a different assembly. Think about how you want to organize your model.
Performance Practices
- Look carefully at those "make it easier on the developer" little helpers. Does it mean that the framework uses Reflection just to make something more convenient for you?
- Identify early on the performance issues of the IoC/DI framework: where does it tangibly affect performance, and where does it merely act as a facilitator.
- Think of your IoC/DI framework as a manager--bad managers make everyone's work harder and slower while claiming to improve efficiency, while good managers facilitate so that people can work faster and the work is easier.
Ease of Use Practices
- Look at the complexity of the metadata that glues everything together. Does it do so much that it hides the intent of the application from the developer? If so, this means that new programmers have a steeper learning curve and eventually, even the veterans forget why something was done in a particular way.
Generalization Practices
- Decide the depth of your generalization. Deeply generalized applications are harder to glean the intent of, but the components potentially have a life that goes beyond the lifetime of the application. Shallow generalization is easier to walk into and support, but not really re-usable outside of the specific application.
Security Practices
- Deal with issues like security early on. Are all commands executable in all security contexts?
Lifetime Practices
- Consider the lifetime of an object and whether or not your data producer component needs to clone the object before sending it off in a command payload. Consumers shouldn't have to worry about whether their collection or data values are going to be clobbered the next time the producer component produces the object or modifies the object.
- Be cognizant of any component initialization requirement. If a component is cached, any initialization that occurs in the constructor will occur only once.
Terminology
Producer/Consumer
I'm using the terms "producer" and "consumer", if not incorrectly, then rather loosely. This is not a producer/consumer problem (moving data through a shared buffer). This is closer to a publisher/subscriber pattern, but not a perfect fit either as the messages are not asynchronous, and many of the "messages" are actually commands--no actual content to the "message".
So, my definition of a "producer" is a class that, at least in this implementation, fires an event, optionally with a data payload. My definition of a consumer is a method with a parameter signature that matches the event delegate and can therefore be wired up to "consume" the event.
Dynamic Composition Framework
Here's a mouthful:
"Dynamic composition is a key feature of service-oriented computing (SOC) where services are discovered and composed at runtime. Current standards and methods for service compositions require statically binding published WSDL interfaces into compositions in BPEL. In this paper, we present our early result of a framework for dynamic composition on the enterprise service bus (ESB). We present the design and implementation of the Dynamic Composition Handler."
But that's basically what I mean when I use that term: constructing an application at runtime from components that are otherwise not cognizant of each other.
Deep Generalization
In a deeply generalized application, you need some way of communicating changes between components in a generalized yet structured manner. So, what I mean by this are several things:
- Constructing a component so that it is abstracted out of the specific application requirements.
- Constructing a component so that it does not have any dependencies on any other components. You might bundle your components into a single assembly, but in no way will any one component require anything in another component. Some minor dependencies on the framework (for attributes and helpers) is acceptable.
- Completely separating out any business logic from the component. This applies mostly to UI components, and allows you to swap out the business logic without touching the component rendering.
- Completely defining the way data gets in and out of the component and the activities the component can initiate. Otherwise known as Design by Contract, except that here we use metadata and/or attributes to provide information to the design tool as to the contract (similar to how you decorate properties in your class so that they can be used by the Visual Studio designer).
Decisions, Decisions
What I want to accomplish with the design tool is not a pretty UI (see the screenshot above) but rather a functional UI for creating Cx metadata: components, properties of those components, and producer/consumer wire-ups. I achieved that goal in the very end: the "declared events" UI was created entirely in the designer, except for one hand-tweak that I had to make to support a generic type; see A Couple Missing Features.
Decision Point: What To Modularize?
It's my tendency to modularize (or generalize) at a rather deep level, so I started of with the idea that the UI would consist of some visualization of three lists:
- the components themselves, possibly a
ListView
with the name, class name, and assembly name as columns; - the producers (events) in those components;
- and the consumers (event handlers) in those components.
It occurred to me that the lists themselves are probably a ListBox
or ListView
control, and they ought to have a label describing the list. Seems pretty basic. Beyond that, I would need some additional functionality for each list:
- The user would need to add and remove components from the list, requiring support for both business and visual components;
- The user can select a producer;
- The consumer list would have checkboxes so the user could check/uncheck consumers wired up to the selected producer.
And so, I was thinking that I would create a general purpose user control consisting of a list with a label. But at some point, I realized, why should I be thinking of the UI in such general terms? Would it not be acceptable to create the UI with all three lists, already customized in their display? Here are my thoughts so far on creating a deeply generalized UI:
Pros of Deep Generalization
- Very flexible in the layout of the UI
- Easily replaceable components for each UI component
- Create a library of re-usable templates (that is what user controls are, after all)
Cons
- The "intent" of the UI is buried in the XML layout
- Can take things to an extreme: minimal value added in wrapping a control inside a user control
- Customization of the template (user control) is pushed out into the XML layout
- Possibly overly generalized collection structures forcing behavior into list item classes
The cons list is worthy of further exploration. I opted for the deep generalization approach so I could explore these issues further.
The Intent of the UI
Can you tell the intent of the UI from this XML (abbreviated for readability, ha ha):
<Components>
<VisualComponent Name="ComponentList" ComponentName="ListView"
Location="10, 10" Size="300, 180"/>
<VisualComponent Name="ProducerList" ComponentName="List"
Location="10, 200" Size="200, 200"/>
<VisualComponent Name="ConsumerList" ComponentName="List"
Location="230, 200" Size="200, 200"/>
<BusinessComponent Name="CxDesigner" ComponentName="CxDesigner" />
</Components>
Barely, and it certainly is a lot easier if you had for reference a screenshot of the UI, like the one at the beginning of the article.
Separating Application Context from the Control
I could create a user control with my lists all nicely pre-configured, but then my user control would be very specific to the designer application, and changing one aspect of the control (such as one of the lists) would require re-implementing the entire control. However, the advantage is that I don't have to deal with customizing each list the way I want to see it, which includes:
- the label associated with the list
- any header configuration for a
ListView
- the default
ListView
display mode - whether the list should have checkboxes
- whether the data is editable
- etc.
However, if I implement a deeply generalized pattern for my UI in which there are generalized list controls that are decoupled from the context of the application, I am now required to specify the application context separate from the control. This could be done imperatively (in code) or declaratively (in metadata). For example, I could define all these additional property values in the XML for the configuration ListView
:
<VisualComponent
Name="ComponentList"
ComponentName="ListView"
Assembly="..\..\..\Cx.Designer.Components\bin\debug\Cx.Designer.Components.dll"
Location="10, 10"
Size="300, 180">
<Property Name="HeaderText">
<Item Value="Name"/>
<Item Value="Component Name"/>
<Item Value="Assembly"/>
</Property>
<Property Name="HeaderWidths">
<Item value="200"/>
<Item Value="200"/>
<Item Value="600"/>
</Property>
</VisualComponent>
So by creating a generalized component, I've separated the application context from the control, requiring some other mechanism to introduce that context back into the control's visual appearance. Having chosen metadata (XML) as the vehicle for this context, I've added complexity by creating a custom format for declaring property values and handling lists like the header text and widths.
Properties are now something else that the designer should handle, so the developer doesn't have to go in and edit the XML by hand. So I've made my life more complicated.
Overly Generalized Collection Structures
If I create a custom user control specifically for the Cx designer, then I might as well manage my collections (components, producers, consumers) using, at minimum, the Cx interfaces. However, if I use highly generalized components (with the intent that I might re-use these components for other apps), then my collection management might work at the IEnumerable
(or similar) level, and the way I display the text associated with the items in a collection now falls on the item in the collection, usually implemented by overriding the ToString
method.
And that's exactly what ended up happening. My list component works with an IEnumerable
in the OnData
handler:
[CxConsumer]
public void OnData(object sender, CxEventArgs<IEnumerable> args)
{
lbList.Items.Clear();
foreach (object obj in args.Data)
{
lbList.Items.Add(obj);
}
}
And the item class must now override the ToString()
implementation so that I get the desired visualization (using the CxProducer
class as an example):
public class CxProducer
{
public ICxComponent Component { get; protected set; }
public string Name { get; protected set; }
public CxProducer(ICxComponent comp, string name)
{
Component = comp;
Name = name;
}
public override string ToString()
{
return Component.Name + "." + Name;
}
}
So I've succeeded in pushing the problem of application specificity into a different area, which may not be the best solution because I've constrained myself, in a completely different part of the code, to the visualization of the producer name. In other words, I've added complexity with regards to the visualization.
You might ask, why not use Generics to specify the collection type? I explore this with a business component that converts a list to a sortable list.
Decision Point: How To Do the Final Initialization?
In my initialization of the designer application, I have this code:
CxApp designer = CxApp.Initialize(Path.GetFullPath("cxdesigner.xml"));
ICxDesigner designerComponent = designer.GetComponent<ICxDesigner>();
designerComponent.LoadComponents(metadataFilename);
Taking Spring.NET as an example, you can specify the initialization method in the XML file that defines the component references. I'm not keen on dumping everything into the XML file:
Pros of Doing it in Code
- Easier to debug, in my opinion.
- Much more explicit as to where the initialization is being called.
- To debug, you can set a breakpoint where the initialization method is being called rather than inside the component's method; if the framework makes the call using Reflection, you lose this ability.
- Are there initialization sequence issues? For example, have all the wire-ups occurred and have other components already been initialized? This last point can force you to specify dependencies in the XML file, which we see with Microsoft's CAB metadata.
- If the initialization throws an exception, your stack trace is shorter. For example, the stack trace when using Spring.NET has some 50 or more calls in the Spring.NET framework between your component's constructor/initialization and, at the other end, something in your application that started the whole process.
- If the initialization throws an exception, you can handle it, rather than it being re-wrapped in the framework's exception handling mechanism.
- Simple is better.
I've fought with some of these issues with Spring.NET and Microsoft's CAB. It can be frustrating.
Cons
- Can't really think of any, other than that putting everything into the XML definition is cool.
For example, in the designer class, I have the following initialization:
protected void LoadComponents()
{
LoadVisualComponents();
LoadBusinessComponents();
RaiseComponentsLoaded();
BuildProducerConsumerList();
RaiseProducerListLoaded();
RaiseConsumerListLoaded();
}
Notice the Raise... calls. This fires events that, by the time we get here, we know that components listening to these events are instantiated and the events are wired up.
Decision Point: Do You Buy Into The Framework Philosophy?
In a deeply generalized application, you need some way of communicating change between components in a generalized yet structured manner. The Cx framework uses events, but there are other command patterns you can use as well. Alternatively, you can use .NET's data binding paradigm, or simply acquire instances of the different components (often through dependency injection) to get at the property values of an instance.
Regardless, you have to do something that builds that dependency. In the Cx framework, you create events in your components, for example:
[CxEvent]
public event CxEnumerableDlgt ProducerListLoaded;
[CxEvent]
public event CxEnumerableDlgt ConsumerListLoaded;
Write the raise methods:
protected void RaiseProducerListLoaded()
{
if (ProducerListLoaded != null)
{
ProducerListLoaded(this, new CxEventArgs<IEnumerable>(producers));
}
}
protected void RaiseConsumerListLoaded()
{
if (ConsumerListLoaded != null)
{
ConsumerListLoaded(this, new CxEventArgs<IEnumerable>(consumers));
}
}
And wire them up, for example:
<Wireups>
<WireUp Producer="CxDesigner.ComponentsAdded" Consumer="ComponentList.OnData"/>
<WireUp Producer="CxDesigner.ProducerListLoaded" Consumer="ProducerList.OnData"/>
<WireUp Producer="CxDesigner.ConsumerListLoaded" Consumer="ConsumerList.OnData"/>
</Wireups>
That's a lot of work, and yes the framework could help you with that--for example, I might ask the framework to create a particular event for me that I could then fire off. Something like:
producerListLoadedEvent = EventHelpers.CreateEvent<IEnumerable>(this, "ProducerListLoaded");
...
producerListLoadedEvent.Fire(producers);
and be done with it. We would still have to tell the framework using attributes about the event we're exposing programmatically:
[CxExplicitEvent("ProducerListLoaded")]
[CxExplicitEvent("ConsumerListLoaded")]
public class CxDesigner : CxApp, ICxBusinessComponentClass, ICxDesigner
something I already do with event transformations, which I added in this version.
Since this looked useful, I actually did implement the support for the above code example. There's the annoying switch
statement because Generics aren't like C++ templates, so we can't create a generic delegate. And yes, there are other common types I'm not showing in this code example:
public static EventHelper CreateEvent<T>(object component, string eventName)
{
EventHelper ret = null;
switch (typeof(T).Name)
{
case "String":
ret = new StringEventHelper(component);
break;
case "IEnumerable":
ret = new EnumerableEventHelper(component);
break;
}
componentEventHelpers[new ComponentKey(component, eventName)] = ret;
return ret;
}
And for the same reason, the Fire
method takes an object
for a parameter, which means we're boxing/unboxing intrinsic types (not shown in this example, but notice the cast):
public override void Fire(object data)
{
if (Event != null)
{
Event(Component, new CxEventArgs<IEnumerable>((IEnumerable)data));
}
}
I feel like there must be a way to use Generics with delegates, but the answer is not the EventHandler<T>
class, because it doesn't handle intrinsic types!
Regarding what I mentioned above about event transformations, here's an example (abbreviated) of the CxExplicitEvent
attribute, because we're asking the framework to transform the TextBox.TextChanged
event with a signature of (object
, EventArgs
) to a Cx event with signature (object
, CxEventArgs<string>
).
[CxExplicitEvent("DisplayTextChanged")]
public partial class TextDisplay : UserControl, ICxVisualComponentClass
{
public TextDisplay()
{
EventHelpers.Transform(this, tbDisplay, "TextChanged",
"Text").To("DisplayTextChanged");
}
This is a nifty little helper that wires up to the TextBox.TextChanged
event and fires off a Cx event of the specified name. No further work on the component wrapper is necessary.
Decision Point: What Does the Framework Do To Make Life Better?
In the code example above for the CxExplicitEvent
attribute, I did this because I wanted to make my life easier. I didn't want to have to write the code to take a general purpose control event and repackage it into a Cx event. While that makes my life easier as a developer, the drawback is that the framework has to use Reflection to get at the property value, which is slower.
protected void CommonHandler(object sender, System.EventArgs e)
{
string val = PropertyInfo.GetValue(Object, null) as string;
if (Event != null)
{
Event(Component, new CxEventArgs<string>(val));
}
}
So if you find yourself using a mechanism provided by the framework to make your life easier (like an AOP feature), ask yourself how are you:
- affecting performance (Reflection, boxing/unboxing, other overheads)
- affecting the ability to debug the code (the automatic property getter/setter feature in C# 3.0 is a great example--you can't set a breakpoint on them)
- affecting readability, as in, how the heck does this property get initialized or this event wired up (I pull my hair out with Spring.NET on that issue all the time)
- Is the (supposed) improvement in maintainability/flexibility/extensibility really worth it?
For example, look at what goes on behind the scenes just to do the initialization to support that convenient event transformation syntax I described above:
public static EventHelper Transform(object component, object obj,
string objectEventName, string objectPropertyName)
{
Type objType=obj.GetType();
EventInfo ei = objType.GetEvent(objectEventName);
PropertyInfo pi = objType.GetProperty(objectPropertyName);
EventHelper helper = null;
switch (pi.PropertyType.Name)
{
case "String":
helper = new StringEventHelper(component, obj, ei, pi);
break;
default:
throw new CxException("No implementation for transforming the event " +
objectEventName + " to a Cx event of type " +
pi.PropertyType.Name);
}
return helper;
}
Lots of Reflection, and that ugly switch
statement. Well, at least I'm not constrained to do things this way! But given the convenience, how many developers really think about the cost of that convenience?
Decision Point: How Entangled With the Framework Do You Want To Get?
In the first version of Cx, the framework was really just a wire-up tool and helped with instantiating components. Other than that, it really stayed out of the way. Now, we're decorating classes, events, and methods with attributes so the designer can discover them, and we've added some helper functions to make life easier for the developer dealing with Cx's style of wire-up, namely events. Furthermore, we've made the XML more complicated because we want to support deeply generalized components, which means initializing properties in the metadata.
The point being, the more invasive the framework becomes with your application, the more dependent upon the framework your application becomes throughout its lifetime. Do you really want that? Are there other teams that might want to re-use your code without buying into your chosen IoC framework as well?
So far, Cx can be used in a very non-invasive manner. You don't need to use the attributes (you can write the XML yourself), and you don't need to use the fancy event transformations and helpers that I added in this version. And quite frankly, the less of the framework you use in terms of attributes and shortcuts, the more re-usable your component is outside of the Cx framework! That's a good thing if re-usability outside of the IoC framework is a project requirement, otherwise it doesn't much matter. And of course, don't forget that you're buying into the philosophy of the framework as well--in this case, Cx's eventing mechanism for communication.
Decision Point: Interfaces and Communication
The point of the framework is to provide you with the tools to properly "componentize" your application, because somebody in management read about how IoC and DI solves all the problems associated with writing on-time, on-budget, enterprise applications. OK, so far so good. If you want to do it right though:
- use interfaces (see Interfaces below for why you wouldn't need to use them);
- you need to plan very carefully what your interfaces expose (constant refactoring: oops, I need access to that method);
- you need to plan very carefully what you're communicating between components (constant refactoring: oops, that would be useful).
I'm finding that this is far easier said than done. If you're using an IoC/DI framework, then I'm assuming you actually are developing an enterprise application (rather than piddling around like I am), and in that case, you probably have many developers spread across many teams, each developing components, modules, infrastructure code, data access layers, presentation layers, and so forth. You've got some thinking to do:
How do your components communicate with each other?
It is possible to implement just about everything in such a decoupled manner that your components don't even need to know whom they are talking to and, in fact, whether anyone is listening. You can use command patterns to initiate activity in another component, and you can use property change events to move data around between components. At this level of abstraction, you don't actually need that many interfaces because your components are so autonomous, they never directly communicate with another component through a standard method call. Personally, I like that approach.
For example, I use an interface for the designer's business component, and I acquire the component using a Cx framework call:
ICxDesigner designerComponent = designer.GetComponent<ICxDesigner>();
designerComponent.LoadComponents(Path.GetFullPath("cxdesigner.xml"));
But I don't need to do it this way. Instead, I could create a command event in my app:
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
CxApp designer = CxApp.Initialize(Path.GetFullPath("cxdesigner.xml"));
App app = designer.GetComponent<App>();
app.Initialize(Path.GetFullPath("cxdesigner.xml"));
Application.Run(new Form1(designer.VisualComponents));
}
}
public class App : ICxBusinessComponentClass
{
protected EventHelper initialize;
public App()
{
initialize = EventHelpers.CreateEvent<string>(this, "Initialize");
}
public void Initialize(string fn)
{
initialize.Fire(fn);
}
}
And in the designer business component, the handler:
[CxConsumer]
public void LoadComponentsHandler(object sender, CxEventArgs<string> args)
{
LoadComponents(args.Data);
}
Now, let's look at the XML. I'm initializing the App
class as a business component:
<BusinessComponent Name="App" ComponentName="App" Assembly="Cx.Designer.App.exe"/>
and wiring up the producer with the consumer:
<WireUp Producer="App.Initialize" Consumer="CxDesigner.LoadComponentsHandler"/>
So, with a little extra code, I accomplished something very interesting: I completely removed the need for the application to have an interface to the designer business component!
I really want to emphasize this point: by changing the architecture from an interface call to a command pattern, I've eliminated the need for the interface. By changing the architecture from using an interface to using a command pattern, I've removed the need to use an interface, and I've improved my code. I have:
- Made my application initialization more powerful, because other components can hook in to my application's initialization command;
- Made the code simpler by eliminating the interface (one less assembly reference, one less piece of code to maintain);
- Made my designer more robust by using a command pattern to initiate activities rather than method calls;
- Degraded performance by using an event mechanism rather than a direct method call;
- Made it more confusing to the uninitiated developer who will wonder, how the heck does the UI get its data?
Now, here's a really interesting final point: By using an event driven command pattern, the framework can add logging of all interaction between components automatically. I'll actually implement that in the next installment (which will be a shorter article!).
Do You Really Need Refactoring?
In an enterprise, multiple teams environment, if you go with the interface approach, you probably will be, for a large part of the time, in a holding pattern of refactoring interfaces as features get added and rebuilding all the projects with dependencies on those components. If you go with the command pattern approach (whatever the mechanism is), you don't need all those interfaces to initiate activities in components, and instead, your teams simply update their assemblies, and you can use the designer to wire up the new functionality to your component's events.
Ah, but there's a catch! You may still need to touch your project to add an event that can be fired for the new commands! But now, you have a choice--if you don't need that command exposed in a component, you actually don't have to touch your code, and better yet, your project won't need to be rebuilt.
Earlier I mentioned that making components more general pushes the initialization of those components into the XML. This isn't quite true--you could do the final initialization in code. Frankly, I think either option has its problems. If you do the final initialization in code:
- You are imperatively coding the initialization, which reduces flexibility (if you needed it to begin with).
- The code needs access to the components, which means you're probably going to be implementing interfaces simply for final initialization.
- When the component is loaded, the code that does the final initialization needs to execute. The mechanism for that is probably going to be obtuse.
If you do the initialization in the metadata:
- Well, it's in metadata not code, so it's another format that needs to be handled by the parser.
- The designer needs to support this. That means another UI for managing properties and framework attributes to discover designer-visible properties.
- You don't get intellisense or compile-time syntax checking.
- But it's probably easier to reconfigure the application as requirements change.
And of course, by opening up a mechanism in the metadata to support property initialization, we open up what I consider to be somewhat of a Pandora's Box: dependency injection (DI), meaning a property can be initialized with not just a simple intrinsic value but with an instance of another component. Why do I consider this to be a Pandora's Box? Because it can be used to easily defeat the architectural goals:
- it's easy to inject instances that do not implement interfaces, breaking an important IoC rule and making it impossible to mock the instance or replace it;
- since interfaces should (must) be used, the benefits of a command pattern with complete decoupling are circumvented;
- since the command pattern is probably not being used, you lose the automatic logging of all communication between components;
- and, since a command pattern isn't being used, you lose the ability for other components to listen to commands.
Do We Need Dependency Injection?
You don't. In an event-based dynamic composition framework, you should really just need to fire an event for some other component to do something and in turn get you whatever your component is looking for, via an event as well. Injection of component instances into properties of other components forces an architecture upon the application without the architects consciously making a decision to buy into this paradigm. On the other hand, as I mentioned previously, an event-based command pattern has an overhead that affects performance and requires some additional coding, so in reality, any large application is probably going to balance component reference injection and command pattern usage in a way that intelligently considers the pros and cons to each approach.
Property Initialization in XML
One of the comments I received in my previous article was that the XML was clean and readable. So, I decided to put property initialization in a separate section, which is different than the example earlier in the article. For example, the component list final initialization is described like this:
<Properties Component="ComponentList">
<Property Name="Label" Value="Components:"/>
<Property Name="HeaderText">
<Item Value="Name"/>
<Item Value="Component Name"/>
<Item Value="Assembly"/>
</Property>
<Property Name="HeaderWidths">
<Item value="200"/>
<Item Value="200"/>
<Item Value="600"/>
</Property>
<Property Name="DataPropertyNames">
<Item Value="Name"/>
<Item Value="ComponentName"/>
<Item Value="AssemblyFilename"/>
</Property>
</Properties>
Frankly, I think this makes it easier for the designer to work with the XML as well, and it removes all this stuff from the component list. While XML is great in the ability to create complex object graphs, I've found that in reality (for both the developer and the underlying code), it's often better to separate out the object graph into different sections.
The properties are initialized in the framework:
protected void FinalComponentInitialization()
{
Verify.IsNotNull(xdoc, "Configuration XML file must be loaded " +
"before loading business components.");
foreach (ICxComponent comp in components.Values)
{
foreach (var property in from el in xdoc.Root.Elements("Properties")
where el.Attribute("Component").Value == comp.Name
select new
{
Property = el.Elements("Property")
})
{
foreach (var prop in from el2 in property.Property
select new
{
Name=el2.Attribute("Name").Value,
Value=(el2.Attribute("Value")==null ? null : el2.Attribute("Value").Value),
Items=el2.Elements("Item"),
})
{
if (!String.IsNullOrEmpty(prop.Value))
{
InitializeProperty(comp, prop.Name, prop.Value);
}
else
{
List<string> itemVals = new List<string>(from el3 in
prop.Items select el3.Attribute("Value").Value);
InitializeProperty(comp, prop.Name, itemVals);
}
}
}
}
}
protected void InitializeProperty(ICxComponent comp, string propName, object propValue)
{
PropertyInfo pi = comp.Instance.GetType().GetProperty(propName,
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
Verify.IsNotNull(pi, "The component " + comp.ComponentName +
" does not implement the property " + propName);
pi.SetValue(comp.Instance, propValue, null);
}
Oh, and did I mention that there is, of course, a performance hit when doing all this initialization via Reflection?
Models: What the Dev Wants vs. What the UI Wants vs. What the Data Service Wants
One of the questions I keep asking myself as I go through this exercise is: if we completely decouple the components, then who is the keeper of the model? Is there actually a centralized place for the model? For example, if the data service is 100% decoupled from the model (no interfaces, just property change notifications and event commands), then the model ends up living in two places: the data service's model is updated with changes being made to the model, and the model is updated when something new is acquired from the data store. Of course, the idea of a model-specific data service is anathema to me anyways. Yes, you read that correctly. I'll investigate this issue some day, but for now, suffice it to say that, if you ignore my extreme architecture practices, you will probably want an interface on your model which can be utilized by the data service.
Forcing the Issue: Persistence of the Component Lists
What better way to explore this issue than by forcing it, by moving the persistence (at the moment, just building the lists) out of the Cx framework into a Cx component. Currently, the model comes from an XML file, but if I generalize this, we could replace the file component with a different persistence service, for example a database or some other file format. This forces me to at least explore the best practices with model management in a dynamic composition (borrowing that expression from the WEF project) framework.
Bootstrapping
Attempting this separation results in an immediate problem: the loader, being a component, can't be initialized because the cart is now in front of the horse: the framework hasn't been initialized because it needs the data from the loader! So, we need a bootstrap process now, which does several things:
- Loads the data service assembly;
- Tells it to load the metadata, our component configuration information;
- Hands over the data objects to the Cx framework;
- Tells the Cx framework to do the component initialization--class instantiation, wire-ups, and final property initialization.
We must use an interface for the data service, since the framework is not up and running. The result is actually surprisingly better. Because I removed all of the XML processing from the CxApp
class, it now is much more pure--it handles instantiation, wire-ups, and final property initialization. That's it!
The initialization, from the application's perspective, takes one additional parameter: the data service assembly:
CxApp designer = CxApp.Initialize(@"..\..\..\Cx.DataService\bin\debug\Cx.DataService.dll",
Path.GetFullPath("cxdesigner.xml"));
While I've hard-coded it here, I'm sure you will realize this can be externalized, and, in fact, associated with the metadata file.
Internally, the CxApp
instantiates the data service at runtime:
public static CxApp Initialize(string dataServiceAssemblyFilename, string configUri)
{
CxApp cx = new CxApp();
cx.InitializeDataService(dataServiceAssemblyFilename, configUri);
cx.InstantiateComponents();
return cx;
}
protected void InitializeDataService(string dataServiceAssemblyFilename, string configUri)
{
Assembly assy = Assembly.LoadFrom(dataServiceAssemblyFilename);
Type t = FindImplementor(assy, "CxDataService", typeof(ICxBusinessComponentClass));
dataService = (ICxDataService)Activator.CreateInstance(t);
dataService.LoadComponents(configUri);
components = dataService.Components;
wireups = dataService.Wireups;
propertyValues = dataService.PropertyValues;
}
However, once the framework is up and running, a model component can interact with the data service component using the event mechanism: that is what I do in my implementation of the designer model.
<BusinessComponent Name="CxDesigner"
ComponentName="CxDesigner"
Assembly="..\..\..\Cx.Designer\bin\debug\Cx.Designer.dll"/>
<BusinessComponent Name="DataService"
ComponentName="CxDataService"
Assembly="..\..\..\Cx.DataService\bin\debug\Cx.DataService.dll"/>
...
<WireUp Producer="CxDesigner.RequestLoadComponents" Consumer="DataService.OnLoadComponents"/>
<WireUp Producer="DataService.ComponentsLoaded" Consumer="CxDesigner.OnComponentsLoaded"/>
Model - Repository Tension
As you can see from the code example above, the data service creates three collections (components, wire-ups, and property values) and the instance of the model references those collections. I believe a better model would essentially reverse the example just given: it makes more sense for the model to be the repository of the data. What if we wanted to read data in from one data service and write it out to another? What if, in a more sophisticated future version of Cx, the data service is loaded into its own application domain so that it can be unloaded by the framework?
We need to consider, in a dynamic composition application framework like Cx, the nature of the data communicated between components:
- If data is being pushed onto the application:
- Who gets notified--the model or the data service?
- If the data service is notified, how does it update the model?
- Do we expose load and save methods or commands:
- in the model, which then works through the interface to communicate with the data service?
- in the data service, which then communicates with the model to access the data?
- in a separate controller?
- Let's assume we have the framework up and running and we have a model and data service that are completely decoupled components, and we can now utilize the framework's capabilities to wire up the interaction between the model and the data service rather than an interface.
- It seems like a good idea that we avoid recreating collections between the data service and the model, but is this really best? We don't know how the components may be used in the future, and a collection maintained by the data service for one component might end up being modified by another component with different criteria.
- This effectively demands that the model maintains an autonomous collection from the data service!
- How does an object mapper work in this kind of an environment?
- How does this work in a paginated data retrieval environment?
- How does this work in an asynchronous data retrieval environment?
I believe the issues (of managing the data collection) I just outlined require that the model is the "master" of the collections associated with its instance. Any collection the data service creates for the purposes of communication can be disposed of after that communication. However, we really don't want the consumer to have to worry about this, so we copy the collection in the component sourcing the data.
protected void LoadConfigurationMetadata()
{
LoadVisualComponents();
LoadBusinessComponents();
LoadWireups();
LoadProperties();
componentsLoaded.Fire(new Dictionary<string, ICxComponent>(Components));
}
UI - Model Tension
The usual architectural solution to the UI-model tension is data binding. Yes, there is a tension between the UI and the model, because the model may want the data in a different type (an integer property tied to a TextBox
control), and of course currency management. Custom type converters are often used to deal with more complex data type mismatches, and another option is the View-Model-Model-View pattern (not just for WPF), particularly for managing UI state. The .NET framework provides a rich infrastructure for binding just about anything to a control, which provides a mechanism for mapping the UI's representation of the data to the model's representation. Just about any discrete value can be bound to a control's property and any collection implementing at some level IList
, IListSource
.
I'm going to take one line of code from the Cx designer business component to illustrate some of the issues:
componentListLoadedEvent.Fire(
new SortableBindingList<ICxComponent>(
new List<ICxComponent>(components.Values)));
This is really very nasty:
- The components collection is a reference to a collection managed by the data service;
- Values of this dictionary, which are: the components themselves;
- But, to be bindable, we have to convert the
ValueCollection
to something that implements an IList
. The ValueCollection
class does not implement IList
; - And, to be sortable, we make a new list using the
SortableBindingList
class I found here to be very useful; - We've now created two new collections: an
IList
collection from the ValueCollection
, and a SortableBindingList
collection from the IList
collection! - We're doing this in the designer business component (which manages the model for the metadata being designed) as opposed to doing this at the UI component level;
- We do not use data binding here: in fact, we cannot because the value collection of the dictionary is not bindable;
- Does the receiver of this message really care about sorting of a collection derived from
IList
? The ValueCollection
class implements IEnumerable
, so is that sufficient? The issue here is that a DataGrid
control will not provide clickable headers for the user to sort on a field if the collection does not derive from a sortable list.
There are so many problems with this one line of code, it's not even funny.
Let me summarize the flaws, taking each of the items above respectively (match the numbered points):
- OK, we'll worry about that later;
- The designer doesn't actually need a dictionary (the Cx framework does, but not the designer business component). This is dovetails into the first issue--we need to potentially work with the data in different ways, so the data service can't be making any assumptions about how we want the data. So, it almost makes sense to specialize the data service for how we want the data, to avoid copying the data;
- Again, we're not getting the data from the data service in the right way;
- Now we're doing something that is more specific to the UI component's requirements of the data, but if we give the component a non-sortable collection, it's still going to have to create a new sortable collection;
- Is it possible to at least eliminate one of these copies?
- In fact, the UI component shouldn't really have to have the data format either. A very nifty way of dealing with this is to inject a helper business process that converts the
List
to a sortable list. This is trivial because we can inject this process in-between the producer event that says "I have data" and the consumer handler. The easiest way to do this right now is to modify the wire-ups and introduce this process. The last thing I want to implement right now is an AOP-style event-injection feature; - Frankly, data binding may not be the best mechanism to use in a dynamic composition application. Multiple object properties cannot be bound to the same control property, and data binding is "silent"--we can't tack on additional listeners. On the other hand, data binding is so convenient, we probably ought to create some helper methods in the Cx framework to replicate the functionality of data binding.
- My conclusion is that we should not be manipulating data in the producer just because we happen to know what the consumer wants, because we can end up wasting time and creating data in a format another consumer doesn't want.
Most of these issues can be addressed by implementing a helper class and by following the best practice rule that, yes, collections (and under certain circumstances, objects themselves) should be cloned when sending them from one component to another. So, the data service creates a copy:
componentListLoaded.Fire(new List<ICxComponent>(ComponentList));
Next, we create a converter to copy the data from a List
to a SortableList
.
The component is instantiated using a generic type declaration for the type we are converting:
<BusinessComponent Name="ComponentBindingConverter"
ComponentName="Cx.Converters.CxBindingConverter`1
[[[Cx.Interfaces.ICxComponent, Cx.Interfaces,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]"
Assembly="..\..\..\Cx.Converters\bin\debug\Cx.Converters.dll" />
This component converts the list to a SortableBindingList
, and the instance is wired up between the data service and the UI component:
<WireUp Producer="CxDesigner.ComponentListLoaded"
Consumer="ComponentBindingConverter.OnConvertToSortedBindingList" />
<WireUp Producer="ComponentBindingConverter.Converted" Consumer="ComponentList.OnData" />
The implementation of the converter is very simple:
namespace Cx.Converters
{
[CxComponentName("Cx.Converters.CxBindingConverter")]
[CxExplicitEvent("Converted")]
public class CxBindingConverter<T> : ICxBusinessComponentClass
{
protected EventHelper converted;
public CxBindingConverter()
{
converted=EventHelpers.CreateEvent<IEnumerable>(this, "Converted");
}
[CxConsumer]
public void OnConvertToSortedBindingList(object sender,
CxEventArgs<IEnumerable> args)
{
SortableBindingList<T> sbl = new SortableBindingList<T>((List<T>)args.Data);
converted.Fire(sbl);
}
}
}
The only real trick in this is specifying the type T
in the declaration of the instance: the whole
`1[[Cx.Interfaces.ICxComponent, Cx.Interfaces,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]
piece of the class definition. Possibly, with C# 4.0's co/contra-variance capability, this will not be necessary.
Also, I'll take this opportunity to give credit to this post which is where I got the implementation for the SortableBindingList
.
The Declarative Dilemma
Once we start down the declarative path, it becomes difficult, if not impossible, to stop. To illustrate, take menus (or more generally, toolbars). Menus are a sort of a beast in themselves:
- they are hierarchical,
- they can include options like checkmarks and even other controls,
- clicking on a menu item fires an event.
In a componentized application, menus create a lot of problems:
- Do you represent them in XML, with yet another schema specific to the menus?
- Do you allow the developer to create the menu using the VS designer?
- If you create the menu in XML, you can name the click events and the Cx Designer can find them. That's a good thing, but now the Cx Designer has to also support designing the menus, which is more complexity;
- If you allow the developer to create the menu using the VS designer, how does the Cx Designer show the menu click events in the producer list? It seems annoying to have to create explicit events somewhere, associated with some menu-visual-component;
- Speaking of which, is the menu bar (or a toolbar) implemented as a Cx visual component?
At the moment, I don't have any good answers to this. I decided to create the Cx Designer's menu using Visual Studio so that I could explore at least a part of the problem. To that end, I created a visual component:
<VisualComponent Name="Menu"
ComponentName="CxMenu"
Assembly="..\..\..\Cx.Designer.Components\bin\debug\Cx.Designer.Components.dll" />
whose sole purpose is to recurse through a MenuStrip
and instantiate event helpers, mangling the name of the menu by appending "Click":
protected void RegisterEvent(ToolStripMenuItem tsmi)
{
EventHelpers.Transform(this, tsmi, "Click").To(tsmi.Name + "Click");
}
Initially, I wired up the events manually, since the Cx Designer has no understanding of the menu events at design time:
<WireUp Producer="Menu.mnuSaveClick" Consumer="CxDesigner.OnSave" />
<WireUp Producer="Menu.mnuExitClick" Consumer="App.CloseDialog" />
<WireUp Producer="Menu.mnuOpenClick" Consumer="CxDesigner.OnOpenMetadata" />
<WireUp Producer="Menu.mnuNewClick" Consumer="CxDesigner.OnNewMetadata" />
<WireUp Producer="Menu.mnuSaveAsClick" Consumer="CxDesigner.OnSaveAs" />
but since that won't do, I bowed to the beast and added an ExplicitEvents
section to the XML:
<ExplicitEvents Producer="Menu">
<Event Name="mnuSaveClick"/>
<Event Name="mnuExitClick"/>
<Event Name="mnuOpenClick"/>
<Event Name="mnuNewClick"/>
<Event Name="mnuSaveAsClick"/>
</ExplicitEvents>
I figure this is an alternative (alternatives are good) to using the ExplicitEventAttribute
as well, so it'll be useful in general.
Push vs. Pull: Data
Another interesting (if you can call it that) problem that I had to solve with the designer has to do with pulling data from one component into another. Again, the UI provides an example of the problem: the user selects a component and then clicks on the "Properties" or "Events" button. At that point, a child dialog comes up and needs to display some property or event lists based on the component selected in the parent dialog--the designer's main form, in this case. So how, at that point, does the child get the information it needs? This is in many ways the classic beginner question of how do you get information into a child dialog. The usual answer, using property setters before you display the dialog, doesn't work in a dynamic composition environment because of the extreme decoupling of the components. In dependency injection frameworks like CAB and Spring.Net, the answer is fairly straightforward: you can specify the property initialization in the metadata and reference objects managed in some container.
Imperative / Declarative Tension
In my exploration of the IoC/DI concepts with my more limited Cx framework, I continue to be reluctant to move fully into the world of DI, because (ironic as it may seem) I'm trying to limit the amount of information that gets stored in the metadata. It makes writing a designer more complicated, and I'm trying to get to an initial release without adding too many new features to the framework that the designer has to support. As we saw with menus, there is a disconnect between what is done imperatively and how to get that information into a designer for the declarative part. We simply don't know what objects are put into what containers imperatively so that, at design time, they can be injected into properties or participate in constructor arguments in the declarative code. The answer that "all objects that need to be injected or participate in constructor arguments should also be initialized declaratively" doesn't fly with me because it ties the developer and the application too closely to the framework. Not that this is in any real sense avoidable, but it should at least be considered.
One Solution
<BusinessComponent
Name="AppProperties"
ComponentName="App"
Assembly="C:\projects2008\Cx\Cx.Designer.App\bin\Debug\Cx.Designer.App.exe" />
Now, we can wire-up the instantiation of the form to this specific app instance (note that this wire-up lives in the main form's metadata):
<WireUp Producer="CxDesigner.EditProperties" Consumer="AppProperties.ShowModalForm" />
and now the model can consume initialization events (note that this wire-up lives in the child form's metadata):
<WireUp Producer="AppProperties.FormInitialized" Consumer="EditProperties.OnInitialize" />
and proceed from there to do whatever initialization it needs to do and firing off events when the initialization is complete, that can be wired up to other components, including visual components.
Removing Wire-ups
As I started using the designer, I noticed an annoying behavior in the business components associated with a dialog: the wire-up between components was not being removed. In other words, even though there were no references remaining to the components, the second time I used the same dialog, the event wired up to the old business component fired as well as the event wired up to the new business component. I discovered this first in global business producers to local business consumer events. For example, the designer component (which is a singleton, and therefore global) firing an event with property information for the selected component, which was consumed by the "edit properties" business component (a local component specific to the dialog).
Implementing Dispose
The first problem was that I wasn't removing the business components from the global cache, so even components with 0 references were being re-used. It seemed that the best mechanism for doing this was for the Cx class to implement IDisposable, and to call the Dispose method when the CxApp instance is no longer needed, or wrap the whole thing in a "using" block:
[CxConsumer]
public void ShowModalForm(object sender, CxEventArgs<string> args)
{
using (CxApp dlg = CxApp.Initialize(@"..\..\..\Cx.DataService\" +
@"bin\debug\Cx.DataService.dll",
Path.GetFullPath(args.Data)))
{
dlg.WireUpComponents();
Form form = new Form();
Size extents = new Size(0, 0);
foreach (ICxVisualComponent comp in dlg.VisualComponents)
{
((ICxVisualComponentClass)comp.Instance).Register(form, comp);
extents = Program.UpdateExtents(comp, extents);
}
form.Size = extents + new Size(25, 40);
form.Activated += new EventHandler(OnActivated);
formInitialized.Fire();
form.ShowDialog();
}
}
I'm not particularly happy with this approach because the developer needs to remember to dispose of the CxApp
instance--waiting for the garbage collector to come along and call the Dispose
method while events are still wired up could lead to some really unexpected side-effects.
In the Dispose
method itself, the component reference from the global cache is removed:
protected void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
List<string> markedForRemoval = new List<string>();
foreach (ICxBusinessComponent comp in busCompList)
{
ComponentReference compRef=null;
if (businessComponentCountMap.TryGetValue(comp.Name, out compRef))
{
--compRef.Count;
if (compRef.Count == 0)
{
markedForRemoval.Add(comp.Name);
}
}
}
foreach (string name in markedForRemoval)
{
components.Remove(name);
businessComponentCountMap.Remove(name);
}
RemoveWireups();
visCompList.Clear();
busCompList.Clear();
}
disposed = true;
}
}
Component References and Initialization
This brings up an interesting question: when the reference count becomes 0, should we actually remove the component from the cache, which forces a new instantiation the next time the component is needed, or should we leave it in the cache so that it can be re-used? Although this applies only to business components, this decision has serious implications for the initialization of the component in its constructor: a component that is re-used never has its constructor called only once, rather than every time the component is instantiated.
Removing Wire-ups
The easiest way to remove wire-ups is to add the wire-up to a collection when the CxApp
instance does the wire-up:
wireupInfo.Add(new WireupInfo(ei, producerTarget, dlgt));
and when the CxApp
instance is disposed, to unwire the producer-consumers:
protected void RemoveWireups()
{
foreach (WireupInfo wi in wireupInfo)
{
wi.Remove();
}
wireupInfo.Clear();
}
Business Component GUIDs
This resolved my issue with events firing to "old" business component instances. Not that it was easy to figure this out. There are times when I wish that I could see the actual memory address of an object so I could easily tell if the event is firing to different component instances or to the same instance. This actually makes a case for assigning a GUID to the business component:
public class CxBusinessComponent : CxComponent, ICxBusinessComponent
{
public Guid Guid { get; protected set; }
public CxBusinessComponent()
{
Guid = Guid.NewGuid();
}
}
Now we can see if the instance is different, by inspecting the GUID.
Are We Having Fun Yet?
I have to admit, this is fun in a sick and twisted way. I'm finding that:
- I'm writing a lot of producer/consumer events;
- I'm doing a lot of attribute programming;
- I'm able to use the Cx Designer more and more as I implement features;
- It really imposes a separation of components (no interfaces, no direct references) and a very clear, explicit contract between components (event producers and method consumers). While I like this, it can also be construed as being a bit of a pain;
- A lot of shortcuts that I would normally take can't really be taken. For example, if I have a button that should open a dialog, I would typically subclass the button, add a property for the dialog it should open, and then wire-up the
Click
event in the subclassed button to load that dialog (because this is most likely a common UI pattern of many applications). I can't do that in a CxButtCxButtonon
component because it doesn't have access to the application's "ShowModalForm
" method. I could, instead, fire an event, but this feels dirty--I'm putting business logic into the UI component rather than maintaining a more purist implementation: "the button was clicked" and letting the designer business component determine what to do. I like this approach better as well because it explicitly states "the designer component has a consumer that will open this dialog" and so anything (not just a button) can source this event. I really think this is a good thing, though it can, of course, be taken to an extreme, so like anything, the point is not to be an extremist but rather to become conscious of our otherwise implicit programming decisions; - It's actually quite fun to wire-up discrete behaviors between components and watch it all come together;
- I'm coding in discrete functional packets, without too much regard for how the code will be utilized for a specific requirement, but rather keeping things general;
- I'm also noticing that it isn't easy to truly modularize something (particularly UI features) without some rework / rethinking. For example, to deal with VS-created menus, I had to modify all of the current UI components by adding a
Register
method, declared in the ICxVisualComponentClass
interface. This had the (I think positive) effect of moving the code that was adding the control to the form's collection to the control itself, but I ended up using an extension method to extend the Control
class behavior; - I've ditched data binding. Is that a good thing? Is there a good way to coerce data binding to use the Cx messaging mechanism so that we can track data binding messages just like any other Cx message?
- Component naming--Right now, if the same name is used in a "child" (typically applies to child dialogs), then the component instance is re-used. This doesn't handle different contexts of the same thing, such as an MDI scenario!
- Some sort of document generation would be very useful, because you don't know what the consumer argument signature should be without looking at the event producer signature. And, this would also be a neat feature to add to the designer, so that it shows only consumers whose signature matches that of the producer. A lot of confusion would be eliminated, not to mention eliminating runtime event wire-up exceptions.
For example, I really do find it totally cool that I can take the menu's Save As event, wire it up to the designer's consumer:
<WireUp Producer="Menu.mnuSaveAsClick" Consumer="CxDesigner.OnSaveAs" />
which, being the "model", sets up a filter and fires its own event:
[CxConsumer]
public void OnSaveAs(object sender, System.EventArgs args)
{
saveAs.Fire("xml files (*.xml)|*.xml|all files (*.*)|*.*");
}
which in turn is wired up to an App consumer:
<WireUp Producer="CxDesigner.SaveAs" Consumer="App.ShowSaveAsDialog" />
whose implementation brings up the Save As dialog and, if the user doesn't cancel, simply fires an event with the filename:
[CxConsumer]
public void ShowSaveAsDialog(object sender, CxEventArgs<string> args)
{
SaveFileDialog sfd = new SaveFileDialog();
sfd.Filter = args.Data;
DialogResult ret = sfd.ShowDialog();
if (ret == DialogResult.OK)
{
save.Fire(sfd.FileName);
}
}
which in turn is wired up back to the designer component:
<WireUp Producer="App.Save" Consumer="CxDesigner.OnSaveFilename" />
which in turn sets its filename, fires an event that the filename is set, and ultimately fires another event to the data service to save the model:
[CxConsumer]
public void OnSaveFilename(object sender, CxEventArgs<string> args)
{
metadataFilename=args.Data;
filenameSet.Fire(metadataFilename);
SaveModel();
}
Incidentally, the filename set event is wired up to an App consumer:
<WireUp Producer="CxDesigner.FilenameSet" Consumer="App.SetCaption" />
that updates the caption of the form:
[CxConsumer]
public void SetCaption(object sender, CxEventArgs<string> args)
{
Program.mainForm.Text = "Cx Designer - "+Path.GetFileName(args.Data);
}
The beauty of this is in the general to specific messaging pattern: the producer is usually a general "I'm saying something", and the consumer is "I'm going to do something specific about what you said". The producer doesn't know or care if anyone is listening.
So yes, I'm having fun, but implementing these events is tedious. There ought to be a better way than having to create an EventHelper
field for every event and an ExplicitEvent
attribute for the class. It should somehow be enough to create the event an attribute!
And, of course, something I haven't done yet, but will, is write an event monitor so you get a lovely audit trail of all the messages and events of the data being sent between components.
There are two things the designer lacks (besides a better UI):
- Editing component definitions
- Defining types for Generics
- Managing toolbars/menus is not pretty
- MDI support
Conclusion
In the process of writing this very un-sexy designer, I did finally get a chance to investigate the pros and cons of an IoC framework and come up with some best practices. I'm sure there's more work to do, but whether the designer is sexy or not is irrelevant--the issues are the same no matter how cool the UI looks. For example, how about a WPF UI that lets you draw the connections between the event producers and the method consumers?
Now, after writing this, it finally occurs to me that the entire structure and use of events is only one kind of messaging, and therefore the details of using events shouldn't be so exposed to the components. Instead, it would be better if the component worked with an abstract messaging system that uses events (for example, in WCF, it's very easy to swap out a TCP communication pipe for an HTTP one). This would of course be much more flexible for handling communication across networks, etc.
Another thing is that, with a large application, the list of producers and consumer components becomes quite large, and the number of producer events and consumer methods for each producer/consumer is probably going to be large as well. In fact, even with the Cx Designer, the sizes of the lists are already approaching unmanageable. So, any designer UI that facilitates the wire-up of the producers to consumers has to be carefully designed so that it facilitates the developer, rather than itself being a hindrance.
It also becomes ridiculously simple to specify which events can run asynchronously. For example, loading the component, producer, and consumer lists in the data service can be done asynchronously, as can populating the UI component. If I created consumers specific to loading each section of the XML rather than having one "load" consumer, the event wire-up could specify "do this asynchronously". That of course leads to a couple interesting artifacts, as a result of all of these events flying about--if you're on an asynchronous event, how do you marshal back to the main application thread for updating the UI, and how do you manage worker threads?
As I mentioned above, I think a very useful feature would be to "document" the event signature of a producer so that the designer shows only consumers capable of consuming that signature. Of all the things that I might want to do next with this framework, this is probably on the top of the list, along with an event logger/viewer.
So, lots of interesting food for thought for future installments.