I am working on a side project to better understand Inversion of Control and Dependency Injection and different design patterns.
I am wondering if there are
So I did it like this. I would have preferred to have injected an IDictionary, but because of the limitation with injecting "IEnumerable" into the constructor (this limitation is Unity specific), I came up with a little workaround.
public interface IShipper
{
void ShipOrder(Order ord);
string FriendlyNameInstance { get;} /* here for my "trick" */
}
..
public interface IOrderProcessor
{
void ProcessOrder(String preferredShipperAbbreviation, Order ord);
}
..
public class Order
{
}
..
public class FedExShipper : IShipper
{
private readonly Common.Logging.ILog logger;
public static readonly string FriendlyName = typeof(FedExShipper).FullName; /* here for my "trick" */
public FedExShipper(Common.Logging.ILog lgr)
{
if (null == lgr)
{
throw new ArgumentOutOfRangeException("Log is null");
}
this.logger = lgr;
}
public string FriendlyNameInstance => FriendlyName; /* here for my "trick" */
public void ShipOrder(Order ord)
{
this.logger.Info("I'm shipping the Order with FedEx");
}
..
public class UpsShipper : IShipper
{
private readonly Common.Logging.ILog logger;
public static readonly string FriendlyName = typeof(UpsShipper).FullName; /* here for my "trick" */
public UpsShipper(Common.Logging.ILog lgr)
{
if (null == lgr)
{
throw new ArgumentOutOfRangeException("Log is null");
}
this.logger = lgr;
}
public string FriendlyNameInstance => FriendlyName; /* here for my "trick" */
public void ShipOrder(Order ord)
{
this.logger.Info("I'm shipping the Order with Ups");
}
}
..
public class UspsShipper : IShipper
{
private readonly Common.Logging.ILog logger;
public static readonly string FriendlyName = typeof(UspsShipper).FullName; /* here for my "trick" */
public UspsShipper(Common.Logging.ILog lgr)
{
if (null == lgr)
{
throw new ArgumentOutOfRangeException("Log is null");
}
this.logger = lgr;
}
public string FriendlyNameInstance => FriendlyName; /* here for my "trick" */
public void ShipOrder(Order ord)
{
this.logger.Info("I'm shipping the Order with Usps");
}
}
..
public class OrderProcessor : IOrderProcessor
{
private Common.Logging.ILog logger;
//IDictionary shippers; /* :( I couldn't get IDictionary to work */
IEnumerable shippers;
public OrderProcessor(Common.Logging.ILog lgr, IEnumerable shprs)
{
if (null == lgr)
{
throw new ArgumentOutOfRangeException("Log is null");
}
if (null == shprs)
{
throw new ArgumentOutOfRangeException("ShipperInterface(s) is null");
}
this.logger = lgr;
this.shippers = shprs;
}
public void ProcessOrder(String preferredShipperAbbreviation, Order ord)
{
this.logger.Info(String.Format("About to ship. ({0})", preferredShipperAbbreviation));
/* below foreach is not needed, just "proves" everything was injected */
foreach (IShipper sh in shippers)
{
this.logger.Info(String.Format("ShipperInterface . ({0})", sh.GetType().Name));
}
IShipper foundShipper = this.FindIShipper(preferredShipperAbbreviation);
foundShipper.ShipOrder(ord);
}
private IShipper FindIShipper(String preferredShipperAbbreviation)
{
IShipper foundShipper = this.shippers.FirstOrDefault(s => s.FriendlyNameInstance.Equals(preferredShipperAbbreviation, StringComparison.OrdinalIgnoreCase));
if (null == foundShipper)
{
throw new ArgumentNullException(
String.Format("ShipperInterface not found in shipperProviderMap. ('{0}')", preferredShipperAbbreviation));
}
return foundShipper;
}
}
...
And calling code: (that would be in something like "Program.cs" for example)
Common.Logging.ILog log = Common.Logging.LogManager.GetLogger(typeof(Program));
IUnityContainer cont = new UnityContainer();
cont.RegisterInstance(log);
cont.RegisterType(FedExShipper.FriendlyName);
cont.RegisterType(UspsShipper.FriendlyName);
cont.RegisterType(UpsShipper.FriendlyName);
cont.RegisterType();
Order ord = new Order();
IOrderProcessor iop = cont.Resolve();
iop.ProcessOrder(FedExShipper.FriendlyName, ord);
Logging Output:
2018/09/21 08:13:40:556 [INFO] MyNamespace.Program - About to ship. (MyNamespace.Bal.Shippers.FedExShipper)
2018/09/21 08:13:40:571 [INFO] MyNamespace.Program - ShipperInterface . (FedExShipper)
2018/09/21 08:13:40:572 [INFO] MyNamespace.Program - ShipperInterface . (UspsShipper)
2018/09/21 08:13:40:572 [INFO] MyNamespace.Program - ShipperInterface . (UpsShipper)
2018/09/21 08:13:40:573 [INFO] MyNamespace.Program - I'm shipping the Order with FedEx
So each concrete has a static string providing its name in a strong-typed fashion. ("FriendlyName")
And then I have an instance string-get property which uses the exact same value to keep things in sync. ("FriendlyNameInstance")
By forcing the issue by using a property on the Interface (below partial code)
public interface IShipper
{
string FriendlyNameInstance { get;}
}
I can use this to "find" my shipper out of the collection of shippers.
The internal method "FindIShipper" is the kinda-factory, but removes the need to have a separate IShipperFactory and ShipperFactory interface and class. Thus simplifying the overall setup. And still honors Constructor-Injection and Composition root.
If anyone knows how to use IDictionary
(and inject via the constructor), please let me know.
But my solution works...with a little razzle dazzle.
...........................
My third-party-dll dependency list. (I'm using dotnet core, but dotnet framework with a semi new version of Unity should work too). (See PackageReference's below)
Exe
netcoreapp2.0
Always
APPEND:
Here is the autofac version:
(using all the same interfaces and concretes above )
Program.cs
namespace MyCompany.ProofOfConcepts.AutofacStrategyPatternExample.DemoCommandLineInterfaceOne
{
using System;
using System.Text;
using Autofac;
using Autofac.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
/* need usings for all the object above */
using MyCompany.ProofOfConcepts.AutofacStrategyPatternExample.Domain;
using NLog;
using NLog.Extensions.Logging;
public class Program
{
private static Logger programStaticLoggerThatNeedsToBeInitiatedInMainMethod = null;
public static int Main(string[] args)
{
Logger loggerFromNLogLogManagerGetCurrentClassLogger = NLog.LogManager.GetCurrentClassLogger(); /* if this is made a private-static, it does not log the entries */
programStaticLoggerThatNeedsToBeInitiatedInMainMethod = loggerFromNLogLogManagerGetCurrentClassLogger;
programStaticLoggerThatNeedsToBeInitiatedInMainMethod.Info("programStaticLoggerThatNeedsToBeInitiatedInMainMethod: Main.Start");
try
{
bool useCodeButNotAutofacJson = true; /* true will "code up" the DI in c# code, false will kick in the autofac.json */
string autoFacFileName = useCodeButNotAutofacJson ? "autofac.Empty.json" : "autofac.json"; /* use "empty" to prove the DI is not coming from non-empty json */
programStaticLoggerThatNeedsToBeInitiatedInMainMethod.Info(string.Format("programStaticLoggerThatNeedsToBeInitiatedInMainMethod: autoFacFileName={0}", autoFacFileName));
IConfiguration config = new ConfigurationBuilder()
.SetBasePath(System.IO.Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile(autoFacFileName)
.Build();
IServiceProvider servicesProvider = BuildDi(config, useCodeButNotAutofacJson);
using (servicesProvider as IDisposable)
{
IOrderProcessor processor = servicesProvider.GetRequiredService();
processor.ProcessOrder(FedExShipper.FriendlyName, new Order());
Microsoft.Extensions.Logging.ILogger loggerFromIoc = servicesProvider.GetService()
.CreateLogger();
loggerFromIoc.LogInformation("loggerFromIoc:Starting application");
loggerFromIoc.LogInformation("loggerFromIoc:All done!");
Console.WriteLine("Press ANY key to exit");
Console.ReadLine();
}
}
catch (Exception ex)
{
Console.WriteLine(GenerateFullFlatMessage(ex));
//// NLog: catch any exception and log it.
programStaticLoggerThatNeedsToBeInitiatedInMainMethod.Error(ex, "programStaticLoggerThatNeedsToBeInitiatedInMainMethod : Stopped program because of exception");
throw;
}
finally
{
// Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux)
LogManager.Shutdown();
}
Console.WriteLine("Returning 0 and exiting.");
return 0;
}
private static IServiceProvider BuildDi(IConfiguration config, bool useCodeButNotAutofacJson)
{
NLog.Extensions.Logging.NLogProviderOptions nlpopts = new NLog.Extensions.Logging.NLogProviderOptions
{
IgnoreEmptyEventId = true,
CaptureMessageTemplates = true,
CaptureMessageProperties = true,
ParseMessageTemplates = true,
IncludeScopes = true,
ShutdownOnDispose = true
};
IServiceCollection sc = new ServiceCollection()
////.AddLogging(loggingBuilder =>
////{
//// // configure Logging with NLog
//// loggingBuilder.ClearProviders();
//// loggingBuilder.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
//// loggingBuilder.AddNLog(config);
////})
.AddLogging(loggingBuilder =>
{
////use nlog
loggingBuilder.AddNLog(nlpopts);
NLog.LogManager.LoadConfiguration("nlog.config");
})
.AddSingleton(config);
//// // /* before autofac */ return sc.BuildServiceProvider();
//// Create a container-builder and register dependencies
Autofac.ContainerBuilder builder = new Autofac.ContainerBuilder();
// Populate the service-descriptors added to `IServiceCollection`
// BEFORE you add things to Autofac so that the Autofac
// registrations can override stuff in the `IServiceCollection`
// as needed
builder.Populate(sc);
if (useCodeButNotAutofacJson)
{
programStaticLoggerThatNeedsToBeInitiatedInMainMethod.Info("Coding up Autofac DI");
/* "Keyed" is not working, do not use below */
////builder.RegisterType().Keyed(FedExShipper.FriendlyName);
////builder.RegisterType().Keyed(UpsShipper.FriendlyName);
////builder.RegisterType().Keyed(UspsShipper.FriendlyName);
builder.RegisterType().As();
builder.RegisterType().As();
builder.RegisterType().As();
builder.RegisterType().As();
}
else
{
programStaticLoggerThatNeedsToBeInitiatedInMainMethod.Info("Using .json file to define Autofac DI");
// Register the ConfigurationModule with Autofac.
var module = new Autofac.Configuration.ConfigurationModule(config);
builder.RegisterModule(module);
}
Autofac.IContainer autofacContainer = builder.Build();
// this will be used as the service-provider for the application!
return new AutofacServiceProvider(autofacContainer);
}
private static string GenerateFullFlatMessage(Exception ex)
{
return GenerateFullFlatMessage(ex, false);
}
private static string GenerateFullFlatMessage(Exception ex, bool showStackTrace)
{
string returnValue;
StringBuilder sb = new StringBuilder();
Exception nestedEx = ex;
while (nestedEx != null)
{
if (!string.IsNullOrEmpty(nestedEx.Message))
{
sb.Append(nestedEx.Message + System.Environment.NewLine);
}
if (showStackTrace && !string.IsNullOrEmpty(nestedEx.StackTrace))
{
sb.Append(nestedEx.StackTrace + System.Environment.NewLine);
}
if (ex is AggregateException)
{
AggregateException ae = ex as AggregateException;
foreach (Exception flatEx in ae.Flatten().InnerExceptions)
{
if (!string.IsNullOrEmpty(flatEx.Message))
{
sb.Append(flatEx.Message + System.Environment.NewLine);
}
if (showStackTrace && !string.IsNullOrEmpty(flatEx.StackTrace))
{
sb.Append(flatEx.StackTrace + System.Environment.NewLine);
}
}
}
nestedEx = nestedEx.InnerException;
}
returnValue = sb.ToString();
return returnValue;
}
}
}
........
autofac.Empty.json (set to copy always)
{}
.......
autofac.json (set to copy always)
{
"defaultAssembly": "MyCompany.MyProject",
"components": [
{
"type": "MyCompany.MyProject.Shippers.FedExShipper",
"services": [
{
"type": "MyCompany.MyProject.Shippers.Interfaces.IShipper"
}
]
},
{
"type": "MyCompany.MyProject.Shippers.UpsShipper",
"services": [
{
"type": "MyCompany.MyProject.Shippers.Interfaces.IShipper"
}
]
},
{
"type": "MyCompany.MyProject.Shippers.UspsShipper",
"services": [
{
"type": "MyCompany.MyProject.Shippers.Interfaces.IShipper"
}
]
},
{
"type": "MyCompany.MyProject.Processors.OrderProcessor",
"services": [
{
"type": "MyCompany.MyProject.Processors.Interfaces.IOrderProcessor"
}
]
}
]
}
and csproj
Exe
netcoreapp3.1
From
https://autofaccn.readthedocs.io/en/latest/integration/netcore.html
PS
In the autofac version I had to change the Logger being injected to be a LoggerFactory.
Here is the OrderProcessor alternate version. You'll have do the same "Microsoft.Extensions.Logging.ILoggerFactory loggerFactory" alternative injection for all 3 concrete "Shipper"'s as well.
namespace MyCompany.MyProject.Processors
{
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
public class OrderProcessor : IOrderProcessor
{
////private readonly IDictionary shippers; /* :( I couldn't get IDictionary to work */
private readonly IEnumerable shippers;
private Microsoft.Extensions.Logging.ILogger logger;
public OrderProcessor(Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, IEnumerable shprs)
{
if (null == loggerFactory)
{
throw new ArgumentOutOfRangeException("loggerFactory is null");
}
if (null == shprs)
{
throw new ArgumentOutOfRangeException("ShipperInterface(s) is null");
}
this.logger = loggerFactory.CreateLogger();
this.shippers = shprs;
}
public void ProcessOrder(string preferredShipperAbbreviation, Order ord)
{
this.logger.LogInformation(string.Format("About to ship. ({0})", preferredShipperAbbreviation));
/* below foreach is not needed, just "proves" everything was injected */
int counter = 0;
foreach (IShipper sh in this.shippers)
{
this.logger.LogInformation(string.Format("IEnumerable:ShipperInterface. ({0} of {1}) -> ({2})", ++counter, this.shippers.Count(), sh.GetType().Name));
}
IShipper foundShipper = this.FindIShipper(preferredShipperAbbreviation);
foundShipper.ShipOrder(ord);
}
private IShipper FindIShipper(string preferredShipperAbbreviation)
{
IShipper foundShipper = this.shippers.FirstOrDefault(s => s.FriendlyNameInstance.Equals(preferredShipperAbbreviation, StringComparison.OrdinalIgnoreCase));
if (null == foundShipper)
{
throw new ArgumentNullException(
string.Format("ShipperInterface not found in shipperProviderMap. ('{0}')", preferredShipperAbbreviation));
}
return foundShipper;
}
}
}
not related to autofac
nlog.config (set to copy always)