5

我已经广泛搜索了一个如何配置 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)"
    }
}
4

2 回答 2

4

第一次尝试应更改为:

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:

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

DryIocPropertiesAndFields.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 来简化基本或仅声明的属性选择。

于 2016-04-28T02:01:33.440 回答
1

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

container.WithWebApi(throwIfUnresolved: type => type.IsController());
于 2016-04-29T06:03:01.800 回答