Subscription / Notification Engine (WF4 EditingContext Intro Part 5)
This part 5 of my 6 part series on the EditingContext.
- Introduction
- Sharing Functionality between Designers
- Host provided capabilities
- Providing callbacks for the host
- Subscription/Notification engine (you are here)
- Inspection, Default Services and Items
In this post, we’re going to tie together a few of the things we’ve seen in the last few posts and show how we can wire up parts of the designer (or the hosting application) to changes made to the Items collection of the EditingContext to do some interesting things.
You will note that both the ServiceManager and ContextItemManager have Subscribe methods, and I’ve talked in previous posts about how the publish mechanism is a little different. I want to dive a little deeper into how these work, the different overloads, and what you can expect have happen on the subscribe side of things.
Services
On service, there are two different publish methods. I will list all four, and then talk about how there are really only two :-)
Method |
Description |
Publish(Type, PublishServiceCallback) | Publishes the specified service type, but does not declare an instance. When the service is requested, the Publish service callback will be invoked to create the instance. The callback is invoked only once. After that, the instance it returned is cached. |
Publish(Type, Object) | Publishes the given service. After it is published, the service instance remains in the service manager until the editing context is disposed of. |
Publish<(Of <(TServiceType>)>)(TServiceType) | Publishes the given service. After it is published, the service instance remains in the service manager until the editing context is disposed of. |
Publish<(Of <(TServiceType>)>)(PublishServiceCallback<(Of <(TServiceType>)>)) | Publishes the given service type, but does not declare an instance yet. When the service is requested, the PublishServiceCallback will be invoked to create the instance. The callback is invoked only once. After that, the instance it returned is cached. |
There are really only two methods here, and some generic sugar for the other two. They are Publish(Type, PublishServiceCallback), and Publish(Type, Object). If you were to look at the implementation of the generic versions, they simply turn around and call the un-generic form.
The difference between the basic one (Publish(Type, Object)) and the version with the callback is that the callback enables us to be a little more lazy and wait to actually create the instance of the object until it is first requested. Let’s look at how PublishServiceCallback is defined:
public delegate Object PublishServiceCallback(
Type serviceType
)
What this lets us do is that the first time someone calls GetService, this method will be called with the responsibility of returning an instance of the service type. Subsequent calls to GetService will simply return the instance returned by the method provided fro the PublishServiceCallback. It is important to note that on Publish, any subscribers will be notified. As the Subscribe callback takes an instance, we will internally call GetService on the notification, which will in turn call the PublishServiceCallback to instantiate an object. If we have subscribers, our publish is less lazy (but that’s by design, we have consumers who are ready and waiting to consume).
Let’s now look at the subscribe methods. Again, here there are two methods (generic and non-generic), but they both do the same thing:
Method |
Description |
Subscribe | Invokes the provided callback when someone has published the requested service. If the service was already available, this method invokes the callback immediately. |
Subscribe<(Of <(TServiceType>)>) | Invokes the provided callback when someone has published the requested service. If the service was already available, this method invokes the callback immediately. |
Both of these use a SubscribeServiceCallback defined as the following
public delegate void SubscribeServiceCallback(
Type serviceType,
Object serviceInstance
)
This allows any consumer to be notified when a service is initially published. This is an important distinction we will call out versus items which provide a more advanced subscription method (namely, to changes).
Why is this Useful?
Generally we find this useful for a few reasons:
- Services may not be available at designer initialization, or the order in which they are created may not be fixed (it is ultimately up to the host to determine this).
- Services may be provided by the host. It is possible your activity designer may be used in a host that does not provide that service. You may want to be flexible in handling that
- Services can be injected at any time. A publish – subscribe model lets us have a little more flexibility to react to new services as they are added. You could imagine a spell checking service that a host only provides on the first time someone hits “spell check.” When this service comes online, then our designers can decide to consume this.
- Flipping things around, you may want to use a service to callout to a host, and you would like the host to subscribe for when an certain activity designer will publish a service.
Now let’s talk about Items:
Items
Items do not have a publish method, per se, but they have the SetValue method which basically publishes an instance to the context. The semantics of SetValue are that it will first attempt to store the new value. Provided that succeeds, we then call OnItemChanged on the ContextItem itself. This is basically notifies the object itself (giving it a chance to react, clean up, or throw if something is really wrong). If this throws, the old value is preserved. If this succeeds, we then notify anyone who has subscribed to the changes.
GetValue allows me to retrieve the ContextItem. There are two GetValue’s, one generic, the other non-generic, but with a type as its parameter. It is important to note the point that is also present in the docs. If there is not an item present when this is called, the default value will be instantiated and returned.
Provided items are written using SetValue, all of the subscribers will be subsequently notified. If I just do an arbitrary GetValue and then make a few changes without calling SetValue, by default nothing interesting is going to happen (that is, no subscribers will be notified, subsequent calls to GetValue will get the updated object however). Subscribe (and it’s generic counterpart) allow me to provide a SubscribeContextCallback which will be invoked whenever SetValue is called. This functions basically in the same way that it does for Services.
An interesting pattern for Items that we use in a few places throughout the designer is to create an AttachedProperty on the modelItem (similar to this post) which in the implementation of the Getter and Setter will call out to the editing context to get or set the value from a ContextItem. This gives me a WPF friendly binding surface (foo.Bar binding syntax) that we can wire up to be change aware. We do this for a number of our triggers within our style implementation for things like is selected, etc. Future post note for me is that I should go through all of the attached properties present on a ModelItem that you could use to bind to :-)
This wraps up a tour of the Subscription / Notification engine present within the EditingContext.
Comments
Anonymous
December 22, 2009
Thanks for another great blog post Matt. I understand that a service can be made available at any time, not necessarily during initialization. Once a service is made available, should it be available for the entire lifetime of the host? If not, I take it there is no way to be notified when the service is going 'offline'? Thanks, NotreAnonymous
December 22, 2009
@Notre, There is no way to "unpublish" a service, so you can assume the service will always be available. If you want to get a clean service manager, the best thing to do is to load a new instance of the designer. matt