Search code examples
c#asp.net-web-apidryioc

DryIoc.WebApi Setup


I'm exploring the use of DryIoc in a .NET WebAPI application and have noticed a strange behavior with the initialization steps. In a simple test webapi application, I have the following DryIoc registration class which gets called immediately after the WebApi config registration.

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

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

And the following WebApi controller:

public class ValuesController : ApiController
{
    private readonly IWidgetService _widgetService;

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

    // GET api/values
    public IEnumerable<Widget> Get()
    {
        return _widgetService.GetWidgets();
    }
}

This seems to work fine, but in experimenting with was seems to me to be the identical, but written a little more verbose, code I get an error.

public class DryIocConfig
{
    public static void Register(HttpConfiguration config)
    {
        var c = new Container();
        c.WithWebApi(config);  // Now separate statement rather than chained. 

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

The exception I get is the following (as JSON):

    {
        "Message" : "An error has occurred.",
        "ExceptionMessage" : "An error occurred when trying to create a controller of type 'ValuesController'. 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.DryLoc.Controllers.ValuesController' 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)"
        }
    }

Is this something odd with DryIoc, or is this a C# nuance that I've just never come across?


Solution

  • This is because .WithWebApi() is an extension method per the source.

     public static IContainer WithWebApi(this IContainer container, HttpConfiguration config,
            IEnumerable<Assembly> controllerAssemblies = null, IScopeContext scopeContext = null,
            Func<Type, bool> throwIfUnresolved = null)
        {
            container.ThrowIfNull();
    
            if (container.ScopeContext == null)
                container = container.With(scopeContext: scopeContext ?? new AsyncExecutionFlowScopeContext());
    
            container.RegisterWebApiControllers(config, controllerAssemblies);
    
            container.SetFilterProvider(config.Services);
    
            InsertRegisterRequestMessageHandler(config);
    
            config.DependencyResolver = new DryIocDependencyResolver(container, throwIfUnresolved);
    
            return container;
        }
    

    In the first syntax example, you create a new instance of Container and pass that newly created instance to .WithWebApi(). This in turn updates the container instance and finally returns it back to variable c.

    In the second syntax example, you are never returning the value of the extension method BACK to the original variable, updating it. You are calling it as if it were a void method, which does nothing in this case, hence the exception.

    If you had instead written:

    var c = new Container();
    c = c.WithWebApi(config);
    

    it would have essentially been a more verbose example of the first syntax and would have properly updated c with the new functionality.