Gestire le exception con MEF

Può capitare che un utente sviluppi un plugin per la vostra applicazione ma che in fase di creazione, cioè quando viene chiamato il costruttore, il plugin lanci un’eccezione.
Come possiamo gestire il tutto con MEF?

Riprendiamo in mano il Plugin Manager visto qualche post fa, in particolare andiamo a modificare l’interfaccia IPluginManager (modificando di conseguenza il PluginManager) ed aggiungiamo un evento PluginError.

event EventHandler<PluginErrorEventArgs> PluginError;

Ora abbiamo bisogno di un “qualcosa” che controlli TUTTI i plugin scoperti dai vari catalog controllando se i plugin, durante la creazione, crashano e che ci permetta di selezionare solo i plugin effettivamente funzionanti.

Qua ci viene in aiuto la classe ExportProvider! Un ExportProvider ha lo scopo di ritornare zero o più Exports in base alla richiesta effettuata e viene usato dal CompositionContainer per soddisfare gli Imports.

Riporto qualche link utile:
http://codebetter.com/blogs/glenn.block/archive/2008/12/25/using-exportprovider-to-customize-container-behavior-part-i.aspx
http://codebetter.com/blogs/glenn.block/archive/2009/05/14/customizing-container-behavior-part-2-of-n-defaults.aspx

 

Creiamo un PluginExportProvider, che deriva da ExportProvider e fa l’override del metodo GetExportsCore

internal class PluginExportProvider : ExportProvider
{
    internal event EventHandler<PluginErrorEventArgs> PluginError;
 
    private CatalogExportProvider _exportProvider;
 
    public PluginExportProvider(ComposablePartCatalog catalog) : base()
    {
        this._exportProvider = new CatalogExportProvider(catalog);
        this._exportProvider.ExportsChanged += (s, e) => OnExportsChanged(e);
        this._exportProvider.ExportsChanging += (s, e) => OnExportsChanging(e);
    }

Il costruttore prende in ingresso un catalog che poi verrà passato al CatalogExportProvider che fornisce accesso a tutti gli Export trovati all’interno del catalog specificato.
Dopo di che sottoscriviamo gli eventi ExportsChanged e ExportsChanging del CatalogExportProvider riscatenandoli a nostra volta…questo permette al nostro export provider di partecipare nel processo di recomposition.

public ExportProvider SourceProvider
{
    get
    {
        return _exportProvider.SourceProvider;
    }
 
    set
    {
        _exportProvider.SourceProvider = value;
    }
}

Come mi ha spiegato weshaggard dobbiamo settare il SourceProvider del CatalogExportProvider in quanto viene usato dall’ImportEngine (interno al CatalogExportProvider) per soddisfare gli exports, per questo creiamo una proprietà per settarlo.

Infine passiamo alla parte cruciale! Il metodo GetExportsCore

protected override IEnumerable<Export> GetExportsCore(ImportDefinition definition,

AtomicComposition atomicComposition)

{
   List<Export> exports = new List<Export>();
   
   foreach (Export export in _exportProvider.GetExports(definition, atomicComposition))
   {
       try
       {
           // provo ad accedere ala valore del plugin
           object value = export.Value;
 
           exports.Add(export);
       }
       catch (CompositionException ex)
       {
           OnPluginError(new PluginErrorEventArgs(ex));
       }
       catch (Exception ex)
       {
           OnPluginError(new PluginErrorEventArgs(ex));
       }
   }
 
   return exports;
}

L’ImportDefinition, richiesto in ingresso, definisce l’import richiesto da una Composable Part quindi ci sarà specificato il contratto richiesto, la cardinalità (Import, ImportMany) e via dicendo.
Cicliamo sugli oggetto Export restituiti dal metodo GetExports del CatalogExportProvider a cui passiamo i parametri definition e atomicComposition e proviamo ad accedere alla proprietà Value dell’Export. Accedendo a Value verrà creato il plugin e se viene lanciata un’eccezione nel costruttore questa verrà gestita dal try…catch scatenando l’evento PluginError. Nel caso la creazione vada a buon fine aggiungiamo l’export alla lista di Export che ritornerà il metodo.

Ritorniamo al PluginManager e vediamo la parte di creazione e “configurazione” del CompositionContainer
Creiamo il DirectoryWatcherCatalog come facevamo nel post precedente, creiamo il PluginExportProvider a cui passiamo il catalog e sottoscriviamo l’evento PluginError.
Infine settiamo il SourceProvider del PluginExportProvider (che espone quello del CatalogExportProvider) con l’istanza del CompositionContainr, ebbene si anche il CompositionContainer deriva da ExportProvider, e chiamiamo il metodo ComposeParts.

public void Init()
{
    try
    {
        if (_isInitialized)
        {
            return;
        }
 
        _isInitialized = true;
        _directoryCatalog = new DirectoryWatcherCatalog(this._pluginsDirectory);
        _pluginExportProvider = new PluginExportProvider(_directoryCatalog);

_pluginExportProvider.PluginError +=

new EventHandler<PluginErrorEventArgs>(_pluginExportProvider_PluginError);

        _compositionContainer = new CompositionContainer(_pluginExportProvider);
        _pluginExportProvider.SourceProvider = _compositionContainer;
        _compositionContainer.ComposeParts(this);
 
    }
    catch (CompositionException ex)
    {
        throw ex;
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

Ora creiamo un plugin, lanciamo un’exception nel costruttore

[ExportPlugin(PluginType = typeof(IFeature))]
public class EndingValueFeature : IFeature, IPlugin
{
    public EndingValueFeature()
    {
        throw new NotImplementedException();
 
        this.Name = "Ending Value Feature";
        ...

e questo non verrà visto dal nostro PluginManager come plugin disponibile :)

image

[Download Code]