简体   繁体   English

带有OWIN TestServer和AutoFac的WebApi2-LifetimeScope已废弃

[英]WebApi2 with OWIN TestServer and AutoFac - LifetimeScope already disposed

I have trouble with testing my application using Owin.TestServer . 我在使用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. 最近,我开始为使用OWIN和AutoFac for DI的WebApi应用程序编写集成测试。 I have 3 integration tests in total. 我总共有3个集成测试。 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: 但是,当我一次运行所有测试时,由于以下AutoFac错误,只有第一个成功,而其他两个则失败:

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. Stacktrace指示该错误来自Owin的AutoFac中间件。

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. GetAllLists测试将正确执行,但是第二个测试将失败,并显示上述消息。

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: 以下是我的AutoFac配置,启动类和示例AutoFac模块:

AutoFacConfig.cs 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: 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: 示例AutoFac模块:

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. @Eris的建议很有道理-我的AutoFacConfig类使用的是静态方法和成员,这意味着Container属性在后续测试中存在,并且不再被创建,并且被标记为已处置。

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不再使用静态成员,因为我不想在应用程序关闭时处理容器。

AutoFacConfig.cs: 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 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: 我怀疑是AutoFacConfig引起了问题:

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

In this case, the _container is not null, but in "Disposed" state. 在这种情况下, _container不为null,而是处于“ Disposed”状态。 It should work if you unconditionally recreate it. 如果您无条件重新创建它,则它应该可以工作。 (I'm not familiar with C#6 syntax yet, so this may not be entirely correct) (我还不熟悉C#6语法,因此这可能并不完全正确)

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

Alternate answer: In self-hosted OWIN Web API, how to run code at shutdown? 替代答案: 在自托管的OWIN Web API中,如何在关闭时运行代码?

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
            });
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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