MEF: Creating a ViewModel in my UserControls - c#

I am currently converting my WPF/MVVM application from Ninject to MEF to take advantage of some plugin architecture. There is no Prism or Unity, nor do I want to go down that path. I'm using VS2015 and .Net 4.6.
I was using the technique that was popular with MVVM Light where you instantiate the ViewModel inside of the XAML with Ninject.
public static ImageViewModel ImageVM => Kernel.Get<ImageViewModel>();
But now that I'm moving to MEF, I would like to see if there are some alternatives. Most of the currently answered posts on Stack Exchange are fairly old and I'm hoping there are some new alternatives available now that .NET 4.5 is available.
tl;dr
I have a Window that contains 10 UserControls. Each user control has a new instance of a ViewModel attached to it. Since the window is creating the user controls in xaml, how can I get a unique instance of my ViewModel into each control?
public partial class FingerprintControl{
public FingerprintControl() {
InitializeComponent();
}
[Import]
public FingerprintControlViewModel ViewModel
{
get { return DataContext as FingerprintControlViewModel; }
set { DataContext = value; }
}
One suggestion that I saw said to add
CompositionInitializer.SatisfyImports(this);
after the InitializeComponent(). But that is a Silverlight only class.
I looked at https://www.nuget.org/packages/Microsoft.Composition/ but the documentation for MEF 2 is just incredibly non-existent on the site.
I also saw that ExportFactory was added to MEF 2, but not sure if that would help either.
I did find in MEF 2 the static method CompositionContextExtensions.SatisfyImports, but I don't know what to do with it. The documentation only says, "Satisfies the imports of the specified object from the specified context." (Not real useful...)

We use a class wrapper for Mef with static methods for all our apps:
public class Mef
{
private static CompositionContainer container = null;
public static CompositionContainer Container { get { return container; } }
private static AggregateCatalog catalog;
public static void Initialize()
{
catalog = new AggregateCatalog();
catalog.Catalogs.Add(new DirectoryCatalog(path: ".", searchPattern: "*.exe"));
catalog.Catalogs.Add(new DirectoryCatalog(path: ".", searchPattern: "*.dll"));
container = new CompositionContainer(catalog);
StartWatch();
}
private static void StartWatch()
{
var watcher = new FileSystemWatcher() { Path = ".", NotifyFilter = NotifyFilters.LastWrite };
watcher.Changed += (s, e) =>
{
string lName = e.Name.ToLower();
if (lName.EndsWith(".dll") || lName.EndsWith(".exe"))
Refresh();
};
watcher.EnableRaisingEvents = true;
}
public static void Refresh()
{
foreach (DirectoryCatalog dCatalog in catalog.Catalogs)
dCatalog.Refresh();
}
}
(Note: We use the above to dynamically load plugins on-demand into our app as required by the user. You may want to use a different catalog system - several to choose from)
Then we initialize the class early on in the app life-cycle, usually in the App.Xaml code-behind constructor:
public App()
{
this.InitializeComponent();
Mef.Initialize();
}
and when I have a base-level mef-import, in the class/code-behind constructor call:
Mef.Container.SatisfyImports(this);
Hope this helps.

Related

Retrieving Autofac container to resolve services

In a C# WindowForms application I start an OWIN WebApp that creates a singleton instance of my other class Erp:
public partial class Engine : Form
{
const string url = "http://*:8080"; //49396
private IDisposable webApp;
public Engine()
{
InitializeComponent();
StartServer();
}
private void StartServer()
{
webApp = WebApp.Start<Startup>(url);
Debug.WriteLine("Server started at " + url);
}
private void btnDoSomething(object sender, System.EventArgs e)
{
// needs to call a method in erp
}
}
class Startup
{
public void Configuration(IAppBuilder app)
{
Trace.Listeners.Remove("HostingTraceListener");
app.UseCors(CorsOptions.AllowAll);
var builder = new ContainerBuilder();
var config = new HubConfiguration();
builder.RegisterHubs(Assembly.GetExecutingAssembly()).PropertiesAutowired();
var erp = new Erp();
builder.RegisterInstance<Erp>(erp).SingleInstance();
var container = builder.Build();
config.Resolver = new AutofacDependencyResolver(container);
app.UseAutofacMiddleware(container);
app.MapSignalR(config);
}
}
After the creation of the WebApp I want to retrieve in other part of my code (i.e. in the button's event handler above) the singleton erp instance created.
As far as I understand I need to use the resolve function:
var erp = container.Resolve<Erp>();
but it's not clear to me how to retrieve the container outside the Configuration function.
I wouldn't overthink it. Set a static variable somewhere and just hold onto it.
public static class ContainerProvider
{
public static IContainer Container { get; set; }
}
and in the block in Startup:
var container = builder.Build();
ContainerProvider.Container = container;
config.Resolver = new AutofacDependencyResolver(container);
Now you can get the container wherever you need it.
EDIT: I have just realised the accepted answer is by a co-owner of the Autofac project, which has left me confused as it seems to go against what is in the documentation. I am going to leave the answer for now in hope of clarification.
Just wanted to provide my own answer is because whilst the accepted answer will work; it is generally considered bad practice.
From the Best Practices and Recommendations section in Autofac's documentation:
Use Relationship Types, Not Service Locators
Giving components access to the container, storing it in a public static property, or making functions like Resolve() available on a global “IoC” class defeats the purpose of using dependency injection. Such designs have more in common with the Service Locator pattern.
If components have a dependency on the container (or on a lifetime scope), look at how they’re using the container to retrieve services, and add those services to the component’s (dependency injected) constructor arguments instead.
Use relationship types for components that need to instantiate other components or interact with the container in more advanced ways.
You haven't given a specific scenario of how you want to use it in your code, so I can't provide you with an exact solution, but is there any reason you need to resolve the instance yourself? Could you not just deliver the Erp instance via dependency injection?
If the answer is yes, the following code I adapted from the Windows Forms Integration Guide page in Autofac's documentation demonstrates how this would be done:
public partial class Form1 : Form {
private readonly Erp _erp;
public Form1(Erp erp) {
this._erp = erp;
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e) {
//do stuff with erp here
}
}
Then Autofac, assuming the registration has been setup correctly, should inject the instance into that class.
I hope this helps!

Adding Autofac to WPF MVVM application

I can't seem to find an solution to this problem. I've seen several questions about this, but none really give me a solution. I am totally new to Autofac and haven't really done much WPF + MVVM, but know the basics.
I have a WPF application (using ModernUI for WPF) which I'm trying to add Autofac to, and I am having a hard time figuring out how to resolve my services within all the views, since they have no access to my container. I have a main view, which is my entry point, where I set up my container:
public partial class MainWindow : ModernWindow
{
IContainer AppContainer;
public MainWindow()
{
SetUpContainer();
this.DataContext = new MainWindowViewModel();
InitializeComponent();
Application.Current.MainWindow = this;
}
private void SetUpContainer()
{
var builder = new ContainerBuilder();
BuildupContainer(builder);
var container = builder.Build();
AppContainer = container;
}
private void BuildupContainer(ContainerBuilder builder)
{
builder.RegisterType<Logger>().As<ILogger>();
...
}
}
The problem I'm having is figuring out how I can resolve my logger and other services within my other views, where I inject all my dependencies through the ViewModel constructor, like so:
public partial class ItemsView : UserControl
{
private ItemsViewModel _vm;
public ItemsView()
{
InitializeComponent();
IFileHashHelper fileHashHelper = new MD5FileHashHelper();
ILibraryLoader libraryLoader = new LibraryLoader(fileHashHelper);
ILogger logger = new Logger();
_vm = new ItemsViewModel(libraryLoader, logger);
this.DataContext = _vm;
}
}
Some views have a ridiculous amount of injected parameters, and this is where I want Autofac to come in and help me clean things up.
I was thinking of passing the container to the ViewModel and storing it as a property on my ViewModelBase class, but I've read that this would be an anti-pattern, and even then I don't know if that would automatically resolve my objects within the other ViewModels.
I managed to put together a simple Console Application using Autofac
class Program
{
static void Main(string[] args)
{
var builder = new ContainerBuilder();
builder.RegisterType<Cleaner>().As<ICleaner>();
builder.RegisterType<Repository>().AsImplementedInterfaces().InstancePerLifetimeScope();
var container = builder.Build();
using (var scope = container.BeginLifetimeScope())
{
ICleaner cleaner = container.Resolve<ICleaner>();
cleaner.Update(stream);
}
}
}
but that was simple since it has a single entry point.
I'd like some ideas on how to add Autofac to my WPF app. I'm sure that I'm doing something wrong. Your help is appreciated.
Expanding on my comment above:
I use Autofac with all my WPF MVVM applications, I believe it to be one of the better DI frameworks - this is my opinion, but I think it is valid.
Also for me PRISM should be avoided 99% of the time, it's a 'solution looking for a problem' and since most people don't build dynamically composable runtime solutions in WPF it is not needed, i'm sure people would\will disagree.
Like any architectural patterns there is a setup\configuration phase to the application life-cycle, put simply in your case before the first View (window) is shown there will be a whole of setup done for Dependency Injection, Logging, Exception Handling, Dispatcher thread management, Themes etc.
I have several examples of using Autofac with WPF\MVVM, a couple are listed below, I would say look at the Simple.Wpf.Exceptions example:
https://github.com/oriches/Simple.Wpf.Exceptions
https://github.com/oriches/Simple.Wpf.DataGrid
https://github.com/oriches/Simple.MahApps.Template
You can use a similar technique as your console application:
class Program
{
[STAThread]
static void Main(string[] args)
{
var builder = new ContainerBuilder();
builder.RegisterType<Cleaner>().As<ICleaner>();
builder.RegisterType<Repository>().AsImplementedInterfaces().InstancePerLifetimeScope();
// Add the MainWindowclass and later resolve
build.RegisterType<MainWindow>().AsSelf();
var container = builder.Build();
using (var scope = container.BeginLifetimeScope())
{
var main = scope.Resolve<MainWindow>();
main.ShowDialog();
}
}
}
Be sure to mark Main with [STAThread]. Then in the project's properties, under the Application tab, set the Startup object to the Program class.
However, I am not certain of the implications of not running App.Run() and of running MainWindow.ShowDialog() instead.
To do the same using App.Run(), do the following:
1) delete StartupUri="MainWindow.xaml" from App.xaml
2) Add the following to App.xaml.cs
protected override void OnStartup(StartupEventArgs e)
{
var builder = new ContainerBuilder();
builder.RegisterType<Cleaner>().As<ICleaner>();
builder.RegisterType<Repository>().AsImplementedInterfaces().InstancePerLifetimeScope();
// Add the MainWindowclass and later resolve
build.RegisterType<MainWindow>().AsSelf();
var container = builder.Build();
using (var scope = container.BeginLifetimeScope())
{
var window = scope.Resolve<MainWindow>();
window.Show();
}
}
WPF doesn't have a natural composition root or easy DI integration. Prism is a pretty common set of libraries specifically intended to bridge that for you.
(That's not Autofac specific - it's general guidance for adding DI to WPF apps.)

MEF isn't loading my plugin

I've implemented a very small plugin system in C# with MEF. But my plugins won't be loaded. In the Aggregate-Catalog I can see my plugin listed. But, after I'll compose these parts, there isn't my plugin in the plugin list, what I'm doing wrong?
Here's a snippet of my code:
Plugin-Loader:
[ImportMany(typeof(IFetchService))]
private IFetchService[] _pluginList;
private AggregateCatalog _pluginCatalog;
private const string pluginPathKey = "PluginPath";
...
public PluginManager(ApplicationContext context)
{
var dirCatalog = new DirectoryCatalog(ConfigurationManager.AppSettings[pluginPathKey]);
//Here's my plugin listed...
_pluginCatalog = new AggregateCatalog(dirCatalog);
var compositionContainer = new CompositionContainer(_pluginCatalog);
compositionContainer.ComposeParts(this);
}
...
And here, the plugin itself:
[Export(typeof(IFetchService))]
public class MySamplePlugin : IFetchService
{
public MySamplePlugin()
{
Console.WriteLine("Plugin entered");
}
...
}
You're doing this wrong. The ImportMany attribute of the _pluginList field doesn't make any sense, since the the plugin manager instance will be created by you, not by the DI container.
You have to create another class that will import all your plugins.
[Export]
class SomeClass
{
readonly IFetchService[] pluginList;
[ImportingConstructor]
public SomeClass([ImportMany(typeof(IFetchService))]IFetchService[] pluginList)
{
this.pluginList = pluginList;
}
}
Now, you can let the DI container compose an instance of this SomeClass for you. You will see that its pluginList field contains your plugin reference.

Catel with Ninject

Our company is using Ninject for DI. I have to create a WPF App with MVVM and want to use Catel.
Because our services which have the DB DataContext are injected with Ninject, I don't know where to start.
I've started with a prepared skeleton project.
This is what App.xaml.cs contains:
public partial class App : Application
{
public IKernel Container;
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
ConfigureContainer();
ComposeObjects();
Current.MainWindow.Show();
}
private void ConfigureContainer()
{
var modules = new INinjectModule[]
{
new ServiceModule()
};
Container = new StandardKernel(modules);
}
private void ComposeObjects()
{
Current.MainWindow = Container.Get<MainWindow>();
Current.MainWindow.Title = "DI with Ninject";
}
}
The ServiceModule is inherited from NinjectModule.
With that code I can use this constructor of my MainWindow:
public MainWindow(IAuthenticationService authenticationService)
{
InitializeComponent();
ViewModel = new MainWindowViewModel(authenticationService);
DataContext = ViewModel;
}
The IAuthenticationService is injected via App.xaml.cs and Ninject. In my opinion this solution is hard to maintain, because if I need a new service, I have to add it to the constructor of my MainWindow.
Now I need the same thing to work with Catel, but I haven't found something in the documentation.
EDIT:
I've found on the documentation that I can register an external IoC container.
How do I create my own component (doc: Replacing the default components) which works with the Ninject's standard kernel?
Also is this a good approach of DI or are there better solutions?
Please see the recommended approach on how to replace the default IoC components:
https://catelproject.atlassian.net/wiki/display/CTL/Replacing+the+default+components
To create your own component, let the Ninject kernel implement the right interface (for example, IDependencyResolver or IServiceLocator) and all should be set.

Prism and IEventAggregator: Trouble at loading Modules

I'm actually working on a project using WPF, MEF and Prism. When I start the application, I need to export a module from the container to open a configuration window before the mainwindow is shown. The code therefore is like this:
protected override void OnStartup(StartupEventArgs e)
{ base.OnStartup(e);
Bootstrapper bootstrapper = new Bootstrapper();
bootstrapper.Run();
var window = bootstrapper.statcontainer.GetExport<Configshell>();
var configview = bootstrapper.statcontainer.GetExport<Module.Module1.View.ConfigView>();
window.Value.Show();
Keyboard.Focus(configview.Value.ok_button); }
Where bootstrapper.statcontainer is a public CompositeContainer (allocated with the "Maincontainer" of the MEFBootstrapper in CreateContainer-Method). I use it to export modules at startup.
Now at GetExport() I get the following first chance exception thrown:
GetExportedValue cannot be called before prerequisite import 'MyApp.Module.Module2.ViewModels.Module2_Functions..ctor (Parameter="C_Aggregator", ContractName="Microsoft.Practices.Prism.PubSubEvents.IEventAggregator")' has been set.
And this is how the ctor there looks like:
[Import]
public IEventAggregator Configaggregator;
[ImportingConstructor]
public Module2_Functions(IEventAggregator C_Aggregator)
{
this.Configaggregator = C_Aggregator;
Configaggregator.GetEvent<FromConfigWindow>();
FromConfigWindow.Instance.Subscribe(receiveStatusFromConfigWindow);
Configaggregator.GetEvent<ToConfigWindow>();
}
I'am using the EventAggregator to publish the configuration and have the same ctor in another module. The confusing thing is that it worked that way until I added another completely independent import to that ViewModel. Here is how the ctor of the ConfigwindowViewModel looks like:
[ImportingConstructor]
public ConfigVM(IEventAggregator C_aggregator)
{
this.Configaggregator = C_aggregator;
Configaggregator.GetEvent<ToConfigWindow>();
ToConfigWindow.Instance.Subscribe(actualizeCompStatus);
Configaggregator.GetEvent<FromConfigWindow>();
}
[Import]
public IEventAggregator Configaggregator;
The two events are looking like this and both already worked until 2 days ago ;-)
[Export]
public class FromConfigWindow : PubSubEvent<Int16>
{
private static readonly EventAggregator _eventAggregator;
private static readonly FromConfigWindow _event;
static FromConfigWindow()
{
_eventAggregator = new EventAggregator();
_event = _eventAggregator.GetEvent<FromConfigWindow>();
}
public static FromConfigWindow Instance
{
get { return _event; }
}
}
[Export]
public class ToConfigWindow : PubSubEvent<Int16>
{
private static readonly EventAggregator _eventAggregator;
private static readonly ToConfigWindow _event;
static ToConfigWindow()
{
_eventAggregator = new EventAggregator();
_event = _eventAggregator.GetEvent<ToConfigWindow>();
}
public static ToConfigWindow Instance
{
get { return _event; }
}
}
So finally the Problem looks to me like the EventAggregator doesn't get instanciated and therefore the exception gets thrown. But how can I work around this? Or am I doing something wrong in the linkage of the Aggregator in the constructors?
I already tried to modify all constructor arguments with an [Import] attribute, this threw the same exception as well, or removing all [Import] attributes form the IEventAggregator Configaggregator objects in the ViewModels.
The problem is similar to this link here but in my case happens with the EventAggregator from the Prism framework.
Please tell me if I should provide you more parts of the code.
I'm not sure why you're Exporting your PubSubEvents or holding a reference to a static new EventAggregator in them. You should only be using one instance of EventAggregator (in this example) which you'll get from your container (Prism will put it there for you).
You should go back to basics and read the excellent Prism documentation. Section 9 "Communicating Between Loosely Coupled Components" for an overview of EventAggregator and PubSubEvents.
If you want to create a bare bones project which simulates the problem you're having and upload it somewhere, I'll gladly have a look at it for you.
It looks like the error is because you have not satisfied all your imports before beginning operations on them. I concur with ChrisO, you should go back and check out the docs. There are a lot of things going on here which seem to be overcomplicating things. Why is there a new EventAggregator() in there? MEF should import that dependency for you. Why all the messing around with the container and getting exports? It seems you are doing a lot of work that should be taken care of by MEF and Prism.

Categories

Resources