用于属性注入的 DryIOC 容器配置

Col*_*lby 5 c# dependency-injection asp.net-web-api dryioc

我已经广泛搜索了一个简单的例子,说明如何配置一个 DryIoc 容器来简单地将依赖项作为属性注入,就像注入构造函数 args 一样。

鉴于以下工作示例...

容器注册:

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

        c.Register<IWidgetService, WidgetService>(Reuse.Singleton);
        c.Register<IWidgetRepository, WidgetRepository>(Reuse.Singleton);
    }
Run Code Online (Sandbox Code Playgroud)

小工具服务:

public class WidgetService : IWidgetService
{
    private readonly IWidgetRepository _widgetRepository;

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

    public IList<Widget> GetWidgets()
    {
        return _widgetRepository.GetWidgets().ToList();
    }
}
Run Code Online (Sandbox Code Playgroud)

小部件存储库:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

配置需要如何更改才能支持 WidgetService 看起来像这样,其中 DryIoc 会将 WidgetRepository 作为属性注入?

所需的小工具服务:

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

    public IList<Widget> GetWidgets()
    {
        return WidgetRepository.GetWidgets().ToList();
    }
}
Run Code Online (Sandbox Code Playgroud)

失败的尝试

我已经尝试过这些配置更改,但它们似乎对在 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);
    }
Run Code Online (Sandbox Code Playgroud)

尝试 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);
    }
Run Code Online (Sandbox Code Playgroud)

我也试过上面的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);
Run Code Online (Sandbox Code Playgroud)

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

{
    "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)"
    }
}
Run Code Online (Sandbox Code Playgroud)

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

    // GET api/<controller>
    public List<WidgetSummary> Get()
    {
        return WidgetService.GetWidgetSummaries();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

更新 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);
    }
Run Code Online (Sandbox Code Playgroud)

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

更新 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);
    }
Run Code Online (Sandbox Code Playgroud)

这似乎至少产生了一个有意义的异常,也许可以解释为什么当我设置容器使用时控制器被不同的对待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)"
    }
}
Run Code Online (Sandbox Code Playgroud)

dad*_*dhi 5

第一个尝试应更改为:

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);
}
Run Code Online (Sandbox Code Playgroud)

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

PropertiesAndFields.All(withNonPublic: false, withPrimitives: false, withFields: false, ifUnresolved: IfUnresolved.Throw)
Run Code Online (Sandbox Code Playgroud)

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

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

或强类型:

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

更新:

DryIoc 的设计采用了最不令人意外的默认设置:具有用于 DI 的单个构造函数的类型,并且没有属性注入。但您可以选择默认值以简化迁移:

IContainer c = new Container(Rules.Default.With(
    FactoryMethod.ConstructorWithResolvableArguments, 
    propertiesAndFields: PropertiesAndFilds.Auto);
Run Code Online (Sandbox Code Playgroud)

对于你的情况来说可能就足够了。

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

    .WithFactorySelector(Rules.SelectLastRegisteredFactory())
    .WithTrackingDisposableTransients()
    .WithAutoConcreteTypeResolution()
Run Code Online (Sandbox Code Playgroud)

更新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 {}
Run Code Online (Sandbox Code Playgroud)

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

更新3:

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

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);
}
Run Code Online (Sandbox Code Playgroud)

然后像这样创建容器:

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

我已经提交了修复后的PR 。

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