Unity IoC dynamic resolution - c#

I want to resolve some dependencies which will only be known at runtime. I am using configuration file to configure Unity (not programmatically).
Here is some code to show what I want to achieve.
Classes:
internal class WorkflowFactory : IWorkflowFactory
{
public IItemWorkflow GetWorkflow(string discriminator)
{
// return an implementation of IItemWorkflow as specified in config file
return null;
}
}
public interface IWorkflowFactory
{
IItemWorkflow GetWorkflow(string discriminator);
}
public interface IItemWorkflow
{
void Handle(int id);
// More methods
}
Usage:
internal class Program
{
private static void Main(string[] args)
{
IWorkflowFactory factory = new WorkflowFactory();
// I am using args to show I do not know the string until runtime
var wf1 = factory.GetWorkflow(args[0]);
var wf2 = factory.GetWorkflow(args[1]);
}
}
If you know a better way, I am totally open and invite suggestions.

Related

Specflow Container of the steps class has not been initialized

public class BaseSteps : Steps
{
[BeforeFeature]
public static void BeforeFeatureStep()
{
var otherStep = new OtherStep();
otherStep.ExecuteStep();
}
}
public class OtherStep : Steps
{
public void ExecuteStep()
{
var key = 'key';
var val = 'val';
this.FeatureContext.Add(key, val);
}
}
This is a sample snippet. When I try to access this.FeatureContext.Add(), I get an exception stating Container of the steps class has not been initialized
Any help on this is appreciated.
The FeatureContext is not initialized, because the Step class is not resolved by the SpecFlow DI Container. So the SetObjectContainer method is not called (https://github.com/techtalk/SpecFlow/blob/master/TechTalk.SpecFlow/Steps.cs#L10).
As a general rule, you should not instantiate the steps classes on your own, but get them via Context Injection (http://specflow.org/documentation/Context-Injection).
But that is not possible in your case because you are in a BeforeFeature hook.
A possible solution would be, that you use the latest pre-release of SpecFlow (https://www.nuget.org/packages/SpecFlow/2.2.0-preview20170523).
There you can get the FeatureContext via a parameter in the hook method.
It looks like this:
[BeforeFeature]
public static void BeforeFeatureHook(FeatureContext featureContext)
{
//your code
}
Your code could then look like this:
public class FeatureContextDriver
{
public void FeatureContextChanging(FeatureContext featureContext)
{
var key = 'key';
var val = 'val';
featureContext.Add(key, val);
}
}
[Binding]
public class BaseSteps : Steps
{
[BeforeFeature]
public static void BeforeFeatureStep(FeatureContext featureContext)
{
var featureContextDriver = new FeatureContextDriver();
featureContextDriver.FeatureContextChanging(featureContext);
}
}
[Binding]
public class OtherStep : Steps
{
private FeatureContextDriver _featureContextDriver;
public OtherStep(FeatureContextDriver featureContextDriver)
{
_featureContextDriver = featureContextDriver;
}
public void ExecuteStep()
{
_featureContextDriver.FeatureContextChanging(this.FeatureContext);
}
}
Code is not tested/tried out and applies the Driver Pattern.
Full Disclosure: I am one of the maintainers of SpecFlow and SpecFlow+.

Using Unity IoC in LINQPad

I'm very new to using Unity.
I'm trying to test a segment of code in LINQPad. This code uses a DBContext which relies on Log4Net as a service. I'm trying to write the sample code to use the actual DBContext, but can't get it to construct.
The code I'm trying is below. If there is more information needed, please ask for it before down-voting, since I'm not sure how much you need to see to understand my issue, since I'm still learning Unity.
void Main()
{
RegisterObjects();
var logger = IoCHelper.Resolve<ILogger>();
//var _logger = new Clark.Logging.MultiLogger();
var _logger = logger;
var _ediContext = new EdiContext();
var transactionId = 1008;
var limit = 0;
var temp = new Type210SubscriberProvider(_ediContext)
.GetAfterNew(transactionId, limit);
temp.Dump();
var temp2 = new Type210SubscriberProvider(_ediContext)
.GetAfter(transactionId, limit);
temp2.Dump();
}
public void RegisterObjects()
{
XmlConfigurator.Configure();
var multiLogger = new MultiLogger();
multiLogger.Register(new Log4NetLogger());
IoCHelper.RegisterInstance<ILogger>(multiLogger);
}
If I try just this:
void Main()
{
var _ediContext = new EdiContext();
}
The error message I am receiving in LINQPad is:
Resolution of the dependency failed, type = "Clark.Logging.ILogger", name = "(none)".
Exception occurred while: while resolving.
Exception is: InvalidOperationException - The current type, Clark.Logging.ILogger, is an interface and cannot be constructed. Are you missing a type mapping?
At the time of the exception, the container was:
Resolving Clark.Logging.ILogger,(none)
UPDATE:
Here is some more detail from the Global.asax.cs file. I'm not sure how to translate this to LINQPad.
protected void Application_Start()
{
// GlobalConfiguration.Configuration is an HttpConfiguration object.
ConfigureContainer(GlobalConfiguration.Configuration);
ConfigureServices(GlobalConfiguration.Configuration);
}
private static void ConfigureServices(HttpConfiguration configuration)
{
configuration.Services.Add(typeof (IExceptionLogger), new UnhandledExceptionLogger(GetLogger()));
configuration.Services.Replace(typeof (IExceptionHandler), new UnhandledExceptionHandler());
}
private static void ConfigureContainer(HttpConfiguration config)
{
config.DependencyResolver = new IoCContainer(IoCHelper.Container);
new LoggingDependencyInitializer().RegisterObjects();
IoCHelper.RegisterType<IEdiContext>(new InjectionFactory(unityContainer => new EdiContext()));
IoCHelper.RegisterType<SubscriberController>();
IoCHelper.RegisterType<ConsumerInformationController>();
IoCHelper.RegisterType<TransactionTypeController>();
}
private static ILogger GetLogger()
{
return IoCHelper.Resolve<ILogger>();
}
Here is the IoCHelper class:
public static class IoCHelper
{
private static UnityContainer _container;
public static UnityContainer Container
{
get { return _container ?? (_container = new UnityContainer()); }
}
public static T Resolve<T>()
{
return Container.Resolve<T>();
}
public static void RegisterType<TFrom, TTo>() where TTo : TFrom
{
Container.RegisterType<TFrom, TTo>();
}
public static void RegisterInstance(Type type, object instance)
{
Container.RegisterInstance(type, instance);
}
public static void RegisterType<T>()
{
Container.RegisterType<T>();
}
public static void RegisterInstance<T>(T instance)
{
Container.RegisterInstance(instance);
}
public static void RegisterType<T>(InjectionFactory injectionFactory)
{
Container.RegisterType<T>(injectionFactory);
}
}
When using an IOC container, you need to register your types (e.g. map interfaces \ abstract classes into their real types). Since when you want the container to resolve an interface, it will want to find it's mapping before providing the instance.
BTW, if Unity doesn't find a map, it will try to construct the type. In your case it fails since you cannot construct an interface.
With Unity, you can do it either by configuration using:
var section = ConfigurationManager.GetSection(SectionName) as UnityConfigurationSection;
if (section != null)
{
section.Configure(container);
}
Or directly:
container.RegisterType<InterfaceType, ConcreteType>();
Since I see this code line in your example:
IoCHelper.RegisterInstance<ILogger>(multiLogger);
You are registering ILogger with a specific instance. There must be a problem with your IoCHelper implementation. Please add more code and I'll edit my answer with a specific solution.

Passing a SqlConnection to class library file (dll)

I am busy developing a class library project in C# to be reused and attached to different projects in future. It will mainly be used for Table Valued Parameters. My question is, how do I pass a SQL connection to it? The connection will be instantiated in another (main project) that the .dll gets attached to.
I currently have a Class Library Project, and have a Console Application Project created in the same solution for testing purposed.
One last requirement is that I don't want to use ConfigurationManager as the connection string will not be stored in app.config or web.config and by default the queries must be passed back to the calling application.
I've come accross a couple of links like the one below, but nothing I can really use:
Sharing a connection string
Please excuse the noobness, I am 7 weeks into professional programming.
In your dll, simply require an IDbConnection or IDbCommand. All the method is then properly abstracted against the interfaces for the data access.
For example:
In your shared dll
public static int LookUpIntForSomething(IDbConnection connection)
{
using (var command = connection.CreateCommand())
{
// use command.
}
}
In your calling app
using (var connection = new SqlConnection("ConnectionString"))
{
var int = DbQueries.LookupIntForSomething(connection);
}
This is excellent example for dependency injection. I would recommend using enterprise library unity for this kind of stuff. In your data access layer library I would define interface:
public interface IConnectionProvider {
string ConnectionString { get; }
}
public interface IAccountProvider {
Account GetAccountById(int accountID);
}
internal class AccountProvider : IAccountProvider {
private IConnectionProvider _connectionProvider;
public AccountProvider(IConnectionProvider connectionProvider) {
if (connectionProvider == null) {
throw new ArgumentNullException("connectionProvider");
}
_connectionProvider = connectionProvider;
}
public Account GetAccountById(int accountID) {
Account result;
using(var conn = new SqlConnection(connectionProvider)) {
// retrieve result here
}
return result;
}
}
public static class Bootstrapper {
public static void Init() {
ServiceLocator.AddSingleton<IAccountProvider, AccountProvider>();
}
}
Then in any assembly using your data access library you can define implementation for IConnectionProvider, like this:
internal class WebConnectionProvider : IConnectionProvider {
public string ConnectionString { get { return "Server=..."; } }
}
internal static class WebBootstrapper {
public static void Init() {
Bootstrapper.Init();
ServiceLocator.AddSingleton<IConnectionProvider, WebConnectionProvider>();
}
}
And anywhere after you call WebBootstrapper.Init() in your assembly you can use:
var accountProvider = ServiceLocator.Resolve<IAccountProvider>();
accountProvider.GetAccountById(1);
Service locator:
using System;
using Microsoft.Practices.Unity;
public class ServiceLocator {
private IUnityContainer m_Container = new UnityContainer();
public void Add<TFrom, TTo>() where TTo : TFrom {
m_Container.RegisterType<TFrom, TTo>();
}
public void BuildUp<T>(T instance) {
m_Container.BuildUp<T>(instance);
}
public void BuildUp(Type type, object instance) {
m_Container.BuildUp(type, instance);
}
public void AddSingleton<TFrom, TTo>() where TTo : TFrom {
m_Container.RegisterType<TFrom, TTo>(new ContainerControlledLifetimeManager());
}
public void AddInstance<T>(T instance) {
m_Container.RegisterInstance<T>(instance);
}
public T Resolve<T>() {
return m_Container.Resolve<T>();
}
private static ServiceLocator m_Instance;
public static ServiceLocator Instance {
get { return m_Instance; }
}
static ServiceLocator() {
m_Instance = new ServiceLocator();
}
}
if i understand your requirements correctly,I'm not sure that i do, i would setup a static struct as such
public static struct ConnectionString
{
public int ID;
public string Connection;
public override string ToString()
{
return Connection;
}
public static ConnectionString DataBase1 = new ConnectionString{ ID = 1 , Connection = "YourConnectionStringhere"};
public static ConnectionString DataBase2 = new ConnectionString{ ID = 2 , Connection = "YourConnectionString2here"};
}
and then use it as such
public void SomeMethod()
{
var I = ReferencedDll.DoSomething(ConnectionString.DataBase1.ToString());
}
or
public void SomeMethod()
{
var ClassFromDll = new ReferencedDll.SomeClass(ConnectionString.DataBase1.ToString());
ClassFromDll.DoSomething();
}
of course this leaves your connection strings hard coded which is not ideal

Strongly typed metadata in MEF2 (System.Composition)

I'm using the System.Composition namespace from the MEF for web and Windows Store apps NuGet package in a new ASP.NET MVC4 project.
I've read that in MEF2 you no longer use Lazy<IExtension, IExtensionMetadata>, but now you must provide a concrete type for the metadata view (and possibly use ExportFactory<> instead of Lazy<> ?).
However, I can't find any examples of how this should all work - just a few mentions of using a concrete type instead of an interface.
I've tried a few things, but keep getting the following error - "Export metadata for 'AccountID' is missing and no default value was supplied".
My code...
Creating the container (in Global.asax or App_Start folder):
// Get assemblies that will be providing imports and exports
var assemblies = GetAssemblies();
// Get conventions that will be used to find imports and exports
var conventions = GetConventions();
var container = new ContainerConfiguration().WithAssemblies(assemblies, conventions).CreateContainer();
// Create and apply a MefControllerFactory so controllers can be composed
ControllerBuilder.Current.SetControllerFactory(new MefControllerFactory(container));
GetConventions() method:
private static ConventionBuilder GetConventions()
{
var conventionBuilder = new ConventionBuilder();
conventionBuilder.ForTypesDerivedFrom<IController>().Export();
conventionBuilder.ForTypesDerivedFrom<IExtension>().Export<IExtension>();
conventionBuilder.ForTypesMatching(t => t.Namespace != null && t.Namespace.EndsWith(".Parts")).Export().ExportInterfaces();
return conventionBuilder;
}
IExtension.cs:
public interface IExtension
{
void DoWork();
}
ExtensionMetadata.cs:
public class ExtensionMetadata
{
public int AccountID { get; set; }
}
ExtensionA.cs (same as ExtensionB.cs):
public void DoWork()
{
System.Diagnostics.Debug.WriteLine("ExtensionA doing work..");
}
ExtensionManager.cs:
public class ExtensionManager
{
private IEnumerable<ExportFactory<IExtension, ExtensionMetadata>> _extensions;
public ExtensionManager(IEnumerable<ExportFactory<IExtension, ExtensionMetadata>> extensions)
{
_extensions = extensions;
}
public void DoWork(int accountID)
{
foreach (var extension in _extensions)
{
if (extension.Metadata.AccountID == accountID)
{
extension.DoWork();
}
}
}
}
I think I'm missing something quite major here. Basically I want to lazily import all Extensions, check their metadata and if a condition is fulfilled have that extension do something.
Would really appreciate your feedback or any links to sample code / tutorials that cover my scenario.
Many thanks!
I think I've worked it out after reading this SO question.
I created a Metadata Attribute:
[MetadataAttribute]
public class ExtensionMetadataAttribute : ExportAttribute, IExtensionMetadata
{
public int AccountID { get; set; }
public ExtensionMetadataAttribute(int accountID) : base(typeof (IExtension))
{
AccountID = accountID;
}
}
Then modified ExtensionA.cs:
[ExtensionMetadata(1)]
public class ExtensionA : IExtension
{
public void DoWork()
{
System.Diagnostics.Debug.WriteLine("ExtensionA doing work..");
}
}
And now ExtensionManager.cs looks like this:
public class ExtensionManager : IExtensionManager
{
private readonly IEnumerable<ExportFactory<IExtension, ExtensionMetadata>> _extensions;
public ExtensionManager(IEnumerable<ExportFactory<IExtension, ExtensionMetadata>> extensions)
{
_extensions = extensions;
}
public void DoWork(int accountID)
{
foreach (var extension in _extensions)
{
if (extension.Metadata.AccountID == accountID)
{
using (var foo = extension.CreateExport())
{
foo.Value.DoWork();
}
}
}
}
}
This seems to do the trick, but I would still be interested in any feedback re best practices, performance issues etc.
Thanks!

MEF: Unable to import in other classes?

Edit: Matt, that does indeed solves some (most) of my problems, thank you. Now the only lingering issue of how do I do this in WPF? I have a custom part based off of a UserControl but there is no way in WPF to do :
[Import]<my:SomeCustomControl>
so the cascade doesn't work in this instance.
/Edit
I am having an issue [Import]ing various MEF components in my project. Do I have to use a CompositionContainer in every class I use? In the code below, a null reference exception is thrown in the method Helper.TimesTwo() but when I call logger.Log() in the Program class, everything works. Any help would be greatly appreciated.
(this will compile and run as a console app).
using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
var p = new Program();
p.Run();
}
[Import]
private ILog logger { get; set; }
public void Run()
{
var catalog = new DirectoryCatalog(".");
var container = new CompositionContainer(catalog);
var batch = new CompositionBatch();
batch.AddPart(this);
container.Compose(batch);
logger.Log("hello");
var h = new Helper();
logger.Log(h.TimesTwo(15).ToString());
Console.ReadKey();
}
}
class Helper
{
[Import]
private IDouble doubler { get; set; }
private Helper()
{
// do I have to do all the work with CompositionContainer here again?
}
public double TimesTwo(double d)
{
return doubler.DoubleIt(d);
}
}
interface ILog
{
void Log(string message);
}
[Export(typeof(ILog))]
class MyLog : ILog
{
public void Log(string message)
{
Console.WriteLine("mylog: " + message);
}
}
interface IDouble
{
double DoubleIt(double d);
}
[Export(typeof(IDouble))]
class MyDoubler : IDouble
{
public double DoubleIt(double d)
{
return d * 2.0;
}
}
}
I think the trick is to make use of the fact that MEF will cascade its imports. So if you import your Helper instance rather than declaring it as a local variable, any imports that the Helper requires will be satisfied.
[Import]
public Helper MyHelper { get; set; }
public void Run()
{
var catalog = new DirectoryCatalog(".");
var container = new CompositionContainer(catalog);
var batch = new CompositionBatch();
batch.AddPart(this);
container.Compose(batch);
logger.Log("hello");
logger.Log(MyHelper.TimesTwo(15).ToString());
Console.ReadKey();
}
I'm sure there's a way to have it satisfy any imports in a local variable, but I like using the "cascaded imports" feature like that.
No you can't do that. You could look into using attached properties though for this. With an attached property you can have the container compose the element that the attached property is added to. Another option would be markup extensions.
Glenn
Try changing
[Import]
private ILog logger { get; set; }
to
[Import]
public ILog logger { get; set; }
It might work.

Categories

Resources