Running into trouble with MEF - c#

I'm trying to create an extensible "utility" console application in .NET 4, and I figured using MEF to do this would give me the best in terms of flexibility and extensibility.
So I started setting up a MEF interface:
public interface IUtility
{
string Title { get; }
string Version { get; }
void Execute(UtilContext context);
}
And then I created two nearly identical test plugins - just to see how this stuff works:
MEF Plugin:
[Export(typeof(IUtility))]
public class Utility1 : IUtility
{
public string Title
{
get { return "Utility 1"; }
}
public string Version
{
get { return "1.0.0.0"; }
}
public void Execute(UtilContext context)
{
}
}
The console app that acts as the "host" for the MEF plugins looks something like this:
[ImportMany]
public IEnumerable<IUtility> _utilities { get; set; }
public void SetupMEF()
{
string directory = ConfigurationManager.AppSettings["Path"];
AggregateCatalog catalog = new AggregateCatalog();
catalog.Catalogs.Add(new DirectoryCatalog(directory));
CompositionContainer container = new CompositionContainer(catalog);
container.ComposeParts(this);
}
I checked - the directory is being read from the app.config correctly, and the plugin modules (*.dll files) are present there after solution has been built. Everything seems just fine..... until I get this exception:
System.Reflection.ReflectionTypeLoadException was unhandled
Message = Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information.
LoaderException:
Method 'get_Version' in type 'Utility.Utility1' from assembly 'Utility1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation
Hmmm.... what exactly is MEF trying to tell me here? And how do I fix this problem? Any thoughts, ideas, pointers?
Did I break some convention by having a property called Version of my own? Is that something reserved by MEF?

Sorry guys - my bad. There was a very old *.dll lingering around in the "plugin" directory that indeed did not have any implementation for that property's Get method.
Wiping out that pre-alpha ;-) *.dll solved my problem. I can indeed load my plugins now, and they can have a property called Version without any problems.

Related

Xamarin Linking Sdk Assemblies Only - using AssemblyName vs AssemblyName.Class.Method

I have a DLL MyAssemblyOne.dll which only contains one class with static methods:
namespace MyAssemblyOne
{
public class MyClassOne
{
public static string MyStaticMethod()
{
...
}
}
}
All is good so far, the assembly MyAssemblyOne.dll is generated.
Now I have another DLL, MyAssemblyTwo.dll which has a dependency on MyAssemblyOne.dll and uses it like:
no using here;
namespace MyAssemblyTwo
{
public class MyClassFromAssemblyTwo
{
public string SomeRandomMethod()
{
...
var smth = MyAssemblyOne.MyClassOne.MyStaticMethod();
...
}
}
}
Now I create a Xamarin project with Linking set to Sdk Assemblies Only and Use Shared Runtime disabled(basically Release mode), and I add my two DLLs - MyAssemblyTwo.dll and MyAssemblyOne.dll. The app builds ok, but when I run it I get something like:
cannot find MyAssemblyOne.dll.
Please note that this works if the Linking option is set to None.
However, if I change MyAssemblyTwo usage of MyAssemblyOne to be:
using MyAssemblyOne;
namespace MyAssemblyTwo
{
public class MyClassFromAssemblyTwo
{
public string SomeRandomMethod()
{
...
var smth = MyClassOne.MyStaticMethod();
...
}
}
}
everything works fine even with the Linking set to Sdk Assemblies Only.
How does the linker work? Why if I have a using statement everything is fine, but if I use the assembly name directly in the code it breaks.
It is worth mentioning that MyAssemblyOne and MyAsseblyTwo are .netstandard20 projects.

How to load a MEF component from another assembly in .net core 2.1

I'm just taking my first baby steps in the MEF territory and wanted to do so using .net core 2.1.
Using VS 2017 (version 15.8.8) I've done a small Console App (.NET Core) with an interface
interface IMessageSender
{
void Send(string message);
}
and an implementation (in the same project)
[Export(typeof(IMessageSender))]
public class EmailSender : IMessageSender
{
public void Send(string message)
{
Console.WriteLine("EmailSender : " + message);
}
}
Finally I have a small compose method executed from my Main(string[] args)
[Import]
private void Compose()
{
var assembly_A = new[] { typeof(Program).GetTypeInfo().Assembly };
var config_A = new ContainerConfiguration().WithAssembly(assembly_A[0]);
var container_A = config_A.CreateContainer();
var msg_A = container_A.GetExport<IMessageSender>();
msg_A.Send("Hello");
}
It works as expected
However, if I add a new class library to my solution and move my implementation of Send(string) to the newly added project things do not work out.
namespace AnotherMefExtensionProjectNamespace
{
[Export(typeof(IMessageSender))]
public class EmailSenderExtended : IMessageSender
{
public void Send(string message)
{
Console.WriteLine("EmailSenderExtended : " + message);
}
}
}
The new Compose method
[Import]
public IMessageSender MessageSender { get; set; }
private void Compose()
{
var assembly_B = new[] { typeof(EmailSenderExtended).GetTypeInfo().Assembly };
var config_B = new ContainerConfiguration().WithAssembly(assembly_B[0]);
var container_B = config_B.CreateContainer();
var msg_B = container_B.GetExport<IMessageSender>();
msg_B.Send("Hello");
}
I've tried to compare the different configs and containers (_A versus _B in the examples) but can't understand what is different. I've even tried to extend the class ContainerConfiguration to load from a specified assembly and it works as long as the given file contains the Main method but fails if I use my "extended" .NET Core Class Library.
public static ContainerConfiguration WithChosenAssembly(this ContainerConfiguration configuration, string pathAndFile)
{
var context = AssemblyLoadContext.Default.LoadFromAssemblyPath(pathAndFile);
var ass_list = new List<Assembly>() { context };
configuration = configuration.WithAssemblies(ass_list, null);
return configuration;
}
I was under the impression that you extend your main application by developing a class library that basically implements the interfaces specified.
I seem to be unable to do this currently, but obviously I misunderstood something very basic.
If someone would care to put me on the right track or give me an alternative idea for "plug-in" development for .net core I would be very grateful.
King regards
Magnus
I realized that my test setup does not mimic any real world scenario and thus I brought my problems on myself.
Obviously I should have had three projects.
One project with only the interface definitions.
One "main" project where all my regular code exists.
One (or more) projects where my MEF implementations of the interfaces exist.
Reviewing my example and adhering to the obvious "design" above it all works exactly as it should.
Most StackOverflow users probably wouldn't make my blunder but for those that did, I hope the above helps. :-)

MvvmCross and PCL Service Layer - how to use OS specific functions

Been googling it for a while, but found nothing really good.
So, the thing is, in MVVM Cross guides our solution (for mobile platforms) is separated on, as example, HelloWorld.Core project and few platform specific projects, as example HelloWorld.Android.UI.
You can see full tutorial here https://www.mvvmcross.com/documentation/tipcalc-tutorial/the-tip-calc-tutorial
And, the thing is, in our .Core project we creating Services, ViewModels and we registering it in MVVM Cross provided IoC-container.
In example which provided by the MvvmCross documentation Service implementation is really simple, just one function like that
public class CalculationService: ICalculationService
{
public double TipAmount(double subTotal, int generosity)
{
return subTotal * ((double)generosity)/100.0;
}
}
so we just go ahead, registering it in our App.cs and then its fine to use in binded ViewModel
public class App: MvxApplication
{
public App()
{
Mvx.RegisterType<ICalculationService, CalculationService>();
Mvx.RegisterSingleton<IMvxAppStart>(new MvxAppStart<HomeViewModel>());
}
}
View model code :
public class TipViewModel : MvxViewModel
{
readonly ICalculation _calculation;
public TipViewModel(ICalculation calculation)
{
_calculation = calculation;
}
// ...
double _tip;
public double Tip
{
get {
return _tip;
}
set
{
_tip = value;
RaisePropertyChanged(() => Tip);
}
}
void Recalculate()
{
Tip = _calculation.TipAmount(SubTotal, Generosity);
}
}
But the thing is, when I started playing around with that, I decided, to, let say, code some simple local storage data scanner to find some specific files.
Simple code example:
public class SimpleDataScanner : IBaseDataScanner
{
private string _basePath { get; set; }
private List<string> _scanResult { get; set; }
public SimpleDataScanner()
{
_basePath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
_scanResult = new List<string>();
}
public List<string> BasicDataScan(string startPath = null)
{
string startFolder = _basePath;
if (startPath.Length > 0 && startPath.Length != null)
{
startFolder = Path.Combine(_basePath, startPath);
}
try
{
TreeScan(startFolder);
}
catch (Exception)
{
throw;
}
return _scanResult;
}
private void TreeScan(string path)
{
foreach (var file in Directory.GetFiles(path))
{
if (Path.HasExtension("pdf"))
{
_scanResult.Add(file);
}
}
foreach (string dir in Directory.GetDirectories(path))
{
TreeScan(dir);
}
}
}
And, as I found out, the problems are:
1) I can't use Android specific functions and properties in PCL
2) I can't use reference in my PCL Core project to, lets say, some other Android class lib project
So, I can't create working Service which means I can't register it in IoC-container and pass to ViewModel.
Whats the way of doing such things?
Any tutorials? Or I getting whole idea wrong?
You need to implement the interface in your platform code, and register the concrete implementation against the interface in Setup.cs inside your platform project. So in your case, IDataScanner would exist inside your Core project, while SimpleDataScanner would live in the Android project.
Inside Setup.cs, you'd register the concrete implementation like this:
protected override void InitializeFirstChance()
{
base.InitializeFirstChance();
Mvx.RegisterSingleton<IDataScanner>(() => new SimpleDataScanner());
}
Essentially, if the interface requires platform-specific or full .NET framework code to implement, those per-platform implementations live in the platforms and are registered in the platform setup. If both the interface and the implementation are shared within the Core project, the dependency can be registered in App.cs.

Mapping classes from Assemblies which are stored in a Different Folder

I'm trying to build an extensible application using MEF and fluent Nhibernate as ORM.
It was working well until I decided to store the extension assemblies in a seperate folder
(\Extensions). MEF loads the extensions without any problem, but
nhibernate throws exceptions because it can't locate the assembly.
Is there a way to add the Extensions Folder to the Assembly searchpath?
MEF Composition:
[ImportMany]
public IEnumerable<IModule> Modules { get; private set; }
public void LoadModules()
{
_initialized = false;
var catalog = new DirectoryCatalog("Extensions");
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
}
Mapping:
foreach (var module in modules)
{
var assembly = module.GetType().Assembly;
config.Mappings(m => m.FluentMappings.AddFromAssembly(assembly));
}
If I'm understanding your question correctly, there are a couple of options:
AppDomain.CurrentDomain.SetupInformation.PrivateBinPath will work if your Extension directory is or is in a subdirectory of the application's directory.
Alternatively, you could handle the AppDomain.AssemblyResolve event.

MEF 'The export is not assignable to type' error

I have just started using MEF and have hit on an early problem.
I have an interface called DataService:
namespace DataAccess
{
interface IDataService
{
string Name { get; }
string Description { get;}
List<String> GetPeople();
}
}
There are 2 implementations of this interface, one for SQL Server and one for Oracle.
Below is the Oracle implementation, SQL Server implementation is exactly the same.
namespace DataAccess
{
[Export(typeof(IDataService))]
[ExportMetadata("Name","Oracle")]
[ExportMetadata("Description","Oracle Data Service")]
public class Oracle : IDataService
{
#region IDataService Members
public string Name
{
get { return "Oracle"; }
}
public string Description
{
get { return "Provides data access to Oracle database"; }
}
public List<string> GetPeople()
{
return new List<String>() { "Oracle boo", "Oracle boo1" };
}
#endregion
}
}
The name and description properties are now defunct as I have replaced these with metadata. As you can see, they are very simple objects, I wanted to make sure I could get this to work before I started doing the hard work.
This is the code I am using to discover the assemblies:
private static CompositionContainer _container;
private const string ASSEMBLY_PATTERN = "*.dll";
private AggregateCatalog _catalog;
[ImportMany]
IEnumerable<DataAccess.IDataService> services { get; set; }
private void button3_Click(object sender, EventArgs e)
{
_catalog = new AggregateCatalog(
new DirectoryCatalog(txtLibPath.Text, ASSEMBLY_PATTERN),
new AssemblyCatalog(Assembly.GetExecutingAssembly()));
_container = new CompositionContainer(_catalog);
_container.ComposeParts(this);
MessageBox.Show(services.Count().ToString());
}
This is the error that is produced:
The composition produced a single composition error. The root cause is provided below. Review the CompositionException.Errors property for more detailed information.
1) The export 'DataAccess.Oracle (ContractName="DataAccess.IDataService")' is not assignable to type 'DataAccess.IDataService'.
Resulting in: Cannot set import 'MEFTest.Form1.services (ContractName="DataAccess.IDataService")' on part 'MEFTest.Form1'.
Element: MEFTest.Form1.services (ContractName="DataAccess.IDataService") --> MEFTest.Form1
It doesn't seem to make any sense that it can't assign to the interface that it was designed for!
Once this problem is solved, my next issue is how to pick one and get an instance of it...
It looks like two different versions of your contract assembly (the one with DataAccess.IDataService) are getting loaded. One is probably from your executable path and the other from your plugin path. I touch on this issue a bit in my blog post on How to Debug and Diagnose MEF Failures, and the MSDN page on Best Practices for Assembly Loading goes into more detail.
Yet another cause:
Code:
interface IMyService
{
}
[Export(typeof(IMyService))]
class MyService
{
}
Message:
The export 'IMyService' is not assignable to type 'IMyService'.
Cause:
The MyService class does not implement the IMyService interface.
For me this had a very simple fix.
Here's a link! that explains the root cause.
In my case, I locked my Assembly version down, but my file version travels. My nuget package ID matches my assembly file version.
Final result is that I can build continuously, create new nugets, and not have this MEF inteface problem.
I must tell that I had such an error in completely idiotic context. Accidentally, I misplaced export directive and put it not on class but on a function inside class:
interface MyInterface
{
void MyFunction();
}
public class MyClass : MyInterface
{
[Export(typeof(MyInterface))]
void MyFunction() { }
}
Surprisingly, the code compiled very fine without any warnings. But then I ve spent hours trying to figure out why MEF fails on my silly misprint!

Categories