65.9K
CodeProject 正在变化。 阅读更多。
Home

简化 MEF:动态加载 Silverlight .xap

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (35投票s)

2010年6月6日

Ms-PL

9分钟阅读

viewsIcon

233463

downloadIcon

2643

如何使用托管可扩展性框架(Managed Extensibility Framework)以视图模型样式(View Model Style)动态加载 Silverlight .XAP

imgBC.jpg

MEF 简化版:使用托管可扩展性框架(Managed Extensibility Framework)动态加载 Silverlight .XAP

实时示例: http://silverlight.adefwebserver.com/dynamicMEF

关于 托管可扩展性框架 (MEF) 的文档描述其目的为“……简化可扩展应用程序的创建”。这是一个非常宽泛的陈述。这就像将核物理描述为“物理学领域中研究原子核的组成和相互作用的学科”。在某些方面,MEF 对编程的强大作用就像核物理对科学的作用一样。

然而,对于“普通人”来说,核物理之所以重要,仅仅是因为它能在你按开关时点亮灯。对于 MEF,我们将用它来简单地加载一个包含 Silverlight 应用程序的 .xap 文件。相信我,它能做的远不止这些,但 MEF 目前的状态就像几年前的 Visual State Manager 一样,功能强大但难以使用,所以你可能会在实现其其他功能之前,等待更高级的工具出现。

一个快速简单的胜利——献给那些难以理解 MEF 的朋友

你可能听说过 MEF 及其伟大之处。然而,你可能会觉得它有些令人困惑且难以理解。不要害怕。本文就是为你准备的!我们的目标是让你获得一个快速简单的胜利。你能够理解它。你将能得到一些可以使用的东西,并且你可以告诉你同事:“MEF?是的,我正在使用它,非常好。”

朋友们的帮助——Glenn Block 和 Davide Zordan

本文涵盖了 Glenn BlockDavide Zordan 的资料和代码。在读完本文后,你一定要阅读他们的文章,因为他们深入探讨了 MEF 的更多功能。本文的区别在于,我们将一步步地进行讲解,并且我们会对所有内容使用 视图模型样式(View Model Style)

如果你是第一次接触视图模型样式(View Model Style),建议你阅读 Silverlight View Model Style : An (Overly) Simplified Explanation 以获得入门介绍。

应用程序

首先,让我们一步步地看看完成后的应用程序。

首先,我们将两个 Silverlight 应用程序添加到主 Silverlight 项目的 Web 项目的 **ClientBin** 目录中。

当你启动主 Silverlight 项目时,你可以从下拉列表中选择一个 Silverlight .xap 文件,然后点击加载按钮。

该 .xap 文件将被动态加载。

创建“注入式” Silverlight 应用程序

我们将首先创建两个 Silverlight 应用程序。它们将各自生成一个 **.XAP** 文件,该文件将被放置在主 Silverlight 项目的 **ClientBin** 目录中。主 Silverlight 项目随后将动态加载它们并显示它们。

打开 **Microsoft Expression Blend 4**,然后选择 **文件** > **新建项目**(我们使用 **Expression Blend** 是因为它提供了易于创建 **视图模型** 样式控件的模板),当然,所有这些代码也可以在 **Visual Studio** 中工作(我实际上最初是用 **Visual Studio** 编写所有代码的)。

创建一个名为 **InjectedApplication1** 的新 **Silverlight 应用程序**。

在 **项目** 窗口中,右键单击 **引用**,然后选择 **添加引用…**。

导航到

..\Program Files\Microsoft SDKs\Silverlight\v4.0\Libraries\Client\

添加对以下项的引用

  • System.ComponentModel.Composition.dll
  • System.ComponentModel.Composition.Initialization.dll

(你需要这些引用,以便我们可以向类添加 MEF 属性,从而使应用程序能够被 MEF 识别)。

其他说明

  • 只有当你的 Silverlight 应用程序(部分程序集)使用 CompositionInitializer 时,才需要引用 System.ComponentModel.Composition.Initialization.dll。
  • 对于部分程序集,你应该将 MEF 引用(和其他共享引用)设置为 CopyLocal = false。
  • CopyLocal = false 将减小 XAP 的大小,同时避免重复。

删除 **MainPage.xaml** 页面。

选择 **文件** > **新建项…**。

创建一个名为 **MainPage.xaml** 的带视图模型的用户控件

打开 **MainPageModel.cs** 文件。

用以下代码替换所有代码:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Data;
using System.ComponentModel;

namespace InjectedApplication1
{
    public class MainPageModel : INotifyPropertyChanged
    {
        public MainPageModel()
        {
            Hello = "Hello From InjectedApplication1";
        }

        #region Hello
        private String _Hello;
        public String Hello
        {
            get { return _Hello; }
            private set
            {
                if (Hello == value)
                {
                    return;
                }

                _Hello = value;
                this.NotifyPropertyChanged("Hello");
            }
        }
        #endregion

        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }
        #endregion
    }
}

打开 **MainPage.xaml.cs**。

用以下代码替换所有代码:

using System.ComponentModel.Composition;
using System.Windows.Controls;

namespace InjectedApplication1
{
    [Export(typeof(UserControl))]
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            this.InitializeComponent();
        }
    }
}

(注意,我们正在向类添加“[Export(typeof(UserControl))]”属性,以便 MEF 可以识别它)。

打开 **MainPage.xaml**。

在 **对象和时间线** 窗口中单击 **UserControl**。

在 **属性** 中,将 **布局** 的宽度高度设置为200

单击 **DataContext** 旁边的新建按钮。

选择 **MainPageModel**,然后单击确定

  • 单击数据选项卡。
  • 数据上下文下,单击并拖动 **Hello: (String)** 到设计表面。

(注意,如果数据上下文为空,请生成项目,关闭 **MainPage.xaml** 并重新打开它)。

文本将显示出来。

项目选项卡中,选择生成项目

在你的计算机上的文件管理器中,当你查看

...\InjectedApplication1\InjectedApplication1\Bin\Debug

你会看到

InjectedApplication1.xap

稍后,你将把它拖放到主 Silverlight 应用程序的 **ClientBin** 目录中,以便动态注入。

重复此过程,创建一个名为 **InjectedApplication2** 的另一个 Silverlight 应用程序。

(注意:你可以从以下链接下载 .xap 文件:InjectedApplicationXAPs.zip

你也可以按 F5 来生成并运行项目。它本质上是一个正常的 Silverlight 项目,只添加了少量的引用、using 语句和属性。

主 Silverlight 应用程序

现在,我们准备创建将使用 MEF 来动态注入我们刚刚创建的两个 Silverlight 应用程序的主 Silverlight 应用程序。

创建一个名为 **MainApplication** 的新 **Silverlight 应用程序 + 网站**。

添加对以下项的引用

  • System.ComponentModel.Composition.dll
  • System.ComponentModel.Composition.Initialization.dll
  • System.Windows.Interactivity

(注意:从 ..\Program Files\Microsoft SDKs\Expression\Blend\Silverlight\v4.0\Libraries\ 获取 System.Windows.Interactivity.dll)。

(注意:System.Windows.Interactivity.dll 用于使行为(稍后使用)生效。它不是 MEF 的一部分)。

删除 **MainPage.xaml** 页面。

创建一个名为 **MainPage.xaml** 的带视图模型的用户控件

添加一个新文件夹并命名为 **HelperClasses**。

ICommand 支持

向 **HelperClasses** 文件夹添加一个名为 **DelegateCommand.cs** 的类。

用以下代码替换所有代码:

using System.Windows.Input;
using System;

namespace MainApplication
{
    // From http://johnpapa.net/silverlight/5-simple-steps-to-commanding-in-silverlight/
    public class DelegateCommand : ICommand
    {
        Func<object, bool> canExecute;
        Action<object> executeAction;
        bool canExecuteCache;

        public DelegateCommand(Action<object> executeAction, Func<object, bool> canExecute)
        {
            this.executeAction = executeAction;
            this.canExecute = canExecute;
        }

        #region ICommand Members

        public bool CanExecute(object parameter)
        {
            bool temp = canExecute(parameter);

            if (canExecuteCache != temp)
            {
                canExecuteCache = temp;
                if (CanExecuteChanged != null)
                {
                    CanExecuteChanged(this, new EventArgs());
                }
            }

            return canExecuteCache;
        }

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)
        {
            executeAction(parameter);
        }

        #endregion
    }
}

这个类使我们能够轻松调用 ICommands。你可以在以下位置找到有关此类的更多信息:http://johnpapa.net/silverlight/5-simple-steps-to-commanding-in-silverlight/

部署目录服务(复杂内容!)

现在我们开始处理“复杂内容”。我们将创建一个类,该类将按需实际加载 .xap。我们将创建一个接口来定义其方法:**AddXap** 和 **RemoveXap**,然后我们将实现该类(为了简化,我们将两者放在同一个文件中;通常情况下,你会将接口放在单独的文件中)。

向 **HelperClasses** 文件夹添加一个名为 **DeploymentCatalogService.cs** 的类。

用以下代码替换所有代码:

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel;

namespace MainApplication
{
    // A type from which to derive the contract name that is used to export the
    // type or member marked with this attribute,
    public interface IDeploymentCatalogService
    {
        // The class that will implement this interface will implement these two methods
        void AddXap(string uri, Action<AsyncCompletedEventArgs> completedAction = null);
        void RemoveXap(string uri);
    }

    [Export(typeof(IDeploymentCatalogService))]
    public class DeploymentCatalogService : IDeploymentCatalogService
    {
        private static AggregateCatalog _aggregateCatalog;
        Dictionary<string, DeploymentCatalog> _catalogs;

        public DeploymentCatalogService()
        {
            _catalogs = new Dictionary<string, DeploymentCatalog>();
        }

        public static void Initialize()
        {
            _aggregateCatalog = new AggregateCatalog();
            _aggregateCatalog.Catalogs.Add(new DeploymentCatalog());
            CompositionHost.Initialize(_aggregateCatalog);
        }

        public void AddXap(string uri, Action<AsyncCompletedEventArgs> completedAction = null)
        {
            // Add a .xap to the catalog
            DeploymentCatalog catalog;
            if (!_catalogs.TryGetValue(uri, out catalog))
            {
                catalog = new DeploymentCatalog(uri);

                if (completedAction != null)
                {
                    catalog.DownloadCompleted += (s, e) => completedAction(e);
                }
                else
                {
                    catalog.DownloadCompleted += catalog_DownloadCompleted;
                }

                catalog.DownloadAsync();
                _catalogs[uri] = catalog;
                _aggregateCatalog.Catalogs.Add(catalog);
            }
        }

        void catalog_DownloadCompleted(object sender, AsyncCompletedEventArgs e)
        {
            // Chekcks for errors loading the .xap
            if (e.Error != null)
            {
                throw e.Error;
            }
        }

        public void RemoveXap(string uri)
        {
            // Remove a .xap from the catalog
            DeploymentCatalog catalog;
            if (_catalogs.TryGetValue(uri, out catalog))
            {
                _aggregateCatalog.Catalogs.Remove(catalog);
                _catalogs.Remove(uri);
            }
        }
    }
}

有关此类内容的详细解释,请参阅这篇文章这篇文章

是的,我们可以深入探讨“它如何工作”的细节,但这已在上面的链接中涵盖。如果你想知道“它做什么”,它会将请求的 .xap 加载到一个“目录”中。我们接下来将要介绍的代码会将 .xap 从目录中取出并加载到主 Silverlight 应用程序中。

使用 MEF 动态下载 Silverlight 应用程序

我们现在将创建视图模型。它基本上执行上面图示中的功能。

打开 **MainPageModel.cs**,用以下代码替换所有代码:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Data;
using System.ComponentModel;
using System.Windows.Input;
using System.ComponentModel.Composition;
using System.Windows.Controls;
using System.Linq;
using System.Collections.ObjectModel;

namespace MainApplication
{
    public class MainPageModel : INotifyPropertyChanged, IPartImportsSatisfiedNotification
    {
        private string strSelectedXAP = "";
        public MainPageModel()
        {
            // Set the command properties
            LoadXAPCommand = new DelegateCommand(LoadXAP, CanLoadXAP);
            SetMEFPanelCommand = new DelegateCommand(SetMEFPanel, CanSetMEFPanel);

            // The following line prevents Expression Blend
            // from showing an error when in design mode
            if (!DesignerProperties.IsInDesignTool)
            {
                // This call causes DeploymentCatalogService() to be called
                CompositionInitializer.SatisfyImports(this);
            }

        }

        #region Commanding
        public ICommand LoadXAPCommand { get; set; }

        public void LoadXAP(object param)
        {
            // Get the .xap selected
            ComboBoxItem objComboBoxItem = (ComboBoxItem)param;
            strSelectedXAP = objComboBoxItem.Content.ToString();

            // Have MEF load the .xap
            CatalogService.AddXap(strSelectedXAP);

            // If the .xap is already loaded we need to call this method
            // because OnImportsSatisfied() will not fire
            LoadSelectedModule();
        }

        private bool CanLoadXAP(object param)
        {
            return true;
        }

        public ICommand SetMEFPanelCommand { get; set; }
        public void SetMEFPanel(object param)
        {
            MefPanel = (Panel)param;
        }

        private bool CanSetMEFPanel(object param)
        {
            // Must be a Panel
            return ((param as Panel) != null);
        }
        #endregion

        // Specifies that a property, field, or parameter value should be provided by
        // the System.ComponentModel.Composition.Hosting.CompositionContainer.
        [Import]
        public IDeploymentCatalogService CatalogService { get; set; }

        // Specifies that a property, field, or parameter should be populated with all
        // matching exports by the System.ComponentModel.Composition.Hosting.CompositionContainer.
        [ImportMany(AllowRecomposition = true)]
        public Lazy<UserControl>[] MEFModuleList { get; set; }

        #region IPartImportsSatisfiedNotification Members
        // This fires when a .xap has been loaded
        // If the .xap has already been loaded this will not fire
        public void OnImportsSatisfied()
        {
            LoadSelectedModule();
        }
        #endregion

        #region LoadSelectedModule
        private void LoadSelectedModule()
        {
            // Ensure that we have a Panel to add the .xap to
            if (MefPanel != null)
            {
                // Create a name for the .xap without the ".xap" part
                string strRevisedSelectedXAPName = strSelectedXAP.Replace(".xap", ".");

                // Determine if the .xap is already loaded
                var SelectedMEFModuleInPanel = (from Module in MefPanel.Children.Cast<UserControl>()
                                                where Module.ToString().Contains(strRevisedSelectedXAPName)
                                                select Module).FirstOrDefault();

                // If the .xap is not loaded
                if (SelectedMEFModuleInPanel == null)
                {
                    // Clear the panel
                    MefPanel.Children.Clear();

                    // Get the selected .xap 
                    var SelectedMEFModule = (from Module in MEFModuleList.ToList()
                                             where Module.Value.ToString().Contains(strRevisedSelectedXAPName)
                                             select Module).FirstOrDefault();

                    // If the .xap is found
                    if (SelectedMEFModule != null)
                    {
                        // Add the .xap to the main page
                        MefPanel.Children.Add(SelectedMEFModule.Value);
                    }
                }
            }
        }
        #endregion

        #region MefPanel
        private Panel _MefPanel;
        public Panel MefPanel
        {
            get { return _MefPanel; }
            private set
            {
                if (MefPanel == value)
                {
                    return;
                }

                _MefPanel = value;
                this.NotifyPropertyChanged("MefPanel");
            }
        }
        #endregion

        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }
        #endregion
    }
}

上面图示展示了视图模型的类视图。

我们已经达到了“复杂内容”的顶峰。接下来的内容就比较简单了。本文末尾的链接将完整解释上面的代码如何工作。一般的解释包含在代码注释中。

视图(UI)

打开 **MainPage.xaml**。

切换到 **XAML 视图**。

用以下代码替换所有代码:

<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MainApplication"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
x:Class="MainApplication.MainPage"
UseLayoutRounding="True"
Width="640" Height="480">
<UserControl.Resources>
<local:MainPageModel x:Key="MainPageModelDataSource" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource MainPageModelDataSource}}">
<TextBlock HorizontalAlignment="Left" Height="40" Margin="8,8,0,0" TextWrapping="Wrap" Text="Main Page"
VerticalAlignment="Top" Width="168" FontSize="32" Foreground="#FFC82929"/>
<Button Content="Load" HorizontalAlignment="Right" Height="24" Margin="0,24,144,0" 
VerticalAlignment="Top" Width="96" />
<StackPanel x:Name="MEFContent" Margin="12,56,16,16"/>
<ComboBox x:Name="comboBox" VerticalAlignment="Top" Margin="192,28,256,0" SelectedIndex="0">
<ComboBoxItem Content="InjectedApplication1.xap"/>
<ComboBoxItem Content="InjectedApplication2.xap"/>
</ComboBox>
</Grid>
</UserControl>

切换回设计视图,你将看到 UI。

连接起来!——面板

我们需要通过注册堆积面板(StackPanel)作为注入 Silverlight 应用程序的位置,以及加载按钮来加载 Silverlight 应用程序,来连接视图视图模型

获取一个InvokeCommandAction行为。

将其拖放到对象和时间线窗口中的LayoutRoot上。

在行为的属性中:

  • 选择Loaded作为EventName(这意味着该行为将在应用程序启动时运行)。
  • 单击常用属性下的Command旁边的数据绑定图标。

选择 **SetMEFPanelCommand**,然后单击确定

CommandParameter选择高级选项(现在我们需要明确指定要将 Silverlight 应用程序放置在哪个 Panel 元素内)。

选择数据绑定...

Element Property选项卡中选择MEFContentStackPanel,然后单击确定

连接起来!——加载按钮

将一个InvokeCommandAction行为拖放到[Button]上。

在行为的属性中:

  • 选择Click作为EventName(当点击加载按钮时,该行为将运行)。
  • 单击常用属性下的Command旁边的数据绑定图标。

绑定到 **LoadXAPCommand**。

CommandParameter选择高级选项,然后选择Element Property Binding...

对象和时间线窗口中单击comboBox

comboBox 的属性下拉列表中选择SelectedItem,然后单击确定

上面的图示显示了现在连接的内容。

还有一件事——初始化DeploymentCatalogService

打开 **App.xaml.cs** 文件。

Application_Startup 方法更改为:

    private void Application_Startup(object sender, StartupEventArgs e)
    {
        DeploymentCatalogService.Initialize();
        this.RootVisual = new MainPage();
    }

我们需要添加“DeploymentCatalogService.Initialize()”代码来初始化DeploymentCatalogService,使其能够工作。

添加 Silverlight 应用程序

当你在项目窗口中查看网站项目时,你只会看到 MainApplication.xap 文件在 **ClientBin** 目录中。

InjectedApplication1.xapInjectedApplication2.xap 文件拖放到该目录中。

F5 来生成并运行应用程序。

你将能够动态加载每个应用程序。

为什么要使用 MEF 来动态加载 Silverlight 应用程序?

还有其他方法可以动态加载 Silverlight .xap。我有一个项目(Introducing SilverlightDesktop.net)可以做到这一点。我遇到的问题是处理项目依赖项。例如,如果你从 Silverlight Control Toolkit 中使用控件,它将无法正常工作。MEF 可以解析任何依赖项。这一点在使用视图模型样式(View Model Style)时非常重要。设计师可以添加许多自定义行为和其他依赖项。

这是一个使用 MEF 的例子,它比使用替代方法(提供相同强大的功能)需要更少的代码。MEF 的难点在于将正确的属性放在正确的位置并正确设置它们的值。希望本教程提供了一个有用的示例。

MEF 还有更多功能

MEF 的功能远不止于此,这个简单的例子几乎是对该技术的一种轻视。然而,我完全预计 Microsoft 会创建更多工具和示例代码,以使它在未来更容易实现其其他功能。

特别感谢 Glenn Block 对本文的帮助。

延伸阅读

© . All rights reserved.