📜 ⬆️ ⬇️

Managed Extensibility Framework (MEF) as a testing ground

Questions to consider

Question 1: MEF and INotifyPropertyChanged: how to notify an exported object about changes?
Question 2: Notification of a change in a property imported through MEF as a collection (ImportMany).
Question 3: Download XAP files on demand through MEF.
Question 4: Modal window in MVVM pattern.

Problem Statement (Wishlist)

Hotel number 1 : I want to make sure that one module (Shell) can “find” modules using MEF at the compilation stage, as well as be able to load third-party modules on demand (for example, when you click the Load button).

Hotel number 2 : Also, I would like the changes to be immediately transferred to another window when I start editing my object in the window of one editor.

Hotel number 3 : I want my application to run several variants of my entity editor (my MyObject object). Let them be two for the time being, with one editor being built into the main module of the application and available immediately when the application starts, and the second even being in a separate XAP file and available only when it is loaded into the project on demand (in foreign sounds like OnDemand ).
')
Hotel 4 : I want editors to open in a modal window.

Assemblies used in implementation

At once I will make a reservation that for this article and in particular in this solution I use my own library (class assemblies). One of them is Calabonga.Silverlight.Framework.dll, which, by the way, has updated its version to the number 1.0.5. It added interfaces for the implementation of a modal window (ModalDialog) with a substitute context. (An example of use is again in the project.) Thus, a modular window can be used in the MVVM pattern and substitute any other View class into this window.

The second build, which I wrote in order not to be constantly muzzled with loading modules, was called XapService. This is the DeploymentCatalogService implementation. The build is in a project that you can download and which is an example of use.

The implementation of "Wishlist"

As you understand, the solution is ready in the form of a solution (solution), in which several projects. This time, it took 6 projects in one solution. (Sln):

image

rice 1. "The structure of the solution and projects that are included in it"

MefTest is the main project, and in the context of this article, this is the shell for the modules (Shell). This project contains one of the modules (let's call it the “staffing table”), which was mentioned in Wishlist # 1. As well as one editors (see Wishlist number 3). This is how the window of the local module looks like:

image

rice 2. "View of the internal module"

MefTest.Web is an ASP.NET application where my experiment is hosted (started).

FormView is an external module (let's call it, for example, “bookkeeping”). This is one of the modules mentioned in Wishlist # 1.

image

rice 3. "View external module"

ContractLibrary is a library of contracts and interfaces. Namely in this assembly is my experimental class MyObject. And in order for all modules to know about the presence of my class, this assembly will be used in all modules.

AdvancedEditor is one of the implementations of the editor, which was mentioned in Wishlist # 3.

I will set up experiments with a simple class MyObject, which implements INotifyPropertyChanged for the Wishlist number 2 to work. Here’s how it looks in code:
public class MyObject : INotifyPropertyChanged
{
private string cityType;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged("Name");
}
}
private string name;
public string CityType
{
get { return cityType; }
set
{
cityType = value;
OnPropertyChanged("CityType");
}
}

#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}


In MainPage (the main shell of the Shell) we implement the IDeploymentCatalogService xap-file loader which helps to load xap-files on demand:

public partial class MainPage : UserControl, IPartImportsSatisfiedNotification
{
[Import]
public IDeploymentCatalogService CatalogService { get; set; }

public MainPage()
{
InitializeComponent();
CompositionInitializer.SatisfyImports(this);
}

[ImportMany(typeof(UserControl), AllowRecomposition=true)]
public UserControl[] Views { get; set; }

public void OnImportsSatisfied()
{
LayoutRoot.Children.Clear();
foreach (UserControl item in Views)
{
LayoutRoot.Children.Add(item);
}
}
}


Modal Dialog Box (ModalDialog)

To display different editors on the screen, I didn’t think anything smarter how to display these modules in a modal window (sorry, I’m not strong in UI interface design). The essence of the modal dialogue structure is that the “framework” itself is modal. The internal context of this framework can be anything. So, if the staffing module (FormLocal is a local internal module in the main project, which we called Shell) uses code behind, then the accounting module (see FormView) is already done according to the MVVM rules (model - view - viewmodel ) pattern. Go into details on the implementation of the modal window, you can see everything in the project itself, which can be downloaded (link at the end of the article).

In brief about the modality of the dialog box, you can say the following - it works! All the functionality is “sewn up” into the library from which Export's stick out. (If it will be interesting, I will later tell you how ModalDialog works in another article, because this article is not about dialogues). For example, it looks like this:

The FormExternalViewModel class has the ModalDialog property, which is the interface for the appearance of the window.

[Import]
public IModalDialog ModalDialog
{
get;
set;
}


In order not to reinvent the wheel, I decided to take the ChildWindow control (calling it ExtendedChildWindow) from the Silverlight library of controls and give it the necessary features, in particular, to release IModalDialog from its library.

[Export(typeof(IModalDialog))]
public class ExtendedChildWindow : ChildWindow, IModalDialog
{
public void ShowDialog()
{
this.Width = 450;
this.Height = 300;
this.Show();
}
}


There is also a FormExternalViewModel class and a property:

[ImportMany(AllowRecomposition = true)]
public IModalView[] Editors { get; set; }


The ImodalView interface is the implementation of the context for a modal window. That is, any visual control (View or any other crap) that implements this interface can be displayed in a modal window. It so happened that I have several in this project, more precisely two - an internal editor and an external editor. Well, and lastly, about the so-called "discoverer" of modal windows:

[Import]
public IModalDialogWorker ModalDialogWorker {get; set; }

This property in the FormExternalViewModel class imports from the Calabonga.Silverlight.Framework.dll library the “launcher” modal dialog, which is called from the OpenDialogCommand command:

this.ModalDialogWorker.ShowDialog(this.ModalDialog, view, o, dialog =>
{
if (this.ModalDialog.DialogResult.HasValue &&
this.ModalDialog.DialogResult.Value)
{
}
});

"" ModalDialogWorker, Calabonga.Silverlight.Framework:

namespace Calabonga.Silverlight.Framework
{
[Export(typeof(IModalDialogWorker))]
public class ModalDialogWorker : IModalDialogWorker
{
public void ShowDialog(IModalDialog modalDialog, IModalView modalView, T dataContext, Action onClosed)
{
if (modalDialog == null)
throw new ArgumentNullException("modalDialog", " null");
if (modalView == null)
throw new ArgumentNullException("modalView", " null");

EventHandler onDialogClosedHandler = null;
EventHandler onViewClosedHandler = null;

if (onClosed != null)
{
onDialogClosedHandler = (s, a) =>
{
modalDialog.Closed -= onDialogClosedHandler;
onClosed(dataContext);
};

onViewClosedHandler = (s, a) =>
{
modalDialog.Closed -= onDialogClosedHandler;
modalView.Closed -= onViewClosedHandler;
modalDialog.DialogResult = a.DialogResult;
onClosed(dataContext);
};

modalDialog.Closed += onDialogClosedHandler;
modalView.Closed += onViewClosedHandler;
}

modalDialog.Content = modalView;
modalView.DataContext = dataContext;
modalDialog.ShowDialog();
}
}
}

, "" .

MEF
(shell) "" ( ), Managed Extensibility Framework (MEF). MEF , :

using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;

, , . XAP-. , , (ExportAttribute), , FormExternal FormView:

[Export(typeof(UserControl))]
public partial class FormExternal : UserControl
{
public FormExternal()
{
InitializeComponent();
}
}


, , , , . (, , ...). () . View:

[ImportMany(typeof(UserControl), AllowRecomposition=true)]
public UserControl[] Views { get; set; }


:

AllowRecomposition=true

, MEF-. , . , . , , :

public MainPage()
{
InitializeComponent();
CompositionInitializer.SatisfyImports(this);
}


MEF- IPartImportsSatisfiedNotification, :

public void OnImportsSatisfied()
{
LayoutRoot.Children.Clear();
foreach (UserControl item in Views)
{
LayoutRoot.Children.Add(item);
}
}


. :

image

, "", CheckBox . :

private IModalView[] editors;
[ImportMany(AllowRecomposition = true)]
public IModalView[] Editors
{
get
{ return editors; }
set
{
editors = value;
checkEditor.IsEnabled = (this.Editors != null) && (this.Editors.Count() > 1);
OnPropertyChanged("Editors");
}
}


Code-Bihind ViewModel (MVVM) ( ), . , "" ... !!!

image

. , " " , , "" (. â„–3 AdvancedEditor).

private void Button_Click(object sender, RoutedEventArgs e)
{
CatalogService.AddXap("FormView.xap");
CatalogService.AddXap("AdvancedEditor.xap");
(sender as Button).IsEnabled = false;
}


, , ( ) , INotifyPropertyChanged.

: Memento .

, , CheckBox . , ( ) .

image

- . ! ?! ! image

Visual Studio 2010
this.ModalDialogWorker.ShowDialog(this.ModalDialog, view, o, dialog =>
{
if (this.ModalDialog.DialogResult.HasValue &&
this.ModalDialog.DialogResult.Value)
{
}
});

"" ModalDialogWorker, Calabonga.Silverlight.Framework:

namespace Calabonga.Silverlight.Framework
{
[Export(typeof(IModalDialogWorker))]
public class ModalDialogWorker : IModalDialogWorker
{
public void ShowDialog(IModalDialog modalDialog, IModalView modalView, T dataContext, Action onClosed)
{
if (modalDialog == null)
throw new ArgumentNullException("modalDialog", " null");
if (modalView == null)
throw new ArgumentNullException("modalView", " null");

EventHandler onDialogClosedHandler = null;
EventHandler onViewClosedHandler = null;

if (onClosed != null)
{
onDialogClosedHandler = (s, a) =>
{
modalDialog.Closed -= onDialogClosedHandler;
onClosed(dataContext);
};

onViewClosedHandler = (s, a) =>
{
modalDialog.Closed -= onDialogClosedHandler;
modalView.Closed -= onViewClosedHandler;
modalDialog.DialogResult = a.DialogResult;
onClosed(dataContext);
};

modalDialog.Closed += onDialogClosedHandler;
modalView.Closed += onViewClosedHandler;
}

modalDialog.Content = modalView;
modalView.DataContext = dataContext;
modalDialog.ShowDialog();
}
}
}

, "" .

MEF
(shell) "" ( ), Managed Extensibility Framework (MEF). MEF , :

using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;

, , . XAP-. , , (ExportAttribute), , FormExternal FormView:

[Export(typeof(UserControl))]
public partial class FormExternal : UserControl
{
public FormExternal()
{
InitializeComponent();
}
}


, , , , . (, , ...). () . View:

[ImportMany(typeof(UserControl), AllowRecomposition=true)]
public UserControl[] Views { get; set; }


:

AllowRecomposition=true

, MEF-. , . , . , , :

public MainPage()
{
InitializeComponent();
CompositionInitializer.SatisfyImports(this);
}


MEF- IPartImportsSatisfiedNotification, :

public void OnImportsSatisfied()
{
LayoutRoot.Children.Clear();
foreach (UserControl item in Views)
{
LayoutRoot.Children.Add(item);
}
}


. :

image

, "", CheckBox . :

private IModalView[] editors;
[ImportMany(AllowRecomposition = true)]
public IModalView[] Editors
{
get
{ return editors; }
set
{
editors = value;
checkEditor.IsEnabled = (this.Editors != null) && (this.Editors.Count() > 1);
OnPropertyChanged("Editors");
}
}


Code-Bihind ViewModel (MVVM) ( ), . , "" ... !!!

image

. , " " , , "" (. â„–3 AdvancedEditor).

private void Button_Click(object sender, RoutedEventArgs e)
{
CatalogService.AddXap("FormView.xap");
CatalogService.AddXap("AdvancedEditor.xap");
(sender as Button).IsEnabled = false;
}


, , ( ) , INotifyPropertyChanged.

: Memento .

, , CheckBox . , ( ) .

image

- . ! ?! ! image

Visual Studio 2010
this.ModalDialogWorker.ShowDialog(this.ModalDialog, view, o, dialog =>
{
if (this.ModalDialog.DialogResult.HasValue &&
this.ModalDialog.DialogResult.Value)
{
}
});

"" ModalDialogWorker, Calabonga.Silverlight.Framework:

namespace Calabonga.Silverlight.Framework
{
[Export(typeof(IModalDialogWorker))]
public class ModalDialogWorker : IModalDialogWorker
{
public void ShowDialog(IModalDialog modalDialog, IModalView modalView, T dataContext, Action onClosed)
{
if (modalDialog == null)
throw new ArgumentNullException("modalDialog", " null");
if (modalView == null)
throw new ArgumentNullException("modalView", " null");

EventHandler onDialogClosedHandler = null;
EventHandler onViewClosedHandler = null;

if (onClosed != null)
{
onDialogClosedHandler = (s, a) =>
{
modalDialog.Closed -= onDialogClosedHandler;
onClosed(dataContext);
};

onViewClosedHandler = (s, a) =>
{
modalDialog.Closed -= onDialogClosedHandler;
modalView.Closed -= onViewClosedHandler;
modalDialog.DialogResult = a.DialogResult;
onClosed(dataContext);
};

modalDialog.Closed += onDialogClosedHandler;
modalView.Closed += onViewClosedHandler;
}

modalDialog.Content = modalView;
modalView.DataContext = dataContext;
modalDialog.ShowDialog();
}
}
}

, "" .

MEF
(shell) "" ( ), Managed Extensibility Framework (MEF). MEF , :

using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;


, , . XAP-. , , (ExportAttribute), , FormExternal FormView:

[Export(typeof(UserControl))]
public partial class FormExternal : UserControl
{
public FormExternal()
{
InitializeComponent();
}
}


, , , , . (, , ...). () . View:

[ImportMany(typeof(UserControl), AllowRecomposition=true)]
public UserControl[] Views { get; set; }


:

AllowRecomposition=true

, MEF-. , . , . , , :

public MainPage()
{
InitializeComponent();
CompositionInitializer.SatisfyImports(this);
}


MEF- IPartImportsSatisfiedNotification, :

public void OnImportsSatisfied()
{
LayoutRoot.Children.Clear();
foreach (UserControl item in Views)
{
LayoutRoot.Children.Add(item);
}
}


. :

image

, "", CheckBox . :

private IModalView[] editors;
[ImportMany(AllowRecomposition = true)]
public IModalView[] Editors
{
get
{ return editors; }
set
{
editors = value;
checkEditor.IsEnabled = (this.Editors != null) && (this.Editors.Count() > 1);
OnPropertyChanged("Editors");
}
}


Code-Bihind ViewModel (MVVM) ( ), . , "" ... !!!

image

. , " " , , "" (. â„–3 AdvancedEditor).

private void Button_Click(object sender, RoutedEventArgs e)
{
CatalogService.AddXap("FormView.xap");
CatalogService.AddXap("AdvancedEditor.xap");
(sender as Button).IsEnabled = false;
}


, , ( ) , INotifyPropertyChanged.

: Memento .

, , CheckBox . , ( ) .

image

- . ! ?! ! image

Visual Studio 2010

Source: https://habr.com/ru/post/107919/


All Articles