【问题标题】:DryIOC Container configuration for property injection属性注入的 DryIOC 容器配置
【发布时间】:2016-05-13 03:00:55
【问题描述】:

我已经广泛搜索了一个关于如何配置 DryIoc 容器以像注入构造函数参数一样简单地将依赖项作为属性注入的简单示例。

鉴于以下工作示例...

容器注册:

    public static void Register(HttpConfiguration config)
    {
        var c = new Container().WithWebApi(config);

        c.Register<IWidgetService, WidgetService>(Reuse.Singleton);
        c.Register<IWidgetRepository, WidgetRepository>(Reuse.Singleton);
    }

小部件服务:

public class WidgetService : IWidgetService
{
    private readonly IWidgetRepository _widgetRepository;

    public WidgetService(IWidgetRepository widgetRepository)
    {
        _widgetRepository = widgetRepository;
    }

    public IList<Widget> GetWidgets()
    {
        return _widgetRepository.GetWidgets().ToList();
    }
}

小部件存储库:

public class WidgetRepository : IWidgetRepository
{
    private readonly IList<Widget> _widgets;

    public WidgetRepository()
    {
        _widgets = new List<Widget>
        {
            new Widget {Name = "Widget 1", Cost = new decimal(1.99), Description = "The first widget"},
            new Widget {Name = "Widget 2", Cost = new decimal(2.99), Description = "The second widget"}
        };
    }

    public IEnumerable<Widget> GetWidgets()
    {
        return _widgets.AsEnumerable();
    }
}

需要如何更改配置以支持如下所示的 WidgetService,其中 DryIoc 将 WidgetRepository 作为属性注入?

所需的小部件服务:

public class WidgetService : IWidgetService
{
    public IWidgetRepository WidgetRepository { get; set; }

    public IList<Widget> GetWidgets()
    {
        return WidgetRepository.GetWidgets().ToList();
    }
}

失败的尝试

我已经尝试过这些配置更改,但它们似乎对在 WidgetService 上启用属性注入没有任何影响。

尝试 1:

public static void Register(HttpConfiguration config)
    {
        var c = new Container().WithWebApi(config);

        // Seems logical - no luck
        c.InjectPropertiesAndFields(PropertiesAndFields.Auto);

        c.Register<IWidgetService, WidgetService>(Reuse.Singleton);
        c.Register<IWidgetRepository, WidgetRepository>(Reuse.Singleton);
    }

尝试 2:

    public static void Register(HttpConfiguration config)
    {
        var c = new Container(Rules.Default.With(propertiesAndFields: PropertiesAndFields.Auto))
                    .WithWebApi(config);

        c.Register<IWidgetService, WidgetService>(Reuse.Singleton);
        c.Register<IWidgetRepository, WidgetRepository>(Reuse.Singleton);
    }

我也用PropertiesAndFields.All 尝试了上述方法,也没有运气。

注意:我知道属性注入不是推荐的方法,构造函数注入是首选的方法有很多原因。但是,我想知道如何正确地做到这两点。

更新

按照@dadhi 的建议,我更改了尝试 #2 来初始化容器,如下所示:

var c = new Container(Rules.Default.With(propertiesAndFields: PropertiesAndFields.All(withNonPublic: false,
                                                            withPrimitive: false,
                                                            withFields: false,
                                                            ifUnresolved: IfUnresolved.Throw)))
.WithWebApi(config);

但后来我收到了这个错误:

{
    "Message" : "An error has occurred.",
    "ExceptionMessage" : "An error occurred when trying to create a controller of type 'WidgetController'. Make sure that the controller has a parameterless public constructor.",
    "ExceptionType" : "System.InvalidOperationException",
    "StackTrace" : "   at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)\r\n   at System.Web.Http.Controllers.HttpControllerDescriptor.CreateController(HttpRequestMessage request)\r\n   at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()",
    "InnerException" : {
        "Message" : "An error has occurred.",
        "ExceptionMessage" : "Type 'IOCContainerTest.DryIOC.Controllers.WidgetController' does not have a default constructor",
        "ExceptionType" : "System.ArgumentException",
        "StackTrace" : "   at System.Linq.Expressions.Expression.New(Type type)\r\n   at System.Web.Http.Internal.TypeActivator.Create[TBase](Type instanceType)\r\n   at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.GetInstanceOrActivator(HttpRequestMessage request, Type controllerType, Func`1& activator)\r\n   at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"
    }
}

DryIoc 现在似乎正在尝试使用我没有的无参数构造函数来初始化我的 WidgetController。我假设由于规则更改为PropertiesAndFields.All(...),DryIoc 正在尝试对所有注册项目使用属性注入。

public class WidgetController : ApiController
{
    private readonly IWidgetService _widgetService;

    public WidgetController(IWidgetService widgetService)
    {
        _widgetService = widgetService;
    }

    // GET api/<controller>
    public List<WidgetSummary> Get()
    {
        return _widgetService.GetWidgetSummaries();
    }
}

我试图使用属性注入来初始化 WidgetService(如上所示),但 WidgetController 使用构造函数注入。也许我不能两者都做,但我认为PropertiesAndFields.Auto 规则会允许两者。我也更改了 WidgetController 以设置属性注入。然后我没有从 DryIoc 中得到任何异常,但 WidgetService 在 WidgetController 中最终为空。这是更新后的 WidgetController。

public class WidgetController : ApiController
{
    public IWidgetService WidgetService { get; set; }

    // GET api/<controller>
    public List<WidgetSummary> Get()
    {
        return WidgetService.GetWidgetSummaries();
    }
}

自动属性注入似乎仍然难以捉摸。

更新 2

经过多次试验和错误(以及来自@dadhi 的建议),我决定在我的 WidgetController 中使用构造函数注入,并在注册其他服务时指定属性注入。这允许随着时间的推移将使用属性注入的代码迁移到构造函数注入,但控制器除外。这是我更新的容器注册:

    public static void Register(HttpConfiguration config)
    {
        var c = new Container(Rules.Default).WithWebApi(config);

        var autoInjectProps = Made.Of(propertiesAndFields: PropertiesAndFields.Auto);

        c.Register<IWidgetService, WidgetService>(Reuse.Singleton, autoInjectProps);
        c.Register<IWidgetRepository, WidgetRepository>(Reuse.Singleton, autoInjectProps);
    }

我很想最终找出适用于控制器和其他服务的黄金设置,但它们的行为似乎不同。但是,目前这是一个足够可行的解决方案。

更新 3

根据@dadhi 的建议将容器配置更新为以下内容,再次尝试将所有内容(包括 WidgetController)连接为属性注入:

    public static void Register(HttpConfiguration config)
    {
        var c = new Container(Rules.Default.With(propertiesAndFields: PropertiesAndFields.All(withNonPublic: false, withPrimitive: false, withFields: false, ifUnresolved: IfUnresolved.Throw)))
                    .WithWebApi(config, throwIfUnresolved: type => type.IsController());

        c.Register<IWidgetService, WidgetService>(Reuse.Singleton);
        c.Register<IWidgetRepository, WidgetRepository>(Reuse.Singleton);
    }

这似乎至少产生了一个有意义的异常,也许可以解释为什么当我将容器设置为使用 PropertiesAndFields.All(..) 时,控制器的处理方式不同:

{
    "Message" : "An error has occurred.",
    "ExceptionMessage" : "An error occurred when trying to create a controller of type 'WidgetController'. Make sure that the controller has a parameterless public constructor.",
    "ExceptionType" : "System.InvalidOperationException",
    "StackTrace" : "   at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)\r\n   at System.Web.Http.Controllers.HttpControllerDescriptor.CreateController(HttpRequestMessage request)\r\n   at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()",
    "InnerException" : {
        "Message" : "An error has occurred.",
        "ExceptionMessage" : "Unable to resolve HttpConfiguration as property \"Configuration\"\r\n  in IOCContainerTest.DryIOC.Controllers.WidgetController.\r\nWhere no service registrations found\r\n  and number of Rules.FallbackContainers: 0\r\n  and number of Rules.UnknownServiceResolvers: 0",
        "ExceptionType" : "DryIoc.ContainerException",
        "StackTrace" : "   at DryIoc.Throw.It(Int32 error, Object arg0, Object arg1, Object arg2, Object arg3)\r\n   at DryIoc.Container.ThrowUnableToResolve(Request request)\r\n   at DryIoc.Container.DryIoc.IContainer.ResolveFactory(Request request)\r\n   at DryIoc.ReflectionFactory.InitPropertiesAndFields(NewExpression newServiceExpr, Request request)\r\n   at DryIoc.ReflectionFactory.CreateExpressionOrDefault(Request request)\r\n   at DryIoc.Factory.GetExpressionOrDefault(Request request)\r\n   at DryIoc.Factory.GetDelegateOrDefault(Request request)\r\n   at DryIoc.Container.ResolveAndCacheDefaultDelegate(Type serviceType, Boolean ifUnresolvedReturnDefault, IScope scope)\r\n   at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.GetInstanceOrActivator(HttpRequestMessage request, Type controllerType, Func`1& activator)\r\n   at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)"
    }
}

【问题讨论】:

    标签: c# asp.net-web-api dependency-injection dryioc


    【解决方案1】:

    第一个尝试应该改为:

    public static void Register(HttpConfiguration config)
    {
        var c = new Container().WithWebApi(config);
    
        c.Register<IWidgetService, WidgetService>(Reuse.Singleton);
        c.Register<IWidgetRepository, WidgetRepository>(Reuse.Singleton);
    
        // Resolve service first then inject properties into it.
        var ws = c.Resolve<IWidgetService>();
        c.InjectPropertiesAndFields(ws);
    }
    

    仅从代码中进行的第二次尝试应该可以工作,但它可能是别的东西。要找出答案,您可以将规则更改为:

    PropertiesAndFields.All(withNonPublic: false, withPrimitives: false, withFields: false, ifUnresolved: IfUnresolved.Throw)
    

    不过,更好的选择是为确切的服务指定确切的属性:

    c.Register<IWidgetService, WidgetService>(Reuse.Singleton, 
         made: PropertiesAndFields.Of.Name("WidgetRepository"));
    

    或强类型:

    c.Register<IWidgetService, WidgetService>(
        Made.Of(() => new WidgetService { WidgetRepository = Arg.Of<IWidgetRepository>() }),
        Reuse.Singleton);
    

    更新:

    DryIoc 的设计具有最令人意外的默认值:类型为 DI 具有单个构造函数且没有属性注入。但是您可以选择使用默认值来简化迁移:

    IContainer c = new Container(Rules.Default.With(
        FactoryMethod.ConstructorWithResolvableArguments, 
        propertiesAndFields: PropertiesAndFilds.Auto);
    

    这对你的情况可能就足够了。

    如果没有,你可以添加规则:

        .WithFactorySelector(Rules.SelectLastRegisteredFactory())
        .WithTrackingDisposableTransients()
        .WithAutoConcreteTypeResolution()
    

    更新 2:

    这个测试对我有用。

    [Test]
    public void Controller_with_property_injection()
    {
        var config = new HttpConfiguration();
        var c = new Container()
            .With(rules => rules.With(propertiesAndFields: PropertiesAndFields.Auto))
            .WithWebApi(config, throwIfUnresolved: type => type.IsController());
    
        c.Register<A>(Reuse.Singleton);
    
        using (var scope = config.DependencyResolver.BeginScope())
        {
            var propController = (PropController)scope.GetService(typeof(PropController));
            Assert.IsNotNull(propController.A);
        }
    }
    
    public class PropController : ApiController
    {
        public A A { get; set; }
    }
    
    public class A {}
    

    但是将PropertiesAndFields.Auto 更改为
    PropertiesAndFields.All(false, false, false, IfUnresolved.Throw) 会产生更新 3 中的错误。

    更新 3:

    感谢您上传sample repo,它有助于找到问题。

    DryIoc PropertiesAndFields.Auto 规则将注入所有声明的和基类 属性,这会导致基ApiController 类中定义的某些属性出错。好在Auto只是一个预定义的规则,你可以自己定义排除基类属性:

    private static IEnumerable<PropertyOrFieldServiceInfo> DeclaredPublicProperties(Request request)
    {
        return (request.ImplementationType ?? request.ServiceType).GetTypeInfo()
            .DeclaredProperties.Where(p => p.IsInjectable())
            .Select(PropertyOrFieldServiceInfo.Of);
    }
    

    然后像这样创建容器:

    var c = new Container()
        .With(rules => rules.With(propertiesAndFields: DeclaredPublicProperties))
        .WithWebApi(config, throwIfUnresolved: type => type.IsController());
    

    我已经提交了带有修复程序的PR

    顺便说一句在未来/下一个 DryIoc 版本中,我将提供 API 来简化基础或仅声明属性选择。

    【讨论】:

    • 感谢@dadhi 的信息,我用您为第二次尝试建议的更新更新了问题并详细说明了结果。来自 Spring.NET,如果注册了匹配服务,我希望属性注入能够自动查找和分配公共属性。此外,我希望对任何给定的注册服务都进行构造函数注入或属性注入。这样一来,我就可以将当前为属性注入设置的大量代码随着时间的推移转换为构造函数注入。
    • 更新了答案以帮助您进行迁移。
    • 这些选项或它们的任何组合都不能为控制器启用属性注入。要么我在运行时得到异常,要么属性为空。但是,我选择了一个可用的解决方案,现在可以让我继续前进,请参阅我的更新 2。它需要我在所有控制器上进行构造函数注入,但允许我选择其他所有内容。
    • 您的控制器及其构造函数的可见性如何?有什么不公开的?
    • 不,控制器与更新 2 结束时显示的相同。没有定义构造函数,所以只有默认的无参数构造函数。
    【解决方案2】:

    好的,忽略我最后的评论,可能不相关。查看异常堆栈跟踪,似乎 WebAPI 回退到使用 Activator.CreateInstance 作为控制器,那是因为 DryIoc 无法解决它。但是回退掩盖了实际的 DryIoc 错误。要找出它,请尝试:

    container.WithWebApi(throwIfUnresolved: type => type.IsController());
    

    【讨论】:

    • 这确实会产生一个信息更丰富的异常,如上面的更新 3 所示。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-04-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多