No more posts from me for the next ten days. My wife and I are going to the Galapagos Islands. No computer. No phone. No twitter. Heaven.
Read more of this post!
Often wrong but never in doubt ... an opinionated romp through .NET
No more posts from me for the next ten days. My wife and I are going to the Galapagos Islands. No computer. No phone. No twitter. Heaven.
Posted by
Ward Bell
at
2:11 AM
1 comments
Links to this post
In this post we explore the question, “can it be done ?”
If we were asking “can we write one application that targets both Windows Forms and ASP?”, I would say “sure but there will be little code in common.” The data access and business layer perhaps … if you are careful. At the application, UI, and presentation levels there are irreconcilable differences. Maybe 10% to 20% of the code can be the same in both environments. That’s my opinion based on recent observations of such a case.
I believe the story is much better when it comes to writing a single application with both a Silverlight and a WPF face. It helps enormously that they share the same presentation philosophy, the same UI specification language (XAML), the same client-side execution model, and many of the same components.
According to my calculations for Silverlight v.3, you will be able to re-use between 52.6% and 93.7% of your code-base to support both platforms.
I’m sure you are wondering … why the big spread?
Let’s walk up the application stack and evaluate the opportunities for re-use at each level.
Let us stipulate that we can use the same database regardless of client technology.
You can write a 99% common data access and business layer … at least you can if you adopt my company’s DevForce product; Rocky’s CSLA.NET for Silverlight may be another option.
This high percentage presupposes that you are prepared to use the same asynchronous server-access in your WPF application that you must use in your Silverlight application. Silverlight only permits asynchronous communications with the server. This means you can’t issue a query and block the UI thread until the data arrive. You must design and develop a user experience that keeps going while the program is waiting for data.
Some argue that this is a good idea anyway. Unfortunately, only a handful of developers have experience writing non-blocking UIs. You should anticipate a slower initial pace of development as the team learns to program in async style.
North of 90% of your non-visual application layer classes can be written once and “shared” between regular DotNet and Silverlight assemblies.
The word “shared” deserves at least passing comment. The DotNet and Silverlight assemblies often look alike but they are not the same. Your Silverlight projects must be compiled against Silverlight assemblies; your WPF projects must be compiled against DotNet assemblies. Obviously you don’t want to duplicate your code. The trick is to arrange for the two kinds of project – Silverlight and WPF – to share the same physical code files. You can do this manually in Visual Studio but it is far more convenient to use the Project Linker from Microsoft Patterns and Practices.
Rocky Lhotka presented techniques for coordinating bi-platform development in his Tech Ed 2009 talk “WUX313 Sharing Code between Your Microsoft .NET Framework Applications and Microsoft Silverlight”; I hope a public link to his talk becomes available soon; you can always ask Rocky for it.
You can rely on Prism to glue your application together because Prism was built to support multi-platform scenarios.
It is the visual classes – the XAML files in particular – that present a significant challenge.
There is no issue if you’ll have one look for Silverlight and a different look for WPF. The Model-View-ViewModel pattern makes it easy to share the non-visual Model and ViewModel classes across platform; all you have to do is whip up your separate View XAML files and – shazam! - you’re done.
Did he really say “whip up your separate XAML files …”? He’s got to be kidding!
Yes I am kidding. Unless you’re auto-generating your views (which I cannot recommend), you will invest heavily in crafting XAML that both looks good and enhances user productivity.
If you decide to produce different views for each platform – and there are strong arguments for doing so – you’ve just identified the 50% of your application (measured in effort if not in kilobytes) that will differ across platform.
On the other hand, if you intend to use the same views – the same XAML – on both platforms, you can hope for 90+% XAML re-use, thus bringing your total for the application to the coveted 93.7% mark.
So there’s the spread: 50% common code if you have separate views and 90+% if you have shared views. No brainer, right?
Not so fast. The effort and compromises essential to re-use XAML can be daunting. WPF and Silverlight XAML are similar but maddeningly different in critical details. I won’t explore these differences in depth here but I will call out a few of the obstacles.
First, there are many features in WPF that don’t exist in Silverlight 3. Implicit styling is one example but there are numerous others. Because WPF is (mostly) a superset of Silverlight, if you design for Silverlight you’ll have a better chance of retargeting the XAML for WPF.
Second, there are features of Silverlight 3 that are not yet in WPF-proper such as the DataForm. It will show up eventually but you’ll have to wait for it.
Third, some of the features available on both platforms happen to be declared in different namespaces; Shawn Burke posted about this back in November 2008 and I don’t believe the incompatibilities have been resolved in Silverlight 3. If I recall (to be confirmed) the VisualStateManager is one crucial example of a component that is not in the same namespace on both platforms.
As it happens, you can define a namespace that doesn’t exist in your XAML file as long you don’t reference it. Your app will build and run fine with a phantom namespace declaration such as xmlns:dummy="dummy". You might think this would provide some relief. You could define one prefix to refer to the WPF namespace and and a second prefix to refer to the Silverlight namespace. But which prefix do you use when you want to declare a VisualStateManager? You can’t toggle prefixes.
Fourth, XAML does not support compiler directives (#if/#else/#endif), namespace aliasing, or partial class files. Why does this matter? Because we use all three of these techniques to manage the inevitable differences between WPF and Silverlight implementations … when managing such differences in code.
If XAML supported compiler directives, for example, we could work around the dueling namespace prefix problem with something like this:
<!-- This does not work! -->
#if SILVERLIGHT
xmlns:Controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
#else
xmlns:Controls="clr-namespace:Microsoft.Windows.Controls;assembly=Microsoft.Windows.Controls"
#endif
You can find a XAML pre-processor that will recognize and resolve compiler-like directives in XAML. Sadly, you won’t be able to use the visual design tools (e.g., Blend) because they won’t recognize or honor the directives.
I hope you will join me in asking Microsoft to make support for conditional XAML a priority.
It is reasonable to contemplate writing a single application that sports both a WPF and Silverlight client experience.
It isn’t automatic. Microsoft could make it easier … and eventually will. But the important take away is that you can do it and it is relatively easy today if you are willing to write distinct Silverlight and WPF XAML files.
In a future post we’ll talk about who wants to write a WPF+Silverlight application and why.
Posted by
Ward Bell
at
2:01 AM
6
comments
Links to this post
A very good friend just hung up on me in the middle of an IM exchange about whether “we” are over-hyping Prism and recommending it for situations in which it is overkill. He said yes; I said no.
Ok, he didn’t actually hang up on me; our IM connection failed.
I’m replaying our exchange … and because he bailed,however involuntarily, … I get the last word.
-----
Friend: [So-and-so] thinks that [some people] are starting to push Prism in cases where they shouldn't.
Me: Such as?
Friend: They are saying that anyone who builds line-of-business (LOB) applications with Silverlight should use Prism. He doesn't agree. He thinks that most of Prism is good only for Composite UI, with a few exceptions like delegate command, and the project linker. I agree with him. Prism is not the “end-all” for every solution.
Me: True ... but it's the right call for modular solutions. And the bootstrapper + Dependency Injection (DI) are pretty much central to anything I would build.
Friend: Anything YOU build yes. But I don't think you can assume that DI is a given. You can't make using DI a “must”. Sure, for composite apps it makes total sense. For TDD (Test Driven Development) and decoupling of components yes ... but I won't go as far as to say "you must".
Me: It isn't a “must”. Nothing is a “must”. But come on ... once you're comfortable with DI, you never want to be without it again. It is trivial to put in place, easy to use, and it makes writing single-responsibility code every so much easier. And testing, by the way, is NOT why I like DI. You get the major benefits of DI without writing a single test.
Friend: In general I agree but there is a bit of self-selection there. I mean DI appeals to you...and me, and others like us but that does not mean it is something that will work for everyone or that they will perceive the benefits.
Me: No, this is not a case of elitist self-selection. DI does benefit everyone. I think you do the inquiring developer a disservice by supposing that they will be unable to see the value. Look, if you don't want guidance ... if you just want to wing it, then don't ask for my advice. If you want guidance, than I say that something like Prism is good for you in almost any business application.
Friend: Huh? I would be careful if I were you. You’re starting to sound dogmatic.
Me: I'm trying to respond authentically to the developer who asks "how do I build an app?"
Friend: It sounds like you’re recommending a swiss army knife. I would be very wary of that!
Me: No, I don’t thing so. There are some standard problems in any business app ... and Prism ... or parts of it anyway ... and I include it's promotion of certain patterns ... Prism addresses those standard problems. DI is NOT a swiss army knife part. Neither is the bootstrapper. And every app has some kind of shell. These constructs are basic to just about any application.
The other Prism components you may safely ignore until you need them. You may not need Regions and Commanding and Event Aggregator during the first weeks. Not to worry. They are non-intrusive. They are waiting for you, quietly, causing no trouble at all. When you do need them, they’ll be ready for you.
Do you want to argue that the presence of unused components is indicative of a "swiss army knife” syndrome? You might as well say that WPF and Silverlight are swiss army knives because they have loads of features you will never use.
Friend: Not the same.
Me: I'm listening. Why not?
Friend: I don't think you can assume that every app must use modules. I don't think you can even assume the bootstrapper needs to be there. Why would I need a bootstrapper? I have Main.xaml as a shell.
Me: I didn't say every app needs modules. You could use Main.xaml as the shell. But what happens is that all of the other jazz that happens at start-up tends to be piled into Main.xaml or App.xaml. This practice is almost always bad. You’re combining the view that defines the application’s screen real estate with initialization logic; these are wildly different concerns. You really SHOULD think about how the application starts as as a thing unto itself. Failure to do so leads to trouble.
Friend: If I am not writing a Composite UI , why do I care?
Me: You care because the app.xaml gets out of control very quickly as more and more initializations mingle with the code to support how your application hosts it’s views. That is the “shell” function. I’m talking about all of the little services you’ll need – logging, authentication, authorization, persistence, email - you want to register them somewhere. This is bootstrapping, not view hosting; these start-up activities belong in a dedicated class.
And if you're NOT thinking in terms of these services – if you’re thinking you’ll introduce these capabilities by “new-ing” up classes or referencing singletons wherever you need them – you’re heading straight for the kind of hopelessly entangled, brittle application that gets progressively harder to maintain and evolve.
Friend: You realize you do come off a bit biased don't you? I know everything you are saying and am being a bit of devil's advocate.
Me: I realize I am talking to you and so I can make some aggressive claims in my argument without serious risk of contradiction. I don’t think my present approach will persuade the uninitiated. But I resist the notion that I am being “biased”. By charging “bias” you are suggesting that I am somehow an unthinking captive to an ideology. That’s the easy, simplistic way to dismiss advice of any kind.
It is not just a “bias” when I tell you that maxing out your credit card with no ability to pay puts you on the road to trouble.
I see a lot of applications. I see a lot of applications go wrong. They tend to go wrong in the same way over and over again.
The concepts that Prism fosters help you find a way out of those familiar mistakes.
Friend: look I am not saying Prism is bad ...
Me: And I will agree that it is not a magic bullet.
But it is more than a collection of components, more than a bag of tricks. If it were only that, you could argue that the tricks were needed only for applications of sufficient sophistication.
I think Prism is something completely different. I think it is is a lens that helps us see farther and more clearly. Through that lens we can find solutions to the problems that would otherwise mire us in the half-measures that gradually slow our progress and make our code stink.
And, by the way, when I say “Prism”, I mean it to stand in for any such ensemble of guidance and infrastructure code. What Prism-like thing you actually use could come from anywhere; you could write your own. The Prism that comes from Pattern and Practices is just an instance of what I have in mind.
Instant Messenger: Your friend signed off at Wed May 27 01:36:30 2009. This user is offline. Your messages will most likely *not* be received!
Me: Grrrr
------
Feel free to chime in yourself.
Posted by
Ward Bell
at
3:09 AM
13
comments
Links to this post
Now that we’ve unveiled the Prism Explorer (PE) which is our demonstration of Prism and DevForce working together, I’m on the hook to write about it. I should be telling the story from the beginning and moving deliberately forward. Instead I’m jumping into the middle because someone asked me about a particular feature. Bear with me.
I first showed PE (in a slightly different version) at Tech Ed 2009 in a talk I gave on migrating an application to Prism; you can see the video of my talk here.
EventAggregator (EA) is one of the Prism components I discussed; it’s an implementation of the Event Aggregator pattern.
namespace PrismExplorer.Infrastructure {
public class EntityNotificationEvent : CompositePresentationEvent<EntityNotificationSpecification> {}
public class EntityNotificationSpecification {
public EntityNotificationSpecification(Type entityType, object entityId) {
EntityType = entityType;
EntityId = entityId;
}
public object EntityId { get; private set; }
public Type EntityType { get; private set; }
public bool IsType<T>() { return IsType(typeof(T)); }
public bool IsType(Type entityType) { return EntityType == entityType; }
}
}private void PublishEntitySelected() {
var spec = GetEntityNotificationSpecification();
if (null == spec) return;
PublishEntitySelected(spec);
}
private void PublishEntitySelected(EntityNotificationSpecification spec) {
_eventAggregator.GetEvent<EntityNotificationEvent>().Publish(spec);
}protected void Subscribe() {
// Simple subscription - pass in the fn that implements the response
_eventAggregator.GetEvent<CacheClearEvent>().Subscribe(ClearView);
// Full-blown subscription
_eventAggregator.GetEvent<EntityNotificationEvent>().Subscribe(
NotifyView, // the function that implements the response
ThreadOption.UIThread, // invoke subscription on the UI Thread
true, // a true KeepAlive means ensure strong fn references
IsNotificationRelevant // Fn to filter events so only hear pertinent events
);
}
public void ClearView(object dummy) {
if (null == _moduleViewModel) return;
_moduleViewModel.Clear();
}
public void NotifyView(EntityNotificationSpecification spec) {
EnsureViewExists();
_moduleViewModel.RequestCustomer(spec.EntityId as string);
}Update 16 June: The "Prism Explorer" code has changed. The "Coordinator" is now a "Screen Factory", it unsubscribes, and the "ViewModel" is now subscribing once it has been created.
The gist of this post remains true and relevant. There is still a GC threat to the "Screen Factory" which must subscribe with a strong reference. The changes were prompted by design interests unrelated to EA.
And ... yeah ... I guess you could say I changed my mind about whether the VM should subscribe. There was very little duplication, the Factory got simpler, less indirection ... hey, what more can I say?
Posted by
Ward Bell
at
6:00 PM
2
comments
Links to this post
A specter has been haunting my code of late. I’ve been calling it the “coordinator” although I’m not wedded to the name. I just know I need it. I need it because I need something to build my Model-View-ViewModel triads. I need something to coordinate them when they are closely related. And I need something to tear them down and dispose of them when they are no longer needed.
That’s my coordinator.
Some friends tell me they don’t see the need for such a thing. I’m not sure how they do without it. Maybe this post can initiate a clarifying discussion.
But first, a little throat clearing.
The Model-View-ViewModel (MVVM) pattern is on the lips of many a Silverlight and WPF developer. It’s ungainly name garners many jokes - the patty-cake pattern, the mumble pattern – but it survives them all. It is the predominant presentation pattern for these technologies and I can’t recall seeing serious discussion of any alternative; it is as close to a consensus heuristic as we get in this business.
There remains plenty to fight about under the MVVM tent.
To pose such questions invites a fist-fight. Small wonder that many developers keep their distance from MVVM; whatever you do you’re doing it wrong.
Perhaps we could all grow up just a little. I’m not diminishing the importance of these differences; I am suggesting a change in conduct.
Sermon over … on to today’s topic: where do you create an MVVM triad and how do you get rid of it when the view is no longer wanted?
The triad doesn’t create itself. Something creates it and puts it in motion.
This isn’t much of an issue in simple applications. It becomes serious when you contemplate
There is significant logic here. Where does it go?
Permit me to indulge an example near to my heart. I have a sample Silverlight application that demonstrates how my company’s DevForce product plays well with Prism. It’s called “Prism Explorer” and you can check it out here. You can run it … and download the source later if you’re interested. Here’s what happens when you run it:
Prism Explorer presents a tab displaying a grid, some buttons, and a ComboBox with queries you can try. Some queries return Customers; some return Employees or Orders or a projection over Customers. There is a second tab that does the same thing … for reasons I won’t go into.
Now if you query for anything except Customers, the application’s responses are confined to the current tab. If you query for Customers, a new tab appears called “Customer Orders”. Click on that tab and, after a trip to the server, you’ll see a master/detail display of the current customer and its orders.
What’s happening? When the application launches, it starts up two modules. One drives the two querying tabs (the “Model Explorer View” module) and the other drives the “Customer Orders” tab.
This second module instantiates a CustomerOrdersCoordinator which just listens (via Event Aggregator) for a “Customer notification event”. It doesn’t display anything. When it finally hears such an event, it create the MVVM triad to display a CustomerOrders master/detail view.
As it happens, the ViewModel takes over the listening chores and updates itself whenever the selected customer changes. The ViewModel doesn’t waste time fetching customer information if it’s view isn’t visible … which is why there is a visible delay when you activate it’s hosting tab.
Thank you if you’ve stuck with me this far and for going along with my scenario. You will notice that the CustomerOrders view and its host tab are not immediately created. They appear only as a consequence of some application logic.
Clearly a ViewModel that doesn’t exist can’t be listening for events. I could have put the coordinator’s logic inside the module class itself. This teaches me nothing. All I get is a module class with an unusual section and unexpected responsibilities … which is why I factored out the Coordinator in the first place.
Confronted with this scenario, what would you do? Is there a need for a coordinator or is there some pattern I’m overlooking?
This is a blog post, not a master’s thesis, but a little literature review seems in order. Gang of Four is silent on these subjects. Martin Fowler’s Patterns of Enterprise Architectures is the next stop on the “Pattern Legends” tour. A landmark effort first published “way back” in 2003 it remains “venerable” in the best sense.
Fowler’s initial thinking was confined to HTML web presentation problems. He expanded the inventory in subsequent years through a series of posts that can be reached from here. The combined corpus of patterns still resonate for WPF and Silverlight developers even if the details aren’t a perfect fit for these more recent technologies.
Most of this material concerns ways to separate logic from the view - Model View Controller (MVC), Model View Presenter (MVP) in its two flavors – Supervising Controller and Passive View - , and Presentation Model (which I am unable to distinguish from MVVM).
As I read him, the collaborator in control (C in MVC, P in MVP, PM in Presentation Model, VM in MVVM) is not responsible for creation or destruction of the collaborating group (aka, the “triad”). Something outside of the triad has these responsibilities. What does that thing look like and, more importantly, what do we call it?
I find three candidates in Fowler’s pattern book: Front Controller (344), Page Controller (333),and Application Controller (379) all of them expressed in web-specific terms. Can we re-purpose them?
None of them deal with disposing of views because that is simply not an issue in the HTML web world. That shouldn’t stop us from augmenting one of them if it is a good match in other respects.
Front Controller is out immediately. It is a single server-side controller that maps incoming requests to commands which, when invoked, result ultimately in pages shown to the user. It has no role in composing the pages. It’s a traffic cop.
Page Controller is more promising once stripped of its obligation to interpret HTTP requests. It acquires the parts we want – Model, View (and, implicitly, the Controller behind the view) – puts them together, and let’s them go on their way. However, as Fowler describes it, this class is mostly about request routing; it’s essentially a simplified combination of Front Controller - which does no page composition at all – and Application Controller.
The Application Controller (379) seems to me to be the most apt pattern. An application controller takes input from an “input controller” and determines the matching “domain logic” and “view”. Fowler has a Front Controller in mind when he speaks of “input controller”; in a RIA application the input could be an event raised in response to a gesture. The application controller then chooses the right recipe and cooks the view to show the user.
My misgiving about this pattern is the sheer scope of its ambitions. In Fowler’s epigram it is a “centralized point for handling screen navigation and the flow of an application”. That’s a big job, especially in a large WPF or Silverlight application. I’m not convinced we should have a “centralized point” for the application as a whole. Perhaps it makes more sense to think this way at the module level which is where I see the most need for “the thing that composes views”.
And yet I remain uneasy. I’m thinking we need something a bit more focused on a smaller set of concerns. I just want something to build and tear down one or more closely related MVVM triads.
Jeremy Miller hints at this in his artful analysis of composite applications, the excellent (albeit unfinished) “Build Your Own CAB” series. I direct your attention to his last completed entry, “The Main Players”, where he talks about Application Controller.
The ApplicationController controls the screen activate lifecycle and the lifecycle of the main form. In most of my systems other screens are activated by calling methods on the ApplicationController. In smaller systems the ApplicationController would also control screen workflow. In larger systems I would break some of that responsibility out into separate collaborating classes.
This reasoning leads him to describe three kinds of subsytem collaborators, one of which, the “Screen Conductor”, captures my intention almost exactly:
[Screen Conductor] controls the activation and deactivation lifecycle of the screens within the application. Depending on the application, the conductor may be synchronizing the menu state of the shell, attaching views in the main panel or otherwise, and calling hook methods on the Presenters [read ViewModels] to bootstrap the screen. It may also be just as important to deactivate a screen when it's made the inactive tab to stop timers.
My applications tend to be modularized with the help of something like Prism. I wouldn’t have one Application Controller with one Conductor. I would tend to have one “Conductor” per module and I would launch it from within my module class.
I’m not a stickler for one-per-module either; let the circumstances guide you. On the other hand, a profusion of Conductors should give you pause; it suggests that your ViewModels are anemic and underperforming.
But Ward, you called your version a “Coordinator”? Shouldn’t it be called “Conductor”.
Maybe it should. It was a toss up when I was thinking about it and I had forgotten Jeremy’s taxonomy until I started this post. John Papa and I had a blast dreaming up names that keep the avalanche of M’s and V’s going. “ViewMotivator” had possibilities: MVVMVM
A rose by any other name …
Posted by
Ward Bell
at
3:17 PM
14
comments
Links to this post
Maybe you heard that PixelShaders are a feature of Silverlight 3 … which is coming our way very soon.
Maybe you’re like I was, thinking that PixelShaders have no place in a business application. Hey, I love those rippling water demos as much as the next guy. But I’ll be looking for new employment the morning after I give my application a psychedelic makeover.
Then my buddy Joe Gershgorin introduced me to the “Grayscale Effect”, a PixelShader effect that de-saturates the colors of the targeted image, “graying” it out.
You say “So what?”
Here’s a hint: What’s the look of a disabled button? It’s gray.
I used to have one image for the active button state and another image for the disabled button state. If I’ve got 10 buttons, that’s 20 images to maintain. That’s double the payload winging over the wire to my Silverlight client.
It takes time (and skills I lack) to produce the first image; adding a disabled version is another step I can do without.
Now, instead of swapping button images when the enabled state changes, I add or remove the grayscale effect.
I’m not sure where Joe found the code for this effect. The first mention I can find is a blog post by Anders Bursjöö from June 2008. I don’t know if Anders post was first; I can confirm that it will take you through the details of the effect as it relates to WPF … which details are the same for Silverlight.
There is no need for me to repeat what Anders had to say or the many who followed him and took their turns at explaining it. I can also recommend Greg Schechter’s series of May 2008 posts on custom GPU-based effects for WPF.
Now put that knowledge to good use by shedding image bloat and waste your creativity on some other project.
Oh … you wanted code?
Dude ... it's XAML. We'll be here all day.
Ok, here are two snippets for your delectation. Imagine we're inside ResourceDictionary defining a style for the button.
Here is the image definition that will use template binding to pick up the image assigned to the button:
<Image
x:Name="ImagePresenter"
Margin="0,0,0,0"
Opacity="1"
Source="{TemplateBinding Image}"
Width="32"
Height="32"
HorizontalAlignment="Center"
Stretch="Uniform"
>
<Image.Effect>
<effects:GrayScaleEffect DesaturationFactor="1"/>
</Image.Effect>
</Image>
Notice how we added the effect, setting the DesaturationFactor to one (meaning … ahem … no desaturation). GrayScaleEffect is the class straight from Anders example.
Now let's look at the pertinent "Disabled" state governed by the VisualStateManager:
<VisualState x:Name="Disabled">
<Storyboard>
<DoubleAnimationUsingKeyFrames
BeginTime="00:00:00" Duration="00:00:00"
Storyboard.TargetName="ImagePresenter"
Storyboard.TargetProperty="(UIElement.Effect).(GrayScaleEffect.DesaturationFactor)">
<DiscreteDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
Notice how we’re identifying the effect and its DesaturationFactor property using attached property syntax. Setting the factor to zero results in the gray effect. We’re not using animation so the duration is zero-time.
Wouldn’t it be great if there were a one-liner to specify a no-animation Storyboard or just to skip the Storyboard altogether? Maybe someday.
Posted by
Ward Bell
at
5:56 PM
8
comments
Links to this post
Denny Boynton cornered me at TechEd for an ARCast video. He got me to talk about Silverlight application development with Prism (like that was hard for me, right?).
Here’s the link: Building Modular Applications Using Microsoft Silverlight and WPF.
Posted by
Ward Bell
at
2:01 PM
0
comments
Links to this post
After too many hours of debugging and hair-pulling, I finally figured out why my Prism EventAggregator (EA) subscriptions were not working in an important case. The reason made perfect sense ... once understood.
Here is the setup.
This is a canonical example of cross-module Eventing, perfect for Prism's EventAggregator.
Here is the subscription in the CustomerOrders module:
_eventAggregator.GetEvent<CustomerSelectedEvent>()
.Subscribe(NotifyView);
But the subscription is never raised and NotifyView is never called! The CustomerOrders module is instantiated alright but it never shows a Customer.
Maybe I didn't publish correctly. So I try subscribing inside some other class that I'm sure is in-use ... such as a class in CustomerSearch itself.
_eventAggregator.GetEvent<CustomerSelectedEvent>()
.Subscribe(HeardIt);
...
public void HeardIt(object dummy) {
var sink = "I heard you";
}
Put a breakpoint on HeardIt; works like a charm.
I break at the point where I'm adding subscriptions to CustomerSelectedEvent; they are all there! I can see both subscriptions in the CustomerSelectedEvent's list of subscriptions.
After a few frustrating hours, I happen to look at _eventAggregator subscriptions when HeardIt is called. Now there is only one, the one for HeardIt. The CustomerOrders subscription is gone!
Then I remember, EventAggregator holds weak references to subscriptions by default ... so the subscriber doesn't have to unsubscribe when it is disposed or garbage collected (GC'd). This is a very cool feature. Sadly, I immediately suspect that this is the source of my problem. To test that thesis, I force the subscription to use strong references.
const bool keepAlive = true;
_eventAggregator.GetEvent<CustomerSelectedEvent>()
.Subscribe(NotifyView, ThreadOption.UIThread, keepAlive);
It works!
Of course now the instance in which I make this subscription will hang around for the life of the application (the life of the EA to be precise). This is a potential memory leak. If I'm going to make and forget a lot of these instances, I better remember to unsubscribe, perhaps via IDisposable. That doesn't seem like fun.
Why did the subscriber disappear ... taking its subscription with it?
Prism decoupling was just doing its job. Most Prism modules that you will ever create actually disappear rather quickly.
You can verify that thesis. Drop the following in your module class (the inheritor of IModule) and set a breakpoint:
// Destructor to demo when GC'd
~MyModule() {
System.Console.WriteLine("Goodbye, MyModule");
}
If your module class is very simple as it should be ... perhaps some type registrations before dropping a view into a region ... you'll see that destructor called in no time; happens almost immediately for me because I'm running in a VM where the garbage collector is very busy.
So my subscription disappeared because I subscribed within a class that itself disappears ... really quickly.
In my example, I was unable to find or construct an instance of a class that outlived the module.
I could have put it inside the View (the ViewModel to be precise); once the View was injected into the visual tree, it would outlive the module because the visual tree would keep it alive. That is why the subscription to "HeardIt" worked in the CustomerSearch module . I had subscribed inside a ViewModel after it's companion View had been presented. It didn't matter that the CustomerSearch module class instance, which had created that ViewModel, had long since been GC'd.
Unfortunately, I can't follow that example in the CustomerOrders module. Can you see why?
Remember I said at the beginning that the CustomerOrders module waits for the first publication of CustomerSelectedEvent before it shows itself. If I don't show anything, everything I create in that module evaporates (get's GC'd) before the first publication of CustomerSelectedEvent!
I have to do something to keep the module around until it can do its work. I'm sure you can think of plenty of ways; I did. They're mostly ugly. I decided that I should put my solution near the cause of the problem ... and so I ensure that at least one subscription has "keepAlive = true".
I won't worry about the potential memory leak from hanging on to the module class; I don't expect to have more than one instance of this module in the lifetime of this application. I'll just document the issue and move on.
Hope this helps you!
p.s.: No ... I did not actually put this logic in the module class. Module classes are supposed to be bare bones. I put it in a Coordinator class, an instance of which is resolved by the CustomerOrders module. The problem is the same. The coordinator is referenced only by the module so it evaporates when the module does. I thought this detail would only interfere with exposition were it introduced earlier.
Posted by
Ward Bell
at
12:56 PM
12
comments
Links to this post