“托管扩展性框架(Managed Extensibility Framework,简称MEF),是微软.NET框架下为提高应用和组件复用程度而推出的,用于使组件能够最大化的重用。使用MEF能够使静态编译的.NET应用程序转换为动态组合,这将是创建可扩展应用、可扩展框架和应用扩展的好途径。它将做为.NET Framework 4.0的组成部分之一发布。现在,MEF也将被包含在Silverlight 4.0中。
那么MEF是怎样工作的呢?简单分为三个步骤:
•Export (输出)
•Import (输入)
•Compose (组合)
简短说一下MEF的工作原理,MEF的核心包括一个catalog和一个CompositionContainer。category用于发现扩展,而container用于协调创建和梳理依赖性。每个可组合的Part提供了一个或多个Export,并且通常依赖于一个或多个外部提供的服务或Import。每个Part管理一个实例为应用程序运行。
下面我们做一个小型可扩展计算器示例来解释这三个过程
1.首先下载MEF框架包,Silverlight 4.0会自带,不过微软已经将其开源了。
http://www.codeplex.com/MEF
2.创建一个Silverlight Navigate Application ,并添加程序集引用(MEF_Beta_2\bin\SL目录下 System.ComponentModel.Composition.dll)
在项目下添加两个类文件Package.cs和PackageCatalog.cs,这两个文件在最新的MEF版本中没有提供,主要用于加载silverlight的Xap包。
这两个文件在MEF框架的Sample中提供了(MEF_Beta_2\Samples\PictureViewer\PictureViewer.Common),将这两个类的访问修饰符改为public, 添加后注意修改命名空间。
3.修改Home.cs 类
代码
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel.Composition;
using System.ComponentModel;
namespace MefDemo
{
//用于更新界面的委托
public delegate void OperateHandler(IOperate Op);
/// <summary>
/// 运算器接口
/// </summary>
public interface IOperate
{
double Op(double left, double right);
string Symbol { set; get; }
string Label { get; set; }
}
/// <summary>
/// 加法运算器
/// </summary>
[Export(typeof(IOperate))]
public class AddButton : Button, IOperate
{
[Import("AddButtonContract",AllowRecomposition = true)]
public string Label { get { return this.Content.ToString(); } set { this.Content = value; } }
[Import("AddSybomContract", AllowRecomposition = true)]
public string Symbol { set; get; }
[Import("ClickHandler")]
public OperateHandler ClickAction { get; set; }
#region IOperate 成员
public double Op(double left, double right)
{
return left + right;
}
#endregion
public AddButton()
{
this.Click += (s, e) => ClickAction(this);
}
}
/// <summary>
/// 减法运算器
/// </summary>
[Export(typeof(IOperate))]
public class SubButton : Button, IOperate
{
[Import("SubButtonContract",AllowRecomposition=true)]
public string Label { get { return this.Content.ToString(); } set { this.Content = value; } }
[Import("SubSybomContract", AllowRecomposition = true)]
public string Symbol { set; get; }
[Import("ClickHandler")]
public OperateHandler ClickAction { get; set; }
#region IOperate 成员
public double Op(double left, double right)
{
return left - right;
}
#endregion
public SubButton()
{
this.Click += (s, e) => ClickAction(this);
}
}
/// <summary>
/// 为每个运算器的属性提供值
/// </summary>
public class ComponentAttributeProvider
{
[Export("AddButtonContract")]
public string AddLabel { get { return "Add"; } }
[Export("AddSybomContract")]
public string AddSymbol { get { return "+"; } }
[Export("SubButtonContract")]
public string SubLabel { get { return "Sub"; } }
[Export("SubSybomContract")]
public string SubSymbol { get { return "-"; } }
}
}
4.修改 Home.xaml
代码
<navigation:Page x:Class="MefDemo.Home"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480"
Title="Home"
Style="{StaticResource PageStyle}">
<navigation:Page.Resources>
<ItemsPanelTemplate x:Key="ItemsPanelTemplate1">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</ItemsPanelTemplate>
</navigation:Page.Resources>
<Grid x:Name="LayoutRoot">
<ScrollViewer x:Name="PageScrollViewer" Style="{StaticResource PageScrollViewerStyle}">
<StackPanel x:Name="ContentStackPanel" Background="Black">
<StackPanel Orientation="Horizontal" Width="455" Height="89" Margin="91,0,91,-30">
<TextBox x:Name="LeftNum" HorizontalAlignment="Left" VerticalAlignment="Center" Width="83" TextWrapping="Wrap"/>
<TextBlock x:Name="Symbol" Width="62" Text="+" TextWrapping="Wrap" FontSize="24" Foreground="#FFF80606" TextAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Left"/>
<TextBox x:Name="RightNum" HorizontalAlignment="Left" VerticalAlignment="Center" Width="78" TextWrapping="Wrap"/>
<TextBlock Width="64" Text="=" TextWrapping="Wrap" Foreground="#FFF20808" FontSize="21.333" TextAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Left"/>
<TextBox x:Name="Result" HorizontalAlignment="Left" VerticalAlignment="Center" Width="146" TextWrapping="Wrap"/>
</StackPanel>
<ListBox x:Name="operateList" ItemsSource="{Binding}" ItemsPanel="{StaticResource ItemsPanelTemplate1}" Height="99" Background="{x:Null}" BorderBrush="{x:Null}"/>
<Button x:Name="DynamicLoadButton" Height="40" Width="196" Content="DynamicLoadOperate"/>
</StackPanel>
</ScrollViewer>
</Grid>
</navigation:Page>
5.新建类 OperatorComponent.cs
代码
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel.Composition;
using System.ComponentModel;
namespace MefDemo
{
//用于更新界面的委托
public delegate void OperateHandler(IOperate Op);
/// <summary>
/// 运算器接口
/// </summary>
public interface IOperate
{
double Op(double left, double right);
string Symbol { set; get; }
string Label { get; set; }
}
/// <summary>
/// 加法运算器
/// </summary>
[Export(typeof(IOperate))]
public class AddButton : Button, IOperate
{
[Import("AddButtonContract",AllowRecomposition = true)]
public string Label { get { return this.Content.ToString(); } set { this.Content = value; } }
[Import("AddSybomContract", AllowRecomposition = true)]
public string Symbol { set; get; }
[Import("ClickHandler")]
public OperateHandler ClickAction { get; set; }
#region IOperate 成员
public double Op(double left, double right)
{
return left + right;
}
#endregion
public AddButton()
{
this.Click += (s, e) => ClickAction(this);
}
}
/// <summary>
/// 减法运算器
/// </summary>
[Export(typeof(IOperate))]
public class SubButton : Button, IOperate
{
[Import("SubButtonContract",AllowRecomposition=true)]
public string Label { get { return this.Content.ToString(); } set { this.Content = value; } }
[Import("SubSybomContract", AllowRecomposition = true)]
public string Symbol { set; get; }
[Import("ClickHandler")]
public OperateHandler ClickAction { get; set; }
#region IOperate 成员
public double Op(double left, double right)
{
return left - right;
}
#endregion
public SubButton()
{
this.Click += (s, e) => ClickAction(this);
}
}
/// <summary>
/// 为每个运算器的属性提供值
/// </summary>
public class ComponentAttributeProvider
{
[Export("AddButtonContract")]
public string AddLabel { get { return "Add"; } }
[Export("AddSybomContract")]
public string AddSymbol { get { return "+"; } }
[Export("SubButtonContract")]
public string SubLabel { get { return "Sub"; } }
[Export("SubSybomContract")]
public string SubSymbol { get { return "-"; } }
}
}
6.运行。 这样就构建了一个简单的运算器,其中的Export、Import就像一个个管道一样相互连接。
7.按照这样的设计,我们想要对其进行扩展,就必须把接口分离。新建一个Silverlight ClassLibrary Project(Named ContractLibrary),这个Library用来封装所有的扩展接口,定义Import/Export契约。
现在把原项目中的OperatorComponent.cs 类中的接口迁移到Library项目中,新建类文件OperateContract.cs 。
代码
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace ContractLibrary
{
public delegate void OperateHandler(IOperate Op);
/// <summary>
/// 运算器接口
/// </summary>
public interface IOperate
{
double Op(double left, double right);
string Symbol { set; get; }
string Label { get; set; }
}
}
编译通过后在Silverlight主工程(MefDemo)中添加对ContractLibrary项目的引用
8.再新建一个Silverlight ClassLibrary Project (Named StaticExtension),,这个工程就是我们用来静态扩展的DLL。
a). 在工程下新建我们的运算器类,AddButton.cs , SubButton.cs.(代码不变).
b). 但注意要添加对ContractLibrary项目的引用和MEF的框架集引用) 。
c). 添加全局属性配置类(ComponentConfiguration.cs)
d). 删除主工程中的ComponetOperater.cs.
e). 添加对StaticExtension的引用.
9.OK,这样我们就可以任意扩展运算器,添加更多的扩展运算了。
10.那么下面是添加一个新的乘法运算所要做的工作。
在StaticExtension中添加新类 Multiply.cs
代码
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel.Composition;
using ContractLibrary;
namespace StaticExtension
{
/// <summary>
/// 乘法运算器
/// </summary>
[Export(typeof(IOperate))]
public class MultiplyButton : Button, IOperate
{
[Import("MultiplyButtonContract", AllowRecomposition = true)]
public string Label { get { return this.Content.ToString(); } set { this.Content = value; } }
[Import("MultiplySybomContract", AllowRecomposition = true)]
public string Symbol { set; get; }
[Import("ClickHandler")]
public OperateHandler ClickAction { get; set; }
#region IOperate 成员
public double Op(double left, double right)
{
return left * right;
}
#endregion
public MultiplyButton()
{
this.Click += (s, e) => ClickAction(this);
}
}
}
11.上面的是静态加载,那么现在我们使用MEF实现动态扩展运算器。桌面程序的动态扩展是动态加载DLL,而对于Silverlight的Web程序则是动态加载Xap包了。
新建普通Silverlight Application(Named DynamicExtension).
去掉勾选Add a test page that references the application.
1). 删掉App和Main等不必要的文件,只留一个空的Silverlight项目,以减少Xap包的大小。
2). 添加ContractLibrary和MEF框架集的引用(可以将引用程序集属性CopyLocal设置为false,因为我们在主工程中已经添加了,可以重用)
3). 添加类Division.cs.
代码
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel.Composition;
using ContractLibrary;
namespace DynamicExtension
{
/// <summary>
/// 乘法运算器
/// </summary>
[Export(typeof(IOperate))]
public class DivisionButton : Button, IOperate
{
[Import("DivisionButtonContract", AllowRecomposition = true)]
public string Label { get { return this.Content.ToString(); } set { this.Content = value; } }
[Import("DivisionSybomContract", AllowRecomposition = true)]
public string Symbol { set; get; }
[Import("ClickHandler")]
public OperateHandler ClickAction { get; set; }
#region IOperate 成员
public double Op(double left, double right)
{
return left * right;
}
#endregion
public DivisionButton()
{
this.Click += (s, e) => ClickAction(this);
}
}
}
4). 添加配置类ComponentConfiguration.cs
代码
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel.Composition;
namespace DynamicExtension
{
/// <summary>
/// 为每个运算器的属性配置值
/// </summary>
public class ComponentConfiguration
{
[Export("DivisionButtonContract")]
public string AddLabel { get { return "Div"; } }
[Export("DivisionSybomContract")]
public string AddSymbol { get { return "/"; } }
}
}
5). 修改Home.cs ,为其注册下载包的相关事件和回调
1.
代码
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using System.Collections.ObjectModel;
using System.ComponentModel.Composition.Hosting;
using System.Reflection;
using System.ComponentModel.Composition;
using ContractLibrary;
namespace MefDemo
{
public partial class Home : Page
{
[ImportMany(typeof(IOperate),AllowRecomposition = true)]
public ObservableCollection<IOperate> Operates = new ObservableCollection<IOperate>();
[Export("ClickHandler")]
public OperateHandler ClickHandler { get { return OperateButton_Click; } }
private PackageCatalog Catalog;
/// <summary>
/// 用于界面控件响应运算后的一些更新工作
/// </summary>
/// <param name="Operate">运算器</param>
public void OperateButton_Click(IOperate Operate)
{
try
{
Symbol.Text = Operate.Symbol;
double left = double.Parse(LeftNum.Text);
double right = double.Parse(RightNum.Text);
this.Result.Text = Operate.Op(left, right).ToString();
}
catch (Exception e)
{
ChildWindow errorWin = new ErrorWindow(e);
errorWin.Show();
}
}
public Home()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(Home_Loaded);
//注册按钮事件
this.DynamicLoadButton.Click += (s, e) =>
{
//下载包
Package.DownloadPackageAsync(
new Uri("DynamicExtension.xap", UriKind.Relative),
(args, package) => Catalog.AddPackage(package)
);
//包被添加到PackageCatalog后会自动重新组合
//并对添加了AllowRecomposition = true属性的Import导入器重新输入数据
};
}
void Home_Loaded(object sender, RoutedEventArgs e)
{
//组合当前XAP包中所有部件(Parts)
Catalog = new PackageCatalog();
Catalog.AddPackage(Package.Current);
CompositionContainer container = new CompositionContainer(Catalog);
container.ComposeParts(this);
//组合后所有实现运算接口(IOperate)的运算器都将被自动填充到 Operates 集合。
//将运算器绑定到 ListBox 控件,用于呈现。
this.operateList.DataContext = Operates;
}
// Executes when the user navigates to this page.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
}
}
}
Ok,最终界面。
点击DynamicLoadOperate按钮后
程序中还有很多细节没有展开说明,理论性的介绍可以参考MSDN和CodePlex上的文档。
文章出处:飞诺网(www.firnow.com):http://dev.firnow.com/course/4_webprogram/asp.net/netjs/20100714/442928.html
MEF gives us the ability to do this. This post will cover the basics needed to build such a composite application split between different silerlight applications and download the referenced silverlight application only when needed.
Steps:
- Create a Silverlight 4 application
- Add references to the following assemblies:
System.ComponentModel.Composition.dll
System.ComponentModel.Composition.Initialization.dll
- Add a new Silverlight 4 application called ExternalSilverlightApplication to the solution that was created in step 1. Ensure the new application is hosted in the web application for the solution and choose to not create a test page for the new application.
- Delete the App.xaml and MainPage.xaml files – they aren’t needed.
- Add references to the following assemblies in the ExternalSilverlightApplication project:
System.ComponentModel.Composition.dll
System.ComponentModel.Composition.Initialization.dll
- Ensure the two references above have their Copy Local values set to false. As we will have these two assmblies in the original Silverlight application, we will have no need to include them in the built ExternalSilverlightApplication build.
- Add a new user control called LeftControl to the ExternalSilverlightApplication project.
- Replace the LayoutRoot Grid with the following xaml:
<Grid x:Name="LayoutRoot" Background="Beige" Margin="40" >
<Button Content="Left Content" Margin="30"></Button>
</Grid>
- Add the following statement to the top of the LeftControl.xaml.cs file
using System.ComponentModel.Composition;
- Add the following attribute to the LeftControl class
[Export(typeof(LeftControl))]
This attribute tells MEF that the type LeftControl will be exported – i.e. made available for other applications to import and compose into the application.
- Add a new user control called RightControl to the ExternalSilverlightApplication project.
- Replace the LayoutRoot Grid with the following xaml:
<Grid x:Name="LayoutRoot" Background="Green" Margin="40" >
<TextBlock Margin="40" Foreground="White" Text="Right Control" FontSize="16" VerticalAlignment="Center" HorizontalAlignment="Center" ></TextBlock>
</Grid>
- Add the following statement to the top of the RightControl.xaml.cs file
using System.ComponentModel.Composition;
- Add the following attribute to the RightControl class
[Export(typeof(RightControl))]
- In your original Silverlight project add a reference to the ExternalSilverlightApplication project.
- Change the reference to the ExternalSilverlightApplication project to have it’s Copy Local value = false. This will ensure that the referenced ExternalSilverlightApplication Silverlight application is not included in the original Silverlight application package when it it built. The ExternalSilverlightApplication Silverlight application therefore has to be downloaded on demand by the original Silverlight application for it’s controls to be used.
- In your original Silverlight project add the following xaml to the LayoutRoot Grid in MainPage.xaml:
<Grid.RowDefinitions>
<RowDefinition Height="65*" />
<RowDefinition Height="235*" />
</Grid.RowDefinitions>
<Button Name="LoaderButton" Content="Download External Controls" Click="Button_Click"></Button>
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center" >
<Border Name="LeftContent" Background="Red" BorderBrush="Gray" CornerRadius="20"></Border>
<Border Name="RightContent" Background="Red" BorderBrush="Gray" CornerRadius="20"></Border>
</StackPanel>
The borders will hold the controls that will be downlaoded, imported and composed via MEF when the button is clicked.
- Add the following statement to the top of the MainPage.xaml.cs file
using System.ComponentModel.Composition;
- Add the following properties to the MainPage class:
[Import(typeof(LeftControl))]
public LeftControl LeftUserControl { get; set; }
[Import(typeof(RightControl))]
public RightControl RightUserControl { get; set; }
This defines properties accepting LeftControl and RightControl types. The attrributes are used to tell MEF the discovered type that should be applied to the property when composition occurs.
- Add the following event handler for the button click to the MainPage.xaml.cs file:
private void Button_Click(object sender, RoutedEventArgs e)
{
DeploymentCatalog deploymentCatalog =
new DeploymentCatalog("ExternalSilverlightApplication.xap");
CompositionHost.Initialize(deploymentCatalog);
deploymentCatalog.DownloadCompleted += (s, i) =>
{
if (i.Error == null)
{
CompositionInitializer.SatisfyImports(this);
LeftContent.Child = LeftUserControl;
RightContent.Child = RightUserControl;
LoaderButton.IsEnabled = false;
}
};
deploymentCatalog.DownloadAsync();
}
This is where the magic happens! The deploymentCatalog object is pointed to the ExternalSilverlightApplication.xap file. It is then associated with the CompositionHost initialization. As the download will be asynchronous, an eventhandler is created for the DownloadCompleted event. The deploymentCatalog object is then told to start the asynchronous download.
The event handler that executes when the download is completed uses the CompositionInitializer.SatisfyImports() function to tell MEF to satisfy the Imports for the current class. It is at this point that the LeftUserControl and RightUserControl properties are initialized with composed objects from the downloaded ExternalSilverlightApplication.xap package.
- Run the application click the Download External Controls button and see the controls defined in the ExternalSilverlightApplication application loaded into the original Silverlight application.
Congratulations! You have implemented download on demand capabilities for composite applications using the MEF DeploymentCatalog class. You are now able to segment your applications into separate xap file for deployment.