Approach
I am employing MEF to create a plugin-nable, if you will, application. My MEF host has an ILogger which exposes TraceMessage(string message). Class Logger implements ILogger and is decorated with an Export attribute so Logger looks like:
[Export(typeof (ILogger))]
public class Logger : ILogger
{ }
The idea being that the various plugins can be offered a central logger that they can write to. Thus, instantiation would be via [Import] attribue, for example:
[Export(typeof (ILogger))]
public class Logger : ILogger
{
private readonly IWindsorContainer _container;
public ICloudTrace CloudTrace
{
get { return _container.Resolve<ICloudTrace>(); }
}
public Logger()
{
_container = new WindsorContainer(new XmlInterpreter());
}
public void TraceMessage(string categoryName, string componentName, string message)
{
CloudTrace.TraceMessage(categoryName, componentName, message);
}
}
And subsequently a log message would be written via Logger.TraceMessage(string message).
Problem
However, this approach throws an InvalidOperationException in my host when its trying to resolve exports, with an error message Sequence contains no matching element.
Exports are resolved in ResolveType(string commandType) (where commandType is the command line parameter needed to execute relevant plugin). ResolveType() looks like:
public dynamic ResolveType(string commandType)
{
try
{
return this.Container.GetExports<ICommand, ICommandMetaData>()
.First(contract => contract.Metadata.CommandType.Equals(commandType, StringComparison.OrdinalIgnoreCase))
.Value;
}
catch (Exception e)
{
Console.WriteLine(e.message);
}
}
I should mention that each plugin has an Execute(Dictionary<string, string> parameters) which is the entry point for the plugin and the class containing this method is decorated with [Export(typeof(ICommand))] [ExportMetadata("CommandType","CommandLine Command string goes here")] attributes.
The problem was in the CompositionContainer construction. Currently, it just loads the plugin assembly specified in the command line instead of doing directory scan or loading currently executing assembly. This has been done for various reasons. So:
var assemblyCatalog = new AssemblyCatalog(Assembly.LoadFrom(assemblyFile: assemblyToLoad));
where assemblyToLoad is a string to the .dll file for the specific plugin. However, the logger is in the host so the host's assembly needs to be loaded. Thus:
var assemblyCatalog = new AggregateCatalog(new AssemblyCatalog(Assembly.GetExecutingAssembly()),
new AssemblyCatalog(Assembly.LoadFrom(assemblyFile: assemblyToLoad)));
fixes the issue.
Thanks to #Matthew Abbott for pointing this out
Related
I am creating the controller dynamically for the types to be loaded from the dll. For that I created one base controller to handle dynamic controller request.
My dynamic controller has following dependencies in the construcor
public class Order
{
private readonly IDBLogger _logger;
private readonly IExceptionHandler _handler
public Order(IDBLogger logger, IExceptionHandler handler)
{
_logger=logger;
_handler=handler
}
public void GetOrderDetail()
{
_logger.log("....."); //Here I am getting null reference as I am not able resolve this _logger type.
/// rest code
}
}
Since the class is loaded from the dll how can I resolve this.
I think my assumption of your objective was way off and it actually seems to me you don't need to mess with ApplicationParts or FeatureProviders at all. To me it appears to be a DI registration issue.
I started with boilerplate .net core 3.1 MVC application (nothing fancy there)
then I added two more class library projects: Common and Orders
Common.dll
I assumed that your interfaces (I'll just use IDBLogger for brevity) must be made available to the controller assembly as well as to the main application. So both projects are set to depend on it.
namespace Common
{
public interface IDBLogger
{
void Log(string s);
}
}
Orders.dll
this assembly defines a web api controller:
using Common;
namespace Orders
{
public class Order
{
private readonly IDBLogger _logger;
public Order(IDBLogger logger)
{
_logger = logger;
}
public void GetOrderDetail()
{
_logger.Log("Inside GetOrderDetail");
}
}
}
MVC
as most of the code is stock standard I'd only list files that I changed.
Note, that now I'm relying on default Controller resolver I only need to register my types onto ServiceCollection
Startup.cs
...
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews(); // add all controllers as you normally would. I think you don't need to mess with ApplicationParts. but if you do, you are already aware that https://stackoverflow.com/questions/36680933/discovering-generic-controllers-in-asp-net-core is a good example
services.AddScoped<IDBLogger, IdbLogger>();
// load assembly and register with DI
var assembly = Assembly.LoadFrom(Path.Combine("..\\path\\to", "Orders.dll"));
var orderType = assembly.ExportedTypes.First(t => t.Name == "Order");
services.AddScoped(orderType); // this is where we would make our type known to the DI container
var loadedTypesCache = new LoadedTypesCache(); // this step is optional - I chose to leverage the same DI mechanism to avoid having to load assembly in my controller for type definition. you can probably find a better approach at doing this
loadedTypesCache.LoadedTypes.Add("order", orderType);
services.AddSingleton(loadedTypesCache); // singleton seems like a good fit here
}
IdbLogger.cs
using Common;
using System;
namespace Test
{
public class IdbLogger : IDBLogger
{
public void Log(string s)
{
Console.WriteLine(s); // nothing fancy here either
}
}
}
ValuesController.cs
[Route("api/[controller]")]
[ApiController]
public class ValuesController
{
public ValuesController(IServiceProvider serviceProvider, LoadedTypesCache cache)
{
var order = serviceProvider.GetService(cache.LoadedTypes["order"]); // you could load the same assembly here to get the type again, but i opted to inject a dictionary with preloaded type instead
// following two lines are just to call the method. you probably have better way of doing it
var m = cache.LoadedTypes["order"].GetMethod("GetOrderDetail", BindingFlags.Public|BindingFlags.Instance);
m.Invoke(order, new object[] { });
}
[HttpGet]
public IEnumerable<string> Get()
{
return new [] { "value1", "value2" };
}
}
relying on built-in DI container allows us to not think about manually instantiating your dynamically loaded types (and their dependencies as long as they are known to the DI container). You might find that having to use reflection to work with these instances is a bit cumbersome so you might want to explore your options there.
hopefully this is the missing piece
You can dynamically load the dependencies and the controller using reflection.
For more info, you can visit the following link.
Assemblies
I'm using MEF (System.Composition from nuget) + Common.Logging and have classes that call ILog, e.g.,
[Export(typeof(ITranslator))]
public class ATranslator : BaseTranslator
{
[ImportingConstructor]
public ATranslator(ILog log)
{
...
}
The logger instance is available from Common.Logging via log = LogManager.GetLogger<ITranslator>(); but how do I plugin this into my composition container?
var container = new ContainerConfiguration()
.WithAssembly(typeof(ITranslator).Assembly)
.CreateContainer();
container.SatisfyImports(this);
so that I can
[ImportMany]
private IEnumerable<ITranslator> Translators { get; set; }
without
System.Composition.Hosting.CompositionFailedException : No export was
found for the contract 'ILog' -> required by import 'log' of part
'ATranslator' -> required by import 'item' of part 'ITranslator[]'
-> required by initial request for contract 'IEnumerable { IsImportMany = True }'
Maybe you can try to do the export to ILog as a property in other class?
public class LogExporter
{
[Export(typeof(ILog))]
public ILog Log
{
return LogManager.GetLogger<ITranslator>();
}
}
Alternatively you can try to do it using ComposeExportedValue Method with code instead of attributes:
container.ComposeExportedValue<ILog>(LogManager.GetLogger<ITranslator>());
While registering components in Castle Windsor, how do we bind specific implementation of an interface to a component that has a dependency on that interface. I know in advance which implementation needs to be used by the component.
For example i created a sample console application based on code from several blogs and tutorials.
Following is the code.
public interface IReport
{
void LogReport();
}
public interface ILogger
{
string Log();
}
public class FileLogger : ILogger
{
public string Log()
{
return "Logged data to a file";
}
}
public class DatabaseLogger : ILogger
{
public string Log()
{
return "Logged data to a database";
}
}
public class McAfeeService : IReport
{
private readonly ILogger _logger;
public McAfeeService(ILogger logger)
{
this._logger = logger;
}
public void LogReport()
{
string getLogResult = this._logger.Log();
Console.WriteLine("McAfee Scan has " + getLogResult);
}
}
public class NortonService : IReport
{
private readonly ILogger _logger;
public NortonService(ILogger logger)
{
this._logger = logger;
}
public void LogReport()
{
string getLogResult = this._logger.Log();
Console.WriteLine("Norton Scan has " + getLogResult);
}
}
class Program
{
private static IWindsorContainer container;
static void Main(string[] args)
{
// Register components
container = new WindsorContainer();
container.Register(Component.For<IReport>().ImplementedBy<NortonService>());
container.Register(Component.For<ILogger>().ImplementedBy<FileLogger>());
IReport service = container.Resolve<IReport>();
service.LogReport();
Console.ReadLine();
}
}
I would like NortonService to always use a Filelogger and McAfeeService to use a Database Logger.
In the above program i am unable to bind NortonService to FileLogger.
How to do it?
The above answers lead me to inline dependencies and the feature service override
Here is the registration code:
container.Register(Component.For<IReport>().ImplementedBy<NortonService>().Named("nortonService"));
container.Register(Component.For<ILogger>().ImplementedBy<FileLogger>());
container.Register(Component.For<ILogger>().ImplementedBy<DatabaseLogger>());
container.Register(
Component.For<IReport>().ImplementedBy<McAfeeService>().Named("mcafeeService")
.DependsOn(Dependency.OnComponent<ILogger, DatabaseLogger>())
);
IReport mcafeescan = container.Resolve<IReport>("mcafeeService");
mcafeescan.LogReport();
IReport nortonscan = container.Resolve<IReport>("nortonService");
nortonscan.LogReport();
Output:
McAfee Scan has Logged data to a database
Norton Scan has Logged data to a file
I had a problem very like this, two implementation of one interface and two implementation of another interface. I wanted to force usage of particular implementations of those interfaces.
My class structure looked like this -
I looked at the naming convention, but didn't really like it. Instead I used the following -
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
Component.For<IMessageLoader>().ImplementedBy<MessageLoaderDatabase>()
,Component.For<IMessageLoader>().ImplementedBy<MessageLoaderFile>()
,Component.For<IMessageOfTheDayService>().ImplementedBy<MessageOfTheDayServiceDatabase>()
.DependsOn(Dependency.OnComponent<IMessageLoader, MessageLoaderDatabase>())
,Component.For<IMessageOfTheDayService>().ImplementedBy<MessageOfTheDayServiceFile>()
.DependsOn(Dependency.OnComponent<IMessageLoader, MessageLoaderFile>())
,Component.For<MessageOfTheDayController>().LifestyleTransient()
.DependsOn(Dependency.OnComponent<IMessageOfTheDayService, MessageOfTheDayServiceFile>())
);
Full info about this approach is here. In the source code provided with that post I show two other ways of achieving the same result.
If you want to do it at runtime, This can be acheived through IHandlerSelector. Write a class that implements IHandlerSelector. It provides a method SelectHandler which will let you define the condition for binding conditionally at runtime. A Handler in this case is a component in Windsor that participates in instance construction. Refer here for more details.
My answer maybe not the best one, you can use naming method to resolve multi implementation:
container.Register(Component.For(typeof(ILogger))
.ImplementedBy(typeof(FileLogger))
.Named("FileLoggerIoC")
.LifestylePerWebRequest() ,
Component.For(typeof(ILogger))
.ImplementedBy(typeof(DatabaseLogger))
.Named("DatabaseLoggerIoC")
.LifestylePerWebRequest());
In your calling functions, you need to resolve it by name :-
var fileLog = container.Resolve("FileLoggerIoC", typeof(ILogger));
var DbLog = container.Resolve("DatabaseLoggerIoC", typeof(ILogger));
Mine method maybe not the best one as people don't like service locator to get the components, you can use this as temporary solution.
For my application I'm trying to write a configuration controller, to load and save settings for certain modules. To do this I am going to use an INI file, where the section names would represent the module names (or other identification), and the values represented by a key.
I registered my controller in the bootstrapper, and use the interface in my constructor for injection in the appropriate classes. However I do not want to enter the module name every time I need to get or set a value, so I tried to use Caller info to find out what module (or class) is calling the method, but this apparently does not work (return empty string).
Is there another way to achieve what I'm trying to do?
Bootstrapper:
protected override void ConfigureContainer()
{
base.ConfigureContainer();
Container.RegisterType<IConfig, ConfigController>(new ContainerControlledLifetimeManager());
}
Config interface:
public interface IConfig
{
string[] GetSettings(string caller = "");
void Set<T>(string setting, T value, string caller = "") where T : class;
T Get<T>(string setting, string caller = "") where T : class;
}
The use of caller argument is error prone. You have many options to avoid it:
Register a ConfigController for each module. Unity supports multiple named registration. You can inject the right controller in each module in the module initialization, or with a Dependency attribute:
Container.Register<IConfig, ConfigController>("module1",
new InjectionConstructor("module1"))
.Register<IConfig, ConfigController>("module2",
new InjectionConstructor("module2"));
class Module1 {
public Module1([Dependency("module1")] IConfig config) {... }
}
Define and implement a IConfigFactory that returns a configured IConfig implementation.
interface IConfigFactory {
IConfig Create(String moduleName);
}
The ConfigController could identify the module detecting the method the made the call.
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!