Silverlight and WPF - CompositeWPF/Prism supports multi-targeting (single shared codebase)

Source Code available at http://www.Codeplex.com/SDMS 
Project Linker Blog w/Webcast HERE  

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: 

Developer writes "I'm not looking for multi-targeting support, I just want an SL app that uses WCF and is maintainable and testable."

My response 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 to).

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 should 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.

[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,
                   "Employee list should have three records!");

}


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 effectiveness 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 effectively exercises many, 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 EmployeeListEventHandler
will be called when
        // data is received

        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 to

    /// GetEmployeeList()

    /// </summary>

    /// <param name="args"></param>

    private void EmployeeEventHandler(EmployeeEventArgs args)

    {

        model.EmployeeList = args.EmployeeList;

    }

 }


Tags: , ,
Categories: CompositeWPF


Actions: E-mail | Permalink |  Grammar/Typo/Better way? Please let me know