若不使用 MEF 托管擴充,動態下載與解析 .xap 套件需自行使用 WebClient。MEF 底層同樣採用 WebClient,再透過 AggregateCatalog 動態組合部件(原始碼可參考 DeploymentCatalog.cs)。
上一篇已介紹目錄服務介面封裝,此處專注於動態載入 .xap 的 AddXap 方法:
public void AddXap(string relativeUri, Action<AsyncCompletedEventArgs> completedAction)
{
DeploymentCatalog catalog;
if (!_catalogs.TryGetValue(relativeUri, out catalog))
{
catalog = new DeploymentCatalog(relativeUri);
catalog.DownloadCompleted += (s, e) => completedAction?.Invoke(e) ?? DownloadCompleted(e);
catalog.DownloadAsync();
_catalogs[relativeUri] = catalog;
_aggregateCatalog.Catalogs.Add(catalog);
}
}
使用時僅需導入介面並呼叫:
[Import]
public IDeploymentService Service { get; set; }
private void button1_Click(object sender, RoutedEventArgs e)
{
this.Service.AddXap("MEFTraining.MefCatalogs.Parts.xap", null);
}
當新部件成功組合進容器,實作此介面的類別會自動收到 OnImportsSatisfied() 回呼,適合用於初始化後續邏輯。
當只需目錄中特定部件時,可使用 ComposablePartCatalog 與 INotifyComposablePartCatalogChanged 建立篩選目錄:
public class FilteredCatalog : ComposablePartCatalog, INotifyComposablePartCatalogChanged
{
private readonly ComposablePartCatalog _inner;
private readonly INotifyComposablePartCatalogChanged _innerNotifyChange;
private readonly IQueryable<ComposablePartDefinition> _partsQuery;
public FilteredCatalog(ComposablePartCatalog inner,
Expression<Func<ComposablePartDefinition, bool>> expression)
{
_inner = inner;
_innerNotifyChange = inner as INotifyComposablePartCatalogChanged;
_partsQuery = inner.Parts.Where(expression);
}
// Changed / Changing 事件轉發給內部目錄
public override IQueryable<ComposablePartDefinition> Parts => _partsQuery;
}
使用 [PartMetadata] 標記部件,再以 Lambda 運算式篩選:
[PartMetadata("UC", "AA")]
[Export(typeof(UserControl))]
public partial class AA : UserControl { ... }
[PartMetadata("UC", "BB")]
[Export(typeof(UserControl))]
public partial class BB : UserControl { ... }
[PartMetadata("UC", "CC")]
[Export(typeof(UserControl))]
public partial class CC : UserControl { ... }
篩選程式碼:
var catalog = new AssemblyCatalog(typeof(MainPage).Assembly);
var parent = new CompositionContainer(catalog);
var filteredCat = new FilteredCatalog(catalog,
def => def.Metadata.ContainsKey("UC") &&
def.Metadata["UC"].ToString() == "CC");
var perRequest = new CompositionContainer(filteredCat, parent);
var control = perRequest.GetExportedValue<UserControl>(); // 取得 CC
此機制讓大型模組化應用能依中繼資料動態決定載入哪些部件,極大提升彈性。