使用具有依赖注入的策略和工厂模式

Using a Strategy and Factory Pattern with Dependency Injection

我正在做一个业余项目,以更好地理解控制反转和依赖注入以及不同的设计模式。

我想知道是否有将 DI 与工厂和策略模式结合使用的最佳实践

当一个策略(从工厂构建)需要为每个可能的构造函数和实现使用不同的参数时,我的挑战就来了。结果,我发现自己在服务入口点声明了所有可能的接口,并通过应用程序向下传递它们。因此,必须为新的和各种策略 class 实现更改入口点。

为了便于说明,我整理了一个配对的示例。我的这个项目堆栈是 .NET 4.5/C# 和 Unity for IoC/DI.

在这个示例应用程序中,我添加了一个默认程序 class,它负责接受一个虚构的订单,并根据订单属性和选择的运输提供商计算运输成本。 UPS、DHL 和 Fedex 有不同的计算方式,每个实现可能依赖也可能不依赖其他服务(访问数据库、api 等)。

public class Order
{
    public string ShippingMethod { get; set; }
    public int OrderTotal { get; set; }
    public int OrderWeight { get; set; }
    public int OrderZipCode { get; set; }
}

计算运费的虚构程序或服务

public class Program
{
    // register the interfaces with DI container in a separate config class (Unity in this case)
    private readonly IShippingStrategyFactory _shippingStrategyFactory;

    public Program(IShippingStrategyFactory shippingStrategyFactory)
    {
        _shippingStrategyFactory = shippingStrategyFactory;
    }

    public int DoTheWork(Order order)
    {
        // assign properties just as an example
        order.ShippingMethod = "Fedex";
        order.OrderTotal = 90;
        order.OrderWeight = 12;
        order.OrderZipCode = 98109;

        IShippingStrategy shippingStrategy = _shippingStrategyFactory.GetShippingStrategy(order);
        int shippingCost = shippingStrategy.CalculateShippingCost(order);

        return shippingCost;
    }
}

// Unity DI Setup
public class UnityConfig
{
    var container = new UnityContainer();
    container.RegisterType<IShippingStrategyFactory, ShippingStrategyFactory>();
    // also register  IWeightMappingService and IZipCodePriceCalculator with implementations
}

public interface IShippingStrategyFactory
{
    IShippingStrategy GetShippingStrategy(Order order);
}

public class ShippingStrategyFactory : IShippingStrategyFactory
{
    public IShippingStrategy GetShippingStrategy(Order order)
    {
        switch (order.ShippingMethod)
        {
            case "UPS":
                return new UPSShippingStrategy();

            // The issue is that some strategies require additional parameters for the constructor
            // SHould the be resolved at the entry point (the Program class) and passed down?
            case "DHL":
                return new DHLShippingStrategy();

            case "Fedex":
                return new FedexShippingStrategy();

            default:
                throw new NotImplementedException(); 
        }
    }
}

现在介绍策略接口和实现。UPS 很容易计算,而 DHL 和 Fedex 可能需要不同的服务(和不同的构造函数参数)。

public interface IShippingStrategy
{
    int CalculateShippingCost(Order order);
}

public class UPSShippingStrategy : IShippingStrategy()
{
    public int CalculateShippingCost(Order order)
    {
        if (order.OrderWeight < 5)
            return 10; // flat rate of  for packages under 5 lbs
        else
            return 20; // flat rate of 
    }
}

public class DHLShippingStrategy : IShippingStrategy()
{
    private readonly IWeightMappingService _weightMappingService;

    public DHLShippingStrategy(IWeightMappingService weightMappingService)
    {
        _weightMappingService = weightMappingService;
    }

    public int CalculateShippingCost(Order order)
    {
        // some sort of database call needed to lookup pricing table and weight mappings
        return _weightMappingService.DeterminePrice(order);
    }
}

public class FedexShippingStrategy : IShippingStrategy()
{
    private readonly IZipCodePriceCalculator _zipCodePriceCalculator;

    public FedexShippingStrategy(IZipCodePriceCalculator zipCodePriceCalculator)
    {
        _zipCodePriceCalculator = zipCodePriceCalculator;
    }

    public int CalculateShippingCost(Order order)
    {
        // some sort of dynamic pricing based on zipcode
        // api call to a Fedex service to return dynamic price
        return _zipCodePriceService.CacluateShippingCost(order.OrderZipCode);
    }
}

上面的问题是每个策略都需要额外的和不同的服务来执行 'CalculateShippingCost' 方法。这些 interfaces/implementations 是否需要注册到入口点(程序 class)并通过构造函数向下传递?

是否有其他模式更适合完成上述场景?也许 Unity 可以专门处理某些事情 (https://msdn.microsoft.com/en-us/library/dn178463(v=pandp.30).aspx)?

我非常感谢在正确方向上的任何帮助或推动。

谢谢, 安迪

使用您的策略类型字符串注册并解析它们。

像这样:

// Create container and register types
IUnityContainer myContainer = new UnityContainer();
myContainer.RegisterType<IShippingStrategy, FedexShippingStrategy>("Fedex");
myContainer.RegisterType<IShippingStrategy, DHLShippingStrategy>("DHL");

// Retrieve an instance of each type
IShippingStrategy shipping = myContainer.Resolve<IShippingStrategy>("DHL");
IShippingStrategy shipping = myContainer.Resolve<IShippingStrategy>("Fedex");

有几种方法可以做到这一点,但我更喜欢的方法是将可用策略列表注入您的工厂,然后将它们过滤为 return 您感兴趣的策略英寸

使用您的示例,我将修改 IShippingStrategy 以添加新的 属性:

public interface IShippingStrategy
{
    int CalculateShippingCost(Order order);
    string SupportedShippingMethod { get; }
}

然后我会像这样实现工厂:

public class ShippingStrategyFactory : IShippingStrategyFactory
{
    private readonly IEnumerable<IShippingStrategy> availableStrategies;

    public ShippingStrategyFactory(IEnumerable<IShippingStrategy> availableStrategies)
    {
        this.availableStrategies = availableStrategies;
    }

    public IShippingStrategy GetShippingStrategy(Order order)
    {
        var supportedStrategy = availableStrategies
                .FirstOrDefault(x => x.SupportedShippingMethod == order.ShippingMethod);
        if (supportedStrategy == null)
        {
            throw new InvalidOperationException($"No supported strategy found for shipping method '{order.ShippingMethod}'.");
        }

        return supportedStrategy;
    }
}

我喜欢这样使用它的主要原因是我永远不必回来修改工厂。如果我必须实施新策略,则不必更改工厂。如果您在容器中使用自动注册,您甚至不必注册新策略,因此这只是一个允许您花更多时间编写新代码的案例。

请参阅 John H 和 Silas Reinagel 的回答。他们都非常有帮助。

我最终结合了这两个答案。

我更新了 John H 提到的工厂和界面。

然后在 Unity 容器中,我添加了带有新命名参数的实现,如 Silas Reinagel 所示。

然后我按照这里的答案使用 Unity 注册要注入策略工厂的集合。 Way to fill collection with Unity

现在每个策略都可以单独实现,不需要修改上游。

谢谢大家

在应用依赖注入时,您将所有 class 的依赖项定义为构造函数中的必需参数。这种做法叫做Constructor Injection。这将创建依赖关系的负担从 class 推给了它的消费者。然而,同样的规则也适用于 class 的消费者。他们还需要在构造函数中定义它们的依赖关系。这一直沿着调用堆栈向上移动,这意味着所谓的 'object graphs' 在某些点上可能变得非常深。

依赖注入负责创建 classes 一直到应用程序的入口点; Composition Root. This does mean however that the entry point needs to know about all the dependencies. In case you use DI without a DI Container—a practice called Pure DI——这意味着此时所有依赖项都必须在普通的旧 C# 代码中创建。如果您使用 DI 容器,您仍然必须将所有依赖项告知 DI 容器。

然而,有时您可以使用一种称为批处理或 Auto-Registration 的技术,其中 DI 容器将对您的项目使用反射并使用 Convention over Configuration. This saves you the burden of registering all types one by one and often prevents you from making changes to the Composition Root 注册类型每次向系统添加新的class。

Do these interfaces/implementations need to be registered with the entry point (the Program class) and passed down through the constructors?

绝对。

As a result I find myself declaring all possible interfaces in the service entry point, and passing them down through the application. As a result, the entry point must be changed for new and various strategy class implementations.

应用程序的入口点是系统中最不稳定的部分(由 Stable-Dependencies Principle 暗示)。它总是,即使没有 DI。但是有了 DI,你可以让系统的其余部分变得不那么不稳定。同样,您可以通过应用 Auto-Registration.

来减少需要在入口点进行的代码更改量

I am wondering if there are best practices to using DI with the factory and strategy patterns?

我想说关于工厂的最佳做法是尽可能少地拥有工厂,如 this article 中所述。事实上,您的工厂接口是多余的,只会使需要它的消费者变得复杂(如文章中所述)。您的应用程序可以很容易地不用,您可以直接注入 IShippingStrategy,因为这是消费者唯一感兴趣的事情:获取订单的运费。它不关心背后是一个还是几十个实现。它只是想获得运费并继续其工作:

public int DoTheWork(Order order)
{
    // assign properties just as an example
    order.ShippingMethod = "Fedex";
    order.OrderTotal = 90;
    order.OrderWeight = 12;
    order.OrderZipCode = 98109;

    return shippingStrategy.CalculateShippingCost(order);
}

然而,这意味着注入的运输策略现在必须是可以根据 Order.Method 属性 决定如何计算成本的东西。但是有一种称为代理模式的模式。这是一个例子:

public class ShippingStrategyProxy : IShippingStrategy
{
    private readonly DHLShippingStrategy _dhl;
    private readonly UPSShippingStrategy _ups;
    //...

    public ShippingStrategyProxy(
        DHLShippingStrategy dhl, UPSShippingStrategy ups, ...)
    {
        _dhl = dhl;
        _ups = ups;
        //...
    }

    public int CalculateShippingCost(Order order) => 
        GetStrategy(order.Method).CalculateShippingCost(order);
    
    private IShippingStrategy GetStrategy(string method)
    {
        switch (method)
        {
            case "DHL": return dhl;
            case "UPS": return ups:
            //...
            default: throw InvalidOperationException(method);
        }
    }
}

这个代理在内部有点像工厂,但这里有两个重要的区别:

  1. 它没有定义不同的接口。这允许消费者仅依赖于 1 个概念:IShippingStrategy.
  2. 它本身不创建策略;他们仍然被注入其中。

此代理只是将传入调用转发给执行实际工作的基础策略实现。

有多种方法可以实现这种代理。例如,您仍然可以在此处手动创建依赖项 - 或者您可以将调用转发给容器,容器将为您创建依赖项。此外,根据最适合您的应用程序的方式,注入依赖项的方式也会有所不同。

尽管这样的代理可能在内部像工厂一样工作,但重要的是这里没有工厂抽象;那只会让消费者复杂化。

上面讨论的所有内容都在我和 Mark Seemann 的书 Dependency Injection Principles, Practices, and Patterns 中进行了更详细的讨论。例如:

  • Composition Root 在§ 4.1 中讨论,
  • 第 4.2 节中的构造函数注入
  • 滥用第 6.2 节中的抽象工厂,以及
  • 第 12 章中的自动注册

所以我就这样做了。我更愿意注入一个 IDictionary,但由于将 "IEnumerable" 注入构造函数的限制(此限制是 Unity 特定的),我想出了一个小的解决方法。

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<string, IShipper> shippers; /*   :(    I couldn't get IDictionary<string, IShipper>  to work */
    IEnumerable<IShipper> shippers;

    public OrderProcessor(Common.Logging.ILog lgr, IEnumerable<IShipper> 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;
    }
}

...

调用代码:(例如 "Program.cs")

            Common.Logging.ILog log = Common.Logging.LogManager.GetLogger(typeof(Program));

            IUnityContainer cont = new UnityContainer();

            cont.RegisterInstance<ILog>(log);

            cont.RegisterType<IShipper, FedExShipper>(FedExShipper.FriendlyName);
            cont.RegisterType<IShipper, UspsShipper>(UspsShipper.FriendlyName);
            cont.RegisterType<IShipper, UpsShipper>(UpsShipper.FriendlyName);

            cont.RegisterType<IOrderProcessor, OrderProcessor>();

            Order ord = new Order();
            IOrderProcessor iop = cont.Resolve<IOrderProcessor>();
            iop.ProcessOrder(FedExShipper.FriendlyName, ord);

记录输出:

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

因此每个具体都有一个静态字符串以强类型方式提供其名称。 ("FriendlyName")

然后我有一个实例 string-get 属性,它使用完全相同的值来保持同步。 ("FriendlyNameInstance")

通过在界面上使用 属性 强制问题(部分代码下方)

public interface IShipper
{
   string FriendlyNameInstance { get;}
}

我可以用它来 "find" 我的托运人从托运人的集合中。

内部方法 "FindIShipper" 是有点工厂,但不需要有单独的 IShipperFactory 和 ShipperFactory 接口和 class。从而简化了整体设置。并且仍然尊重构造函数注入和 Composition root.

如果有人知道如何使用IDictionary<string, IShipper>(并通过构造函数注入),请告诉我。

但我的解决方案有效...有点令人眼花缭乱。

............................

我的第三方dll依赖列表。 (我正在使用 dotnet 核心,但带有半新版本 Unity 的 dotnet 框架也应该可以工作)。 (请参阅下面的 PackageReference)

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Common.Logging" Version="3.4.1" />
    <PackageReference Include="Microsoft.Extensions.Configuration" Version="2.1.1" />
    <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="2.1.1" />
    <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.1.1" />
    <PackageReference Include="Unity" Version="5.8.11" />
  </ItemGroup>

  <ItemGroup>
    <None Update="appsettings.json">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </None>
  </ItemGroup>

</Project>

追加:

这里是 autofac 版本:

(使用上面所有相同的接口和具体内容)

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<IOrderProcessor>();
                    processor.ProcessOrder(FedExShipper.FriendlyName, new Order());

                    Microsoft.Extensions.Logging.ILogger loggerFromIoc = servicesProvider.GetService<ILoggerFactory>()
                    .CreateLogger<Program>();
                    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<IConfiguration>(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<FedExShipper>().Keyed<IShipper>(FedExShipper.FriendlyName);
                ////builder.RegisterType<UpsShipper>().Keyed<IShipper>(UpsShipper.FriendlyName);
                ////builder.RegisterType<UspsShipper>().Keyed<IShipper>(UspsShipper.FriendlyName);

                builder.RegisterType<FedExShipper>().As<IShipper>();
                builder.RegisterType<UpsShipper>().As<IShipper>();
                builder.RegisterType<UspsShipper>().As<IShipper>();
                builder.RegisterType<OrderProcessor>().As<IOrderProcessor>();
            }
            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(设置为始终复制)

{}

.......

autofac.json(设置为始终复制)

{
  "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"
        }
      ]
    }
  ]
}

和 csproj

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Autofac" Version="5.1.2" />
    <PackageReference Include="Autofac.Configuration" Version="5.1.0" />
    <PackageReference Include="Autofac.Extensions.DependencyInjection" Version="6.0.0" />
    <PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="3.1.2" />
    <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.2" />
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.2" />
    <PackageReference Include="Microsoft.Extensions.Http" Version="3.1.2" />
    <PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.2" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.1.2" />
    <PackageReference Include="NLog.Extensions.Logging" Version="1.6.1" />
  </ItemGroup>

来自

https://autofaccn.readthedocs.io/en/latest/integration/netcore.html

PS

在 autofac 版本中,我不得不将注入的 Logger 更改为 LoggerFactory。

这是 OrderProcessor 替代版本。您也将对所有 3 个混凝土 "Shipper" 执行相同的 "Microsoft.Extensions.Logging.ILoggerFactory loggerFactory" 替代注入。

namespace MyCompany.MyProject.Processors
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Microsoft.Extensions.Logging;
    public class OrderProcessor : IOrderProcessor
    {
        ////private readonly IDictionary<string, IShipper> shippers; /*   :(    I couldn't get IDictionary<string, IShipper>  to work */
        private readonly IEnumerable<IShipper> shippers;
        private Microsoft.Extensions.Logging.ILogger<OrderProcessor> logger;

        public OrderProcessor(Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, IEnumerable<IShipper> shprs)
        {
            if (null == loggerFactory)
            {
                throw new ArgumentOutOfRangeException("loggerFactory is null");
            }

            if (null == shprs)
            {
                throw new ArgumentOutOfRangeException("ShipperInterface(s) is null");
            }

            this.logger = loggerFactory.CreateLogger<OrderProcessor>();
            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;
        }
    }
}

与autofac无关

nlog.config(设置为始终复制)

<?xml version="1.0" encoding="utf-8" ?>
<!-- XSD manual extracted from package NLog.Schema: https://www.nuget.org/packages/NLog.Schema-->
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xsi:schemaLocation="NLog NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      autoReload="true"
      internalLogFile="MyCompany.MyProject.Nlog.internalLogFile.log"
      internalLogLevel="Info" >

  <!-- the targets to write to -->
  <targets>
    <!-- write logs to file -->
    <target xsi:type="File" name="target1" fileName="MyCompany.MyProject.Nlog.MyConsoleAppProgram.log"
            layout="${date}|${level:uppercase=true}|${message} ${exception}|${logger}|${all-event-properties}" />
    <target xsi:type="Console" name="target2"
            layout="${date}|${level:uppercase=true}|${message} ${exception}|${logger}|${all-event-properties}" />
  </targets>

  <!-- rules to map from logger name to target -->
  <rules>
    <logger name="*" minlevel="Trace" writeTo="target1,target2" />
  </rules>
</nlog>