3

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?

Colby
  • 85
  • 1
  • 2
  • 7

2 Answers2

3

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.

David L
  • 32,885
  • 8
  • 62
  • 93
  • Ah, thanks @David for directing me to the source of the .WithWebApi() method. I had glanced at this, but didn't realize that the reference to the container was modified and returned. Makes perfect sense now. Thanks! – Colby Apr 27 '16 at 21:18
  • this is indeed the answer why it happens. however, the container that is given by `this IContainer container` should be the same container that gets returned in `return container`. there should also be a side effect is the second example, such that `c = c.WithWebApi(config);` behaves the same as `c.WithWebApi(config);`, because of the implicit conventions of how the rest of the OWIN/WebAPI fluent interfaces work. – MovGP0 Apr 27 '16 at 21:20
  • it is also a bit counterintuitive why the syntax is `container.WithWebApi(config)`, instead of `config.WithDryIoc(container)`, because it is `app.UseDryIocOwinMiddleware(container)` in the OWIN package. – MovGP0 Apr 27 '16 at 21:25
  • @ColbyBrown happy to help! Please consider marking my answer as the accepted answer to point others in the right direction. – David L Apr 27 '16 at 22:00
  • @MovGP0 it would seem that there are a few naming inconsistencies between the different packages in the library :) – David L Apr 27 '16 at 22:01
  • The names and the target "this" were selected to better match with the integrated library, suggestions for improvement are fine too :) Regarding WithWebApi method it is good to look into API docs to find what are input and return parameters. The info is provided by VS intellisence, or more detailed via Ctrl-Q on the method with R#. – dadhi Apr 28 '16 at 02:12
1

This seems fine and should work. Obviously the container returned from ccontainer.WithWebApi(config); is not the original container, which is unexpected at this point and therefore at least a code smell, since it gives rise to possible bugs. Better write a couple of unit tests and open an issue at the DryIoC Bitbucket site.

To help you out, here is an template of how to write such tests. Create a new test project and do the following:


Install NuGet Packages

Install-Package Microsoft.AspNet.WebApi.OwinSelfHost
Install-Package DryIoc.WebApi.Owin.dll
Install-Package Xunit 

Controller

public sealed class ValuesController : ApiController
{
    private IWidgetService WidgetService { get; }

    public ValuesController(IWidgetService widgetService)
    {
        if(widgetService == null) 
            throw new ArgumentNullException(nameof(widgetService));
        WidgetService = widgetService;
    }

    [HttpGet]
    public IEnumerable<Widget> Get()
    {
        return WidgetService.GetWidgets().ToArray();
    }
}

OWIN Startup

public sealed class Startup
{
    public void Configuration(IAppBuilder app)
    {
        var config = new HttpConfiguration()
            .ConfigureRouting()
            .ConfigureDependencyInjection();

        app.UseWebApi(config);

        // protip: use OWIN error page instead of ASP.NET yellow pages for better diagnostics 
        // Install-Package Microsoft.Owin.Diagnostics
        // app.UseErrorPage(ErrorPageOptions.ShowAll); 
    }

    private static HttpConfiguration ConfigureRouting(this HttpConfiguration config)
    {
        config.MapHttpAttributeRoutes();
        config.Routes.MapHttpRoute(
            name: "default", 
            routeTemplate: "/", 
            defaults: new { controller = "values" });
        return config;
    }

    private static HttpConfiguration ConfigureDependencyInjection(this HttpConfiguration config)
    {
        new Container()
            .Register<IWidgetService, WidgetService>(Reuse.Singleton)
            .Register<IWidgetRepository, WidgetRepository>(Reuse.Singleton)
            .WithWebApi(config);
        return config;
    }
}

Unit Test

[Fact]
public async Task ShouldFoo()
{
    const string baseAddress = "http://localhost:8000";
    using (WebApp.Start<Startup>(baseAddress))
    {
        var httpclient = new HttpClient
        { 
            BaseAddress = new Uri(baseAddress)
        };
        var response = await httpclient.GetAsync("/");
        Assert.That(...);
    }
}
MovGP0
  • 7,267
  • 3
  • 49
  • 42
  • Thanks @MovGP0 for the tip on setting up a test. Your answer lines up with what David was saying as well, the container different after the call to .WithWebApi(..) and was returned, by I did nothing with it. His answer shows the correct usage. – Colby Apr 27 '16 at 21:23