Original article:
Http://www.global-webnet.net/blogengine/post/2009/03/06/Silverlight-and-WPF-CompositeWPFPrism-supports-multi-targeting-(single-shared-codebase). aspx
A recent Microsoft codeplex Forum interaction had me updating the sdms application so that the employee module and all of it's views were multi-targeting (worked on both desktop and Silverlight ). the reasoning follows below:
Not long ago, the interaction in the Microsoft codeplex Forum prompted me to update sdms.ProgramTo make the employee module and their view multi-targeting (working in WPF and Silverlight at the same time ). The reason is as follows:
Developer writes "I'm not looking for multi-targeting support, I just want an SL app that uses WCF and is maintainable and testable ."
The developer said: "I don't care. Multi-targeting As long SL Program usage WCF And make the maintenance and measurable "
My response follows:
My reply is as follows:
I don't think they are mutually exclusive-I believe you may want/need multi-targeting support. let me explain by example-I just spent the last couple of hours updating the sdms application so that the modules folder is supported under both desktop and Silverlight. why? "Maintainable and testable". I created a unit test for you and checked everything in (been really wanting to do this for a while and this gave me good reason ).
I don't think they are mutually exclusive. I believe what you need is multi-targeting support. Let me use an example to illustrate it. I only spent a few hours updating the sdms program so that the module is supported in both WPF and Silverlight. Why? "Maintainable and testable" I created a unit test for you and checked everything (it really didn't take long to do this, which gives me a good reason)
The key point here is that the "only" code that will be different will be the XAML (Silverlight/WPF) and the actual WCF Service call. I did however create my desktop WCF Service Using async communications so you won't find any Silverlight conditional statements anywhere in the business logic layer or data layer (they are one and the same code for both sides ). I shoshould give a plug for the project linker (blogged about on my blog site w/webcast); all my time was spent creating empty WPF views and implementing the interface on them.
The key point is that the only differenceCodeIs a call to the XAML and WCF services. Although I used asynchronous communication to create a desktop version of the WCF Service, you cannot find the Silverlight condition declaration in any part of the business logic layer (the Code of WCF and Silverlight is the same ). I shoshould give a plug for the project linker. I spent all my time creating an empty WPF view and implementation interface.
[Testmethod]
Public void testmethod1 ()
{
// Imodule does all of the heavy lifting-configures all interfaces
// So we'll just use it to set things up.
Imodule module = container. Resolve <imodule> ();
Module. initialize ();
// Resolve the employeelist presenter
Employeelistpresenter mockview = container. Resolve <employeelistpresenter> ();
// Give WCF Service a chance to complete
Thread. Sleep (2000 );
// Cast so we can easily access Presentation Model
Employeepresentationmodel model =
(Employeepresentationmodel) mockview. model;
Assert. areequal (3, model. employeelist. Count,
"Maid list! ");
}
Note we can put the testing thread to sleep I just tested everything short of the UI which is databound to the presentation model (nothing to test in the view) all the way through the WCF Service and back. since my presentationmodel implements inotifypropertychanged I can rest assured my view will work (assuming I did my binding correctly ).
Let's see what imodule was up to (showing the specified tiveness of Multi-targeting)
Public class moduleemployee: imodule
{
// For Class use
Private readonly iunitycontainer container;
Private readonly iregionviewregistry regionviewregistry;
/// <Summary>
/// Constructor: Setup class
/// </Summary>
/// <Param name = "Container"> </param>
/// <Param name = "regionviewregistry"> </param>
Public moduleemployee (iunitycontainer container,
Iregionviewregistry regionviewregistry)
{
This. Container = container;
This. regionviewregistry = regionviewregistry;
}
Public void initialize ()
{
Registerviewandservices ();
// Employeemodule-views folder
Regionviewregistry. registerviewwithregion ("mainregion ",
() => Container. Resolve <employeemainpresenter> (). View );
Regionviewregistry. registerviewwithregion ("frmcaption ",
() => Container. Resolve <frmcaptionpresenter> (). View );
Regionviewregistry. registerviewwithregion ("frmemployeelist ",
() => Container. Resolve <employeelistpresenter> (). View );
Regionviewregistry. registerviewwithregion ("tabinformation ",
() => Container. Resolve <employeeinformationpresenter> (). View );
Regionviewregistry. registerviewwithregion ("tabassigned ",
() => Container. Resolve <employeeassignedpresenter> (). View );
Regionviewregistry. registerviewwithregion ("tabinwork ",
() => Container. Resolve <employeeinworkpresenter> (). View );
Regionviewregistry. registerviewwithregion ("frmstatus ",
() => Container. Resolve <frmstatuspresenter> (). View );
}
Private void registerviewandservices ()
{
Container. registertype <iemployeemainview, employeemainview> ()
// Layers
. Registertype <iemployeeproviderbll, employeeproviderbll> ()
. Registertype <iemployeeproviderdal, employeeproviderdal> ()
// Views
. Registertype <ifrmstatusview, frmstatusview> ()
. Registertype <ifrmcaptionview, frmcaptionview> ()
. Registertype <iemployeelistview, employeelistview> ()
. Registertype <iemployeelistview, employeelistview> ()
. Registertype <iemployeeinworkview, employeeinworkview> ()
. Registertype <iemployeeassignedview, employeeassignedview> ()
. Registertype <iemployeeinformationview, employeeinformationview> ()
// Services
. Registertype <iemployeeservice, employeeservice> ()
// Models
. Registertype <iemployeepresentationmodel, employeepresentationmodel> (
New containercontrolledlifetimemanager ());
}
}
}
It did some pretty heavy lifting which tells me everything that will be executed during Silverlight runtime-works.
The following is the presenter, which is responsible for updating the presentation model (which the view is observing ). you can see that my desktop unit test has tively exercises finished, if not all, logic within the process.
Note: Silverlight unit testing is done in a browser... I 'd rather take this approach.
Hope this helps in your quest to finding an architecture that works for you!
Bill
Public class employeelistpresenter: presenterbase <iemployeelistview>
{
Readonly iemployeeservice employeeservice;
Readonly ieventaggregator aggregator;
Readonly iemployeepresentationmodel model;
/// <Summary>
/// Constructor: Setup class
/// </Summary>
/// <Param name = "Container"> </param>
/// <Param name = "View"> </param>
Public employeelistpresenter (
Iemployeelistview view,
Iemployeepresentationmodel model,
Iunitycontainer container,
Ieventaggregator aggregator,
Iemployeeservice Service): Base (view, model, container)
{
This. aggregator = aggregator;
This. employeeservice = service;
This. Model = model;
// Subscribe to listboxchanged event and
Aggregator. getevent <listboxchangedevent> ()
. Subscribe (listboxchangedeventhandler, true );
Aggregator. getevent <employeeevent> ()
. Subscribe (employeeeventhandler, true );
// Async call to service to populate employee list.
// The employeelisteven thandler will be called when
// Data is already ed
Employeeservice. getemployeelist ();
}
/// <Summary>
/// Subscribed to in constructor-updates the model's
/// Selectedemployee property every time a new employee is selected
/// </Summary>
/// <Param name = "ARGs"> </param>
Private void listboxchangedeventhandler (selectionchangedeventargs ARGs)
{
Model. selectedemployee = args. addeditems [0] As employee_data;
Statusbarevent sbevent = aggregator. getevent <statusbarevent> ();
If (sbevent! = NULL)
Aggregator. getevent <statusbarevent> (). Publish (
New statusbardata
{
Message = string. Format ("You clicked {0 }",
Model. selectedemployee. displayvalue ),
Panel = statuspanel. Left
});
}
/// <Summary>
/// Handler for when employee list is returned by service call
/// Getemployeelist ()
/// </Summary>
/// <Param name = "ARGs"> </param>
Private void employeeeventhandler (employeeeventargs ARGs)
{
Model. employeelist = args. employeelist;
}
}