Простой инжектор не может вводить каждый зарегистрированный класс запроса веб-API при запуске Owin

Я создаю API, используя Owin, Web API, Entity Framework, ASP.NET Identity. Я использую Simple Injector в качестве моей выборки DI.

Во время процесса запуска Owin я хочу, чтобы семена базы данных с некоторыми образцами данных. Это обрабатывается классом, реализующим IDatabaseInitializer, который выглядит примерно так:

public class MyDbInitializer : DropCreateDatabaseAlways<MyDataContext>
{
    private readonly IUserManager _userManager;

    public MyDbInitializer(IUserManager userManager)
    {
        _userManager = userManager;
    }

    protected override void Seed(MyDataContext context)
    {
        SeedIdentities();
    }

    private void SeedIdentities()
    {
        var user = new User
        {
            UserName = "someUsername",
            Email = "[email protected]"
        };

        _userManager.CreateAsync(user, "Password");
    }

IUserManager является прокси для класса ASP.NET Identiy UserManager, который косвенно зависит от IUnitOfWork. Если вам интересно, IUserManager регистрируется следующим образом:

container.Register(typeof(IUserManager), 
    () => container.GetInstance<IUserManagerFactory>().Create());

Поскольку я хочу использовать единую рабочую единицу для каждого запроса веб-API, я зарегистрировал свой IUnitOfWork следующим образом:

container.RegisterWebApiRequest<IUnitOfWork, MyUnitOfWork>();

Это работает отлично и денди для всего, кроме как при разрешении зависимости IUserManager в классе MyDbInitializer. Во время запуска приложения SimpleInjector выходит из строя со следующим исключением ActivationException:

SimpleInjector.ActivationException was unhandled by user code
  HResult=-2146233088
  Message=The registered delegate for type IUserManagerFactory threw an exception. The IUnitOfWork is registered as 'Web API Request' lifestyle, but the instance is requested outside the context of a Web API Request.
   Source=SimpleInjector
   StackTrace:
     at SimpleInjector.InstanceProducer.GetInstance()
     at SimpleInjector.Container.GetInstance[TService]()
     at Project.WebApi.CompositionRoot.SimpleInjectorCompositionRoot.<RegisterSecurityDependencies>b__c() in c:\code\Project\Project.WebApi\CompositionRoot\SimpleInjectorCompositionRoot.cs:line 130
     at lambda_method(Closure )
     at lambda_method(Closure )
     at SimpleInjector.InstanceProducer.GetInstance()
   InnerException: SimpleInjector.ActivationException
     HResult=-2146233088
     Message=The IUnitOfWork is registered as 'Web API Request' lifestyle, but the instance is requested outside the context of a Web API Request.
     Source=SimpleInjector
     StackTrace:
        at SimpleInjector.Scope.GetScopelessInstance[TService,TImplementation](ScopedRegistration`2 registration)
        at SimpleInjector.Scope.GetInstance[TService,TImplementation](ScopedRegistration`2 registration, Scope scope)
        at SimpleInjector.Advanced.Internal.LazyScopedRegistration`2.GetInstance(Scope scope)
        at lambda_method(Closure )
        at SimpleInjector.InstanceProducer.GetInstance()
     InnerException:

Я предполагаю, что класс Owin Startup.cs считается вне жизненного цикла запроса веб-API, и эта инсталляция конструктора в инициализаторе базы данных терпит неудачу, потому что она вызывается классом Startup.cs.

Я не уверен, как решить эту проблему. Должен ли я использовать гибридный образ жизни для регистрации IUnitOfWork, или есть ли лучшее решение?

Ответ 1

Вы правы. Во время запуска нет запроса веб-API. К счастью, WebApiRequestLifestyle (который используется методом расширения RegisterWebApiRequest) использует ExecutionContextLifestyle под обложками, поэтому очень просто "имитировать" веб-запрос следующим образом:

// using SimpleInjector.Extensions.ExecutionContextScoping;

using (container.BeginExecutionContextScope())
{
    var initializer = container.GetInstance<MyDbInitializer>();
    intializer.InitializeDatabase();
}

Ответ 2

Кроме того, если вы размещены в IIS, интегрированный конвейер создаст новый CallContext для каждого этапа конвейера, где SimpleInjector сохраняет контекст выполнения.

Итак, если вы выполняете BeginExecutionContextScope в части промежуточного программного обеспечения, находящегося на другом этапе конвейера, чем работает ваш код веб-API, контекст выполнения SimpleInjector не будет сохраняться.

Вы можете использовать app.UseStageMarker, чтобы настроить, где выполняется промежуточное ПО в конвейере, но мне не удалось заставить контекст выполнения SimpleInjector сохраняться через все мое промежуточное ПО OWIN, а затем в веб-API. Следующий код в моей делетерацииHandlerProxy всегда заканчивает создание новой области выполнения, несмотря на использование того же контейнера SimpleInejctor

protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
    // Trigger the creation of the DependencyScope to ensure the instance
    // is resolved within that scope.
    request.GetDependencyScope();
}