简体   繁体   中英

WebApi2 with OWIN TestServer and AutoFac - LifetimeScope already disposed

I have trouble with testing my application using Owin.TestServer . I couldn't find anything useful, and I hope this is an easy fix that community can help with :)

Recently I started to write integration tests for my WebApi application that is using OWIN and AutoFac for DI. I have 3 integration tests in total. When I run each test individually, they are all passing. However when I run all of the tests at once, only first one succeeds and the other two are failed because of the following AutoFac error:

System.AggregateException: One or more errors occurred. ---> 
System.ObjectDisposedException: Instances cannot be resolved and nested lifetimes cannot be created from this LifetimeScope as it has already been disposed.

Stacktrace indicates that the error comes from the AutoFac middleware for Owin.

I have following setup for the tests:

[TestClass]
public class DinnerListControllerTests
{
    private TestServer _server;
    private TransactionScope _transactionScope;

    [TestInitialize]
    public void Init()
    {
        _transactionScope = new TransactionScope(TransactionScopeOption.RequiresNew);
        _server = TestServer.Create<Startup>();
    }

    [TestCleanup]
    public void Dispose()
    {
        _server?.Dispose();
        _transactionScope?.Dispose();
    }

    [TestMethod]
    public void GetAllLists()
    {
        var response = _server.HttpClient.GetAsync("/api/dinnerlists").Result;
        response.IsSuccessStatusCode.Should().BeTrue("there should be no error");
        var result = response.Content.ReadAsAsync<IEnumerable<DinnerListDTO>>().Result;
        result.Should().NotBeNull().And.HaveCount(5);
    }

    [TestMethod]
    public void GetActiveListsReturnsTwoLists()
    {
        var response = _server.HttpClient.GetAsync("/api/dinnerlists/active").Result;
        response.IsSuccessStatusCode.Should().BeTrue();

        var result = response.Content.ReadAsAsync<IEnumerable<DinnerListDTO>>().Result;
        result.Should()
            .NotBeNullOrEmpty()
            .And.HaveCount(2)
            .And.OnlyContain(dto => dto.OpenUntil.CompareTo(DateTime.Now) > 0);
    }
}

GetAllLists test will execute properly, but the second one will fail with message stated above.

I have tried with different registration scopes of the dependecies, but it didn't help. Below is my AutoFac configuration, startup class and sample AutoFac module:

AutoFacConfig.cs

public class AutoFacConfig
{
    private static IContainer _container;
    public static IContainer Container => _container ?? (_container = BuildContainer());

    public static void ConfigureAutoFac(HttpConfiguration config)
    {
        if (config == null)
            throw new ArgumentNullException(nameof(config));

        FluentValidationModelValidatorProvider.Configure(config,
            provider => provider.ValidatorFactory = new AutoFacValidatorFactory(Container));

        config.DependencyResolver = new AutofacWebApiDependencyResolver(Container);
    }

    private static IContainer BuildContainer()
    {
        var autoFacBuilder = new ContainerBuilder();
        var assembly = Assembly.GetExecutingAssembly();
        autoFacBuilder.RegisterApiControllers(assembly).InstancePerRequest();

        autoFacBuilder.RegisterType<DinnerDbContext>().InstancePerRequest();
        autoFacBuilder.RegisterModule<RepositoryModule>();
        autoFacBuilder.RegisterModule<ServicesModule>();
        autoFacBuilder.RegisterModule<ValidationModule>();
        autoFacBuilder.RegisterModule<AutoMapperModule>();
        autoFacBuilder.RegisterModule<AutofacWebTypesModule>();

        return autoFacBuilder.Build();
    }
}

Startup.cs:

public class Startup
{
    public void Configuration(IAppBuilder appBuilder)
    {
        var httpConfiguration = new HttpConfiguration();

        WebApiConfig.Register(httpConfiguration);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);

        AutoFacConfig.ConfigureAutoFac(httpConfiguration);
        AutoMapperConfig.RegisterMappings();

        appBuilder.UseAutofacMiddleware(AutoFacConfig.Container);
        appBuilder.UseAutofacWebApi(httpConfiguration);
        appBuilder.UseWebApi(httpConfiguration);
    }
}

Sample AutoFac module:

public class RepositoryModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        var assembly = System.Reflection.Assembly.GetExecutingAssembly();
        builder.RegisterAssemblyTypes(assembly)
            .Where(type => type.Name.EndsWith("Repository"))
            .AsImplementedInterfaces()
            .InstancePerRequest();
    }
}

EDIT - SOLUTION

What @Eris suggested made a lot of sense - my AutoFacConfig class was using static methods and members, which meant that the Container property was present on subsequent tests and it was not being created again and it was marked as disposed.

I decided to refactor the code so AutoFacConfig is not using static members any more, as I didn't want to play with disposal of the container on application shutdown.

AutoFacConfig.cs:

public class AutoFacConfig
{
    private IContainer _container;
    public IContainer Container
    {
        get { return _container ?? (_container = BuildContainer()); }
    }

    public void ConfigureAutoFac(HttpConfiguration config)
    {
        //...
    }

    private IContainer BuildContainer()
    {
        var autoFacBuilder = new ContainerBuilder();
        //...
        return autoFacBuilder.Build();
    }
}

Startup.cs

public class Startup
{
    public void Configuration(IAppBuilder appBuilder)
    {
        var httpConfiguration = new HttpConfiguration();
        var autofacConfig = new AutoFacConfig();  // create instance of AutoFacConfig           
        autofacConfig.ConfigureAutoFac(httpConfiguration); // configure autofac

        appBuilder.UseAutofacMiddleware(autofacConfig.Container);
        appBuilder.UseAutofacWebApi(httpConfiguration);
        appBuilder.UseWebApi(httpConfiguration);
    }
}

I suspect it's the AutoFacConfig causing a problem:

    public static IContainer Container => _container ?? (_container = BuildContainer());

In this case, the _container is not null, but in "Disposed" state. It should work if you unconditionally recreate it. (I'm not familiar with C#6 syntax yet, so this may not be entirely correct)

    public static IContainer Container => _container = BuildContainer();

Alternate answer: In self-hosted OWIN Web API, how to run code at shutdown?

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        var context = new OwinContext(app.Properties);
        var token = context.Get<CancellationToken>("host.OnAppDisposing");
        if (token != CancellationToken.None)
        {
            token.Register(() =>
            {
                // code to run 
                // null out disposable resources
            });
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM