Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

container.RegisterWebApiControllers(GlobalConfiguration.Configuration) causes InvalidOperationException

In my integration tests I'm using the same SimpleInjector.Container which I construct in the Web API project I'm testing.

But this line in composition root class:

container.RegisterWebApiControllers(GlobalConfiguration.Configuration);

causes an exception:

System.TypeInitializationException : The type initializer for 'MyProject.Api.Test.Integration.HttpClientFactory' threw an exception.
---- System.InvalidOperationException : This method cannot be called during the application's pre-start initialization phase.
Result StackTrace:  
at MyProject.Api.Test.Integration.HttpClientFactory.Create()
   at MyProject.Api.Test.Integration.Controllers.ProductControllerIntegrationTest.<GetProductBarcode_Should_Return_Status_BadRequest_When_Barcode_Is_Empty>d__0.MoveNext() in d:\Projects\My\MyProject.Api.Test.Integration\Controllers\ProductControllerIntegrationTest.cs:line 26
----- Inner Stack Trace -----
   at System.Web.Compilation.BuildManager.EnsureTopLevelFilesCompiled()
   at System.Web.Compilation.BuildManager.GetReferencedAssemblies()
   at System.Web.Http.WebHost.WebHostAssembliesResolver.System.Web.Http.Dispatcher.IAssembliesResolver.GetAssemblies()
   at System.Web.Http.Dispatcher.DefaultHttpControllerTypeResolver.GetControllerTypes(IAssembliesResolver assembliesResolver)
   at System.Web.Http.WebHost.WebHostHttpControllerTypeResolver.GetControllerTypes(IAssembliesResolver assembliesResolver)
   at SimpleInjector.SimpleInjectorWebApiExtensions.GetControllerTypesFromConfiguration(HttpConfiguration configuration)
   at SimpleInjector.SimpleInjectorWebApiExtensions.RegisterWebApiControllers(Container container, HttpConfiguration configuration)
   at MyProject.Api.ContainerConfig.RegisterTypes(Container container) in d:\Projects\My\MyProject.Api\App_Start\ContainerConfig.cs:line 128
   at MyProject.Api.ContainerConfig.CreateWebApiContainer() in d:\Projects\My\MyProject.Api\App_Start\ContainerConfig.cs:line 63
   at MyProject.Api.Test.Integration.HttpClientFactory..cctor() in d:\Projects\My\MyProject.Api.Test.Integration\HttpClientFactory.cs:line 17

After commenting it everything works fine, both the web app itself and the tests.

So the question is:

  • What is the reason for the exception?
  • (And is this method really required?)

Here's the code for HttpClientFactory (a helper class to create HttpClient with proper headers, such as api key or authorization):

internal static class HttpClientFactory
{
    private static readonly Container _container = ContainerConfig.CreateWebApiContainer();

    public static HttpClient Create()
    {
        var client = new HttpClient { BaseAddress = GetUrl() };
        //...
        return client;
    }
}
like image 402
abatishchev Avatar asked Nov 02 '14 21:11

abatishchev


2 Answers

If we look closely at the stack trace we can exactly see what is going on here. The RegisterWebApiControllers extension method calls the GetControllerTypes method on the IHttpControllerTypeResolver instance it grabs from the HttpConfiguration and it passes the IAssembliesResolver that is also retrieved from the configuration. The called GetControllerTypes method (of the WebHostHttpControllerTypeResolver) calls into the GetControllerTypes of the DefaultHttpControllerTypeResolver which will eventually cause a call to GetReferencedAssemblies of the System.Web.Compilation.BuildManager class.

The System.Web.Compilation.BuildManager however, can not be called early in the ASP.NET pipeline, or outside the context of ASP.NET at all. Since you're in a test, the BuildManage will throw the exception you are experiencing.

So the solution (or 'trick' so you will) here is to replace the default IAssembliesResolver when unit testing. I imagine that resolver to look like this:

public class TestAssembliesResolver : IAssembliesResolver
{
    public ICollection<Assembly> GetAssemblies()
    {
        return AppDomain.CurrentDomain.GetAssemblies();
    }
}

[TestMethod]
public void TestMethod1()
{
    // Replace the original IAssembliesResolver.
    GlobalConfiguration.Configuration.Services.Replace(typeof(IAssembliesResolver),
        new TestAssembliesResolver());

    var container = SimpleInjectorWebApiInitializer.BuildContainer();

    container.Verify();
}

It's a bit unfortunate that you have to deal this, especially since Simple Injector was designed to be testable. It seems that we overlooked this by integrating the RegisterWebApiControllers extension method so deeply with Web API. We have to take a step back and think about how to make it easier to verify the Web API configuration inside a unit test.

like image 124
Steven Avatar answered Oct 12 '22 05:10

Steven


A solution for SI v3.x is just ...

container.RegisterWebApiControllers(
  GlobalConfiguration.Configuration, 
  Assembly.GetExecutingAssembly()
);

.. so now it knows the assembly to search for controllers.

like image 4
Tony O'Hagan Avatar answered Oct 12 '22 03:10

Tony O'Hagan