I've recently looked into using MEF in order to build a plugin framework and also read quite a few articles and followed tutorials however what I'm attempting to achieve (and i haven't seen an example of this yet) is to provide an access point so i can load plugins from a set directory at the point of (for example) form loads, in order to alter the controls or prevent load ect or another example would be button clicks in order to extent or once again prevent standard functionality from taking place. Could anyone point me in the direction of other resources or provide a simple example explaining how this could be accomplished?
TIA
This is a simple implementation example. First add the reference System.ComponentModel.Composition to the project.
Declare the Plugin interface:
[InheritedExport]
public interface IPlugin
{
string Name { get; }
}
In the same or another assembly, implement the bellow interface.
public class Plugin1 : IPlugin
{
public string Name
{
get { return "Plugin#1"; }
}
}
Later build your Catalog using DirectoryCatalog and AssemblyCatalog.
public class CatalogManager : IDisposable
{
[ImportMany(typeof (IPlugin), AllowRecomposition = true)]
private IEnumerable<IPlugin> _plugins;
private CompositionContainer _container;
public CompositionContainer Container
{
get { return _container; }
}
public IEnumerable<IPlugin> Plugins
{
set { _plugins = value; }
}
private CatalogManager(string pluginsDir)
{
var catalog = new AggregateCatalog();
//--load all plugin from plugin directory
if (Directory.Exists(pluginsDir))
catalog.Catalogs.Add(new DirectoryCatalog(pluginsDir));
//--load all plugin from executing assembly
catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
Initialize(catalog);
}
private void Initialize(AggregateCatalog catalog)
{
_container = new CompositionContainer(catalog);
_container.ComposeParts(this);
}
public void Dispose()
{
if (_container == null)
return;
_container.Dispose();
_container = null;
}
}
Related
I'm pretty new to Specflow and C#, so I'm facing an issue with specflow hooks.
The problem is: when I use [BeforeScenario], the method is not even called while debugging.
Removing these hooks and replacing it by [TestInitialize], it works perfectly.
I searched here for solution in many questions, but I didn't find any problem besides something about private methods, which not seems to be my case.
I have 4 classes: Tests, Steps, PageObjects, and Hooks (which contains driver and hooks).
'Tests' class inherits from 'Steps', which inherits from 'PageObjects', which inherits from 'Hooks'.
Every call is public and I'm writing down some code from 'Hooks' class:
namespace AutomationPractice.Helper
{
[Binding]
public class Hooks
{
public IWebDriver _driver;
[BeforeFeature]
public void BeforeScenario()
{
if (_driver == null)
{
_driver = new ChromeDriver();
}
else { throw new Exception("Couldn't initialize the driver"); }
}
[AfterFeature]
public void AfterScenario()
{
if (_driver != null)
{
_driver.Quit();
}
else throw new Exception("There was an error while trying to close the driver");
}
}
}
'PageObjects' class:
namespace AutomationPractice.PageObjects
{
[Binding]
public class GoogleSearchPageObjects : Hooks
{
public string goToGooglePage(string url)
{
return _driver.Url = url;
}
public IWebElement GetTxtSearch()
{
return _driver.FindElement(By.Name("q"));
}
public void fillTxtSearch(string search)
{
GetTxtSearch().SendKeys(search);
}
}
}
'Steps' class:
namespace AutomationPractice.Steps
{
[Binding]
public class GoogleSearchSteps : GoogleSearchPageObjects
{
[Given(#"I am on google home page")]
public void GivenIAmOnGoogleHomePage(string url)
{
goToGooglePage(url);
}
[When(#"I fill the '(.*)' field")]
public void WhenIFillTheField(string search)
{
fillTxtSearch(search);
}
Every class is rounded by [Binding] though.
Thanks in advance!
You have too many things going on in the same class hierarchy. It would be much simpler to decouple the following things:
The Web Driver
The page objects
Step definitions
You can use SpecFlow's dependency injection framework to wire these things together using constructor arguments.
First your Hooks class where you manage the web driver instance for all step definitions and page objects:
[Binding]
public class Hooks
{
private IObjectContainer container;
public Hooks(IObjectContainer container)
{
this.container = container;
}
[BeforeScenario]
public void CreateWebDriver()
{
var driver = new ChromeDriver();
container.RegisterInstanceAs<IWebDriver>(driver);
}
[AfterScenario]
public void DestroyWebDriver()
{
var driver = container.Resolve<IWebDriver>();
driver.Quit();
driver.Dispose();
}
}
And the google search page object becomes a separate class that receives a web driver object as a constructor parameter, which decouples it from SpecFlow all together.
public class GoogleSearchPage
{
private readonly IWebDriver driver;
private IWebElement TxtSearch => driver.FindElement(By.Name("q"));
public GoogleSearchPage(IWebDriver driver)
{
this.driver = driver;
}
public void EnterSearchTerm(string searchTerm)
{
TxtSearch.SendKeys(searchTerm);
}
}
And finally the step definition class, which is where everything gets wired together via the dependency injection framework that comes with SpecFlow:
[Binding]
public class GoogleSearchSteps
{
private GoogleSearchPage googleSearch;
public GoogleSearchSteps(IWebDriver driver)
{
googleSearch = new GoogleSearchPage(driver);
}
[When(#"I fill the '(.*)' field")]
public void WhenIFillTheField(string search)
{
googleSearch.EnterSearchTerm(search);
}
}
Part of the problem you have right now is the class hierarchy. You are mixing classes that should be separated, but coordinated. By separating the step definitions from the initialization of the web driver, and keeping the page object in its own class you keep the dependencies between these objects organized and limited to exactly what they need (decoupling), and yet still allow them to work together (cohesion).
Your methods are names BeforeScenario and AfterScenario, but you are using the attributes for BeforeFeature and AfterFeature.
These have to be static that they will be called.
You need to change the attributes.
I have a class that is instantiated using dependency injection of a logger type like so:
var _logger = Logger.GetLogger(LoggerType.MongoLogger);
var service = new MyService(_logger);
in my unit tests, I replace the logger to use:
var _logger = Logger.GetLogger(LoggerType.TextFileLogger);
Now, I want to use MEF to load MyService as a plugin I created the service like this:
[Export(typeof(IService))]
public class MyService: IService
{
private ILogger _logger;
public MyService(ILogger logger)
{
this._logger = logger;
}
public void DoServiceWork()
{
_logger.Log("Starting service work");
}
}
How do I make this thing work in the MEF framework ?
Edited:
Added more elaborate example, using a console app.
Bootstrap Class
This class creates the MEF container as well as initializes the aggregate catalog. You should also instantiate other exportable items, e.g. ILogger, which be used by other dependent classes in your program. Creating properties and marking them with Export allows these instances to be used throughout your program.
You should only instantiate this class once. In this example, we instantiate it in the main program block at the startup.
We have marked both Container and ILogger as exports as we want these instances to be available to other dependent classes.
Exporting IService
Marking your MySerivce class with Export(IService) allows it to be exportable in MEF. We use MEF to get an instance of it by calling Container.GetExportedValue<IService>();. Note: by default MEF will use singleton shared instance, i.e. object will be created once. If you want non-shared instances, you will have to mark classes with PartCreationPolicy(CreationPolicy.NonShared)
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition;
class Program
{
static void Main(string[] args)
{
var bootstrap = new Bootstrap();
var service = bootstrap.Container.GetExportedValue<IService>();
service.DoServiceWork();
}
}
public class Bootstrap
{
[Export]
public CompositionContainer Container { get; private set; }
[Export(typeof(ILogger))]
public ILogger Logger { get; private set; }
public Bootstrap()
{
//Create an aggregate catalog that will hold assembly references
var catalog = new AggregateCatalog();
//Adds this assembly.
//Exports defined in the classes and types within this assembly will now be composable
//Add to the catalogs if there are more assemblies where exports are defined.
catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
//Create the CompositionContainer with the parts in the catalog
this.Container = new CompositionContainer(catalog);
this.Logger = Logger.GetLogger(LoggerType.MongoLogger);
this.Container.ComposeParts(this);
}
}
[Export(typeof(IService))]
public class MyService : IService
{
//adding pragma directive removes compiler warning of unassigned property/field
//as these will be assigned by MEF import
#pragma warning disable
[Import]
private ILogger _logger;
#pragma warning restore
public MyService()
{
//logger will be instantiated by MEF
}
public void DoServiceWork()
{
_logger.Log("Starting service work");
}
}
Hi am working on console application which uses Dependency Injection pattern, i have created the interface like
public interface IRecurringTransactionPlanDataService : IService<RecurringTransactionPlan>
{
IQueryable<RecurringTransactionPlan> RecurringTransactionPlanData();
}
and the class implements as
public class RecurringTransactionPlanDataService : Service<RecurringTransactionPlan>, IRecurringTransactionPlanDataService
{
private readonly IRepositoryAsync<RecurringTransactionPlan> _repository;
public RecurringTransactionPlanDataService(IRepositoryAsync<RecurringTransactionPlan> repository)
: base(repository)
{
_repository = repository;
}
public IQueryable<RecurringTransactionPlan> RecurringTransactionPlanData()
{
return _repository.RecurringTransactionPlanData();
}
}
The repository:
public static class RecurringTransactionPlanRepository
{
public static IQueryable<RecurringTransactionPlan> RecurringTransactionPlanData(this IRepository<RecurringTransactionPlan> repository)
{
return repository.Queryable();
}
}
the above code all in separate projects, i am using this in MVC and also in console application, while using in MVC there is no error fetching data from db by UnityConfig.cs, but in console application we need to manually register and resolve the interfaces, i have tried this,
My Console Application:
public class RecurringTransaction
{
public readonly IRecurringTransactionPlanDataService _recurringTransactionPlanDataService;
public RecurringTransaction()
{
var container = new UnityContainer();
container.RegisterType<IRecurringTransactionPlanDataService, RecurringTransactionPlanDataService>();
_recurringTransactionPlanDataService = container.Resolve<IRecurringTransactionPlanDataService>();
}
}
public class Program
{
public static void Main(string[] args)
{
FeePaymentTracker.UnityConfig.RegisterComponents();
RecurringTransaction rt = new RecurringTransaction();
var restult = rt.GetRecurringTransactionRecords();
}
}
am getting the above error. expecting your ideas to resolve the error.
In your RecurringTransaction-method you create a new container and then you register RecurringTransactionPlanDataService in that new container. But you do not register the dependencies that the implementation RecurringTransactionPlanDataService has. That container will only have one registration.
var container = new UnityContainer();
container.RegisterType<IRecurringTransactionPlanDataService, RecurringTransactionPlanDataService>();
_recurringTransactionPlanDataService = container.Resolve<IRecurringTransactionPlanDataService>();
Since RecurringTransactionPlanDataService has a dependency to IRepositoryAsync<RecurringTransactionPlan> you need to register that as well.
Change your code to:
var container = new UnityContainer();
container.RegisterType<IRecurringTransactionPlanDataService, RecurringTransactionPlanDataService>();
container.RegisterType<IRepositoryAsync<RecurringTransactionPlan>, YourRepositoryImplementation>();
_recurringTransactionPlanDataService = container.Resolve<IRecurringTransactionPlanDataService>();
As a sidenote you may want to re-use the same container. In console-applications I usually resolve a "ProgramStarter", which then gets the correct injections. This way you only need to use the service locator anti-pattern in the root. But can use proper DI in the rest of the application.
class Program
{
static void Main(string[] args)
{
var container = new UnityContainer();
container.RegisterType<ProgramStarter, ProgramStarter>();
// Pass the same container to the config.
FeePaymentTracker.UnityConfig.RegisterComponents(container);
var program = container.Resolve<ProgramStarter>();
program.Run();
}
}
public class ProgramStarter
{
IRecurringTransactionPlanDataService _dataService;
public ProgramStarter(IRecurringTransactionPlanDataService dataService)
{
_dataService = dataService;
}
public void Run()
{
// Do stuff.
}
}
in the code that you have posted you have an interface called IPaymentService and its implementation. It seems ok. But then in the screenshot you are trying to resolve a dependency called RecurringTransactionPlanDataService. Make your that you have registered this dependency. Could you add information about how you are registering the dependencies in the container?
The problem in your code is that you are trying to resolve the implementation instead of the interface. You should change the following line:
_recurringTransactionPlanDataService = container.Resolve<RecurringTransactionPlanDataService>();
with that:
_recurringTransactionPlanDataService = container.Resolve<IRecurringTransactionPlanDataService>();
Cheers,
What I am basically trying to achieve is to combine MEF and Castle Windsor.
But I happen to be unable to get started using MEF. The Problem is, when I call ComposeParts on the compositionContainer in the MefInstaller-Class, it doesn't fill the installers-collection, for some reason.
The DirectoryCoatalog words fine and loads the needed files properly (including ModuleA.dll).
public class MefInstaller : IWindsorInstaller
{
[ImportMany]
public IEnumerable<IWindsorInstaller> installers { get; set; } // here the Exported Objects should be stored
public void Install(IWindsorContainer container, IConfigurationStore store)
{
var directory = Environment.CurrentDirectory;
var directoryCatalog = new DirectoryCatalog(directory);
var compositionContainer = new CompositionContainer(directoryCatalog);
compositionContainer.ComposeParts(this);
foreach (var windsorInstaller in installers)
{
windsorInstaller.Install(container, store);
}
Console.WriteLine("List in Field : {0}", installers.Count());
}
}
The Class to import looks as the following:
[Export("ComponentInstaller")]
public class ModuleAInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container
.Register(Component.For<IFoo>().ImplementedBy<FooA>())
.Register(Component.For<IBar>().ImplementedBy<BarA>());
}
}
What am i doing wrong? I have experimented with different attributes and contract names on both the MefInstaller and ModuleAInstaller-Classes. I have also tried to compose the parts using a CompositionBatch with no success. Any help is greatly appreciated!
Nevermind, I have solved it. The problem was the attribute on the ModuleAInstaller-Class.
everything works fine using
[Export(typeof(IWindsorInstaller))]
public class ModuleAInstaller : IWindsorInstaller
{
[...]
}
I am using MEF and my plugin directory is c:\dev\plugins
I have a form, but when I open it up, I have the following error:
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 'Helper (ContractName="IHelper")' is not assignable to type 'IHelper'. Resulting in: Cannot set import 'helper (ContractName="IHelper")' on part 'Manager'. Element: helper (ContractName="IHelper") --> Manager`
I have two assemblies that contain the same exports, but I am using DirectoryCatalog to only load one of them at a time.
This error only seems to show in the designer. When I run the code, I don't get an exception and the app runs fine. The designer does give me the option to Ignore and Continue but I did this once and it failed, so I am holding back.
public class Manager
{
private static readonly Manager instance = new Manager();
public static IHelper Helper { get { return Manager.instance.helper; } }
[Import(typeof(IHelper))]
internal IHelper helper { get; set; }
private Manager()
{
using (DirectoryCatalog catalog =
new DirectoryCatalog(#"c:\dev\plugins"))
{
using (CompositionContainer container =
new CompositionContainer(catalog))
{
container.ComposeParts(this);
}
}
}
}
public interface IHelper
{
string LabelText { get; }
}
[Export(typeof(IHelper))]
public class SpecificHelper : IHelper
{
public string LabelText
{
get { return "Id:"};
}
}