DryIOC用于属性注入的容器配置 [英] DryIOC Container configuration for property injection

查看:878
本文介绍了DryIOC用于属性注入的容器配置的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在广泛的搜索中寻找一个简单的示例,该示例说明如何配置DryIoc容器,以与注入构造函数args相同的方式简单地将依赖项作为属性注入.

I have search far and wide for a simple example of how to configure a DryIoc container to simply inject dependencies as properties the same way that it injects constructor args.

给出以下工作示例...

Given the following working example...

容器注册:

    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作为属性注入?

How does the configuration need to change to support a WidgetService that looks like this where DryIoc will inject the WidgetRepository as a property?

所需的小部件服务:

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

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

失败的尝试

我尝试了这些配置更改,但是它们似乎对在WidgetService上启用属性注入没有任何作用.

FAILED ATTEMPTS

I have tried these config changes, but they seem to have no effect in enabling property injection on the WidgetService.

ATTEMP 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);
    }

ATTEMP 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进行上述操作,也没有运气.

I have also tried the above with PropertiesAndFields.All, also with no luck.

注意:我了解不建议使用属性注入,出于多种原因,首选构造函数注入.但是,我想知道如何正确地做这两个事情.

NOTE: I understand that property injection is not the recommended approach and that constructor injection is preferred for many reasons. However, I want to know how to do both correctly.

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

Following @dadhi's advice, I changed attempt #2 to initialize the container like:

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尝试对所有注册项目使用属性注入.

DryIoc now seems to be attempting to initialize my WidgetController by using a no-arg constructor which I didn't have. I am assuming since the rules change to PropertiesAndFields.All(...), DryIoc is attempting to use property injection for all registered items.

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中最终为null.这是更新的WidgetController.

I was attempting to initialize the WidgetService (shown above) using property injection, but the WidgetController using constructor injection. Perhaps I can't do both, but I was thinking that the PropertiesAndFields.Auto rule would have allowed for both. I changed the WidgetController to be setup for property injection as well. Then I get no exceptions from DryIoc, but WidgetService ends up null in the WidgetController. Here's the updated WidgetController.

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

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

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

Automatic property injection still seems elusive.

经过反复试验(和@dadhi的建议),我决定在WidgetController中使用构造函数注入,并在注册其他服务时指定属性注入.这就允许将现在利用属性注入的代码迁移到随时间推移的构造函数注入,控制器除外.这是我更新的容器注册:

After much trial and error (and suggestions from @dadhi), I settled for using constructor injection in my WidgetController and specifying property injection when registering other services. This allows for migrating code that utilizes property injection now to constructor injection over time, with the exception of controllers. Here's my updated container registration:

    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);
    }

我希望最终找出对控制器和其他服务均适用的黄金设置,但是它们的行为似乎有所不同.但是,目前这是一个可行的解决方案.

I would love to eventually figure out the golden setting that would work for both controllers and other service, but they seem to act differently. But, this is a workable enough solution for now.

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

Updated the container configuration per @dadhi's suggestion to the following in another attempt at wiring up everything (including WidgetController) as property injection:

    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(..)时为什么对待控制器的方式有所不同:

This seems to at least yield an exception that makes some sense and perhaps explain why the controllers are treated different when I setup the container to use 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)"
    }
}

推荐答案

第一个选项应更改为:

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);
}

仅从代码中进行第二次尝试就可以,但是可能还有其他事情.要找出答案,您可以将规则更改为:

The second attempt from the code alone should work, but it may be something else. To find out you may change the rules to:

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

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

The better alternative though, would be to specify exact property for exact service:

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的单个构造函数且没有属性注入的类型.但是您可以选择使用默认值以简化迁移:

Update:

DryIoc designed with the defaults of least surprise: type with single constructor for DI and without property injection. But you can opt-in the defaults to simplify migration:

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

这足以满足您的情况.

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

If not, you may add rules:

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

更新2:

该测试对我有用.

Update 2:

This test worked for me.

[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中产生了错误.

But changing PropertiesAndFields.Auto to
PropertiesAndFields.All(false, false, false, IfUnresolved.Throw) produced the error from your update 3.

感谢您上传示例存储库,它有助于发现问题.

Thank you for uploading the sample repo, it helped to find the problem.

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

DryIoc PropertiesAndFields.Auto rule will inject all declared and base class properties, which causes the error for some properties defined in base ApiController class. The good thing is that Auto is just a predefined rule, and you can define your own to exclude the base class properties:

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 .

I have submitted the PR with the fix.

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

BTW In future / next DryIoc version I will provide the API to simplify base or declared only property selection.

这篇关于DryIOC用于属性注入的容器配置的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆