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...
Container registration:
public static void Register(HttpConfiguration config)
{
var c = new Container().WithWebApi(config);
c.Register<IWidgetService, WidgetService>(Reuse.Singleton);
c.Register<IWidgetRepository, WidgetRepository>(Reuse.Singleton);
}
Widget Service:
public class WidgetService : IWidgetService
{
private readonly IWidgetRepository _widgetRepository;
public WidgetService(IWidgetRepository widgetRepository)
{
_widgetRepository = widgetRepository;
}
public IList<Widget> GetWidgets()
{
return _widgetRepository.GetWidgets().ToList();
}
}
Widget Repository:
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();
}
}
How does the configuration need to change to support a WidgetService that looks like this where DryIoc will inject the WidgetRepository as a property?
Desired Widget Service:
public class WidgetService : IWidgetService
{
public IWidgetRepository WidgetRepository { get; set; }
public IList<Widget> GetWidgets()
{
return WidgetRepository.GetWidgets().ToList();
}
}
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);
}
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.
Update
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);
but then I received this error:
{
"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 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();
}
}
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.
Update 2
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.
Update 3
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);
}
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)"
}
}