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

原文链接:

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:

不久前 Microsoft Codeplex论坛里的交互,促使我更新了SDMS程序,以使雇员模块和他们的视图是Multi-targeting的(同时工作在WPF和Silverlight)。原因如下:

Developer writes "I'm not looking for multi-targeting support, I just want an SL app that uses WCF and is maintainable and testable."
开发者说:“我不关心multi-targeting的支持,我只要SL程序使用WCF并使可维护和可测的
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).
我不认为他们是互斥的,我相信你应该要的就是multi-targeting支持。让我用个例子来展开说明。我只花了几个小时更新了SDMS程序,以使模块在WPF和Silverlight下都被支持。为什么?“可维护和可测试的”我为你创建了一个单元测试并检查了一切(做了这些真的没用多长时间,这给了我一个很好的理由)
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.

关键点在于,唯一可能不同的代码是XAML和WCF服务的调用。虽然我使用异步通讯创建了桌面版的WCF服务,你也在任何业务逻辑层的地方都找不到Silverlight的条件声明(WCF和Silverlight两遍代码是相同的一个代码)。I should give a plug for the Project Linker。我时间都花费在创建空的WPF视图和实现接口上了。

[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;

    }

}

posted @ 2009-07-09 19:18  傻样精英  阅读(1446)  评论(1编辑  收藏  举报