2015-08-08 23 views
6

Zgodnie z postem this, powinno być możliwe wstrzykiwanie zależności między aplikacjami SignalR (choć z pewnymi ograniczeniami, takimi jak problem z metodą OnDisconnected()). W moim przypadku jest to ASP Web API (nie MVC) i nie działa z jakiegoś powodu.Zależność Simple Injector per-web-api-request od SignalR hub

Oto odpowiednie części:

container.RegisterWebApiControllers(httpConfiguration); 

container.RegisterWebApiRequest<DbContext, MyDbContext>(); 
container.RegisterWebApiRequest<ISampleRepository, SampleRepository>(); //DbContext injected to SampleRepository 


//Enable injections to SignalR Hubs 
var activator = new SimpleInjectorHubActivator(container); 
GlobalHost.DependencyResolver.Register(typeof(IHubActivator),() => activator); 

Klasa ta umożliwia wstrzyknąć do piast:

public class SimpleInjectorHubActivator : IHubActivator 
     { 
      private readonly Container _container; 

      public SimpleInjectorHubActivator(Container container) 
      { 
       _container = container; 
      } 

      public IHub Create(HubDescriptor descriptor) 
      { 
       return (IHub)_container.GetInstance(descriptor.HubType); 
      } 
} 

i piasty sam:

[HubName("sample")] 
public class SampleHub : Hub 
    { 

     public ActiveBetsHub(ISampleRepository repository) 
     { 
     } 

     //Irrelevant methods here. OnDisconnected() NOT implemented! 
    } 

Przy tej konfiguracji otrzymuję wyjątek:

No registration for type SampleHub could be found and 
an implicit registration could not be made. 
The ISampleRepository is registered as 'Web API Request' 
lifestyle, but the instance is requested outside the context of a Web API Request. 

Co jest oczekiwane, jak rozumiem. Jednak mam dokładnie taki sam wyjątek przy zmianie stylu życia repozytorium Transient:

var transientHybrid = Lifestyle.CreateHybrid(() => HttpContext.Current != null, new WebApiRequestLifestyle(), Lifestyle.Transient); 
    container.Register<ISampleRepository, SampleRepository>(transientHybrid); 

Podejrzewam, że problem może leżeć w HttpContext.Current != null czeku, który nie działa dla Web API w ten sam sposób jak dla MVC.

SignalR 2,2

Proste wtryskiwacza 2.8.3

Czego brakuje?

UPDATE:

To stos ślad na jak SignalR tworzy Piasty:

at SimpleInjector.InstanceProducer.GetInstance() 
    at SimpleInjector.Container.GetInstance(Type serviceType) 
    at MyWebAbi.WebApiApplication.SimpleInjectorHubActivator.Create(HubDescriptor descriptor) in Global.asax.cs:line 108 
    at Microsoft.AspNet.SignalR.Hubs.DefaultHubManager.ResolveHub(String hubName) 
    at Microsoft.AspNet.SignalR.Hubs.HubDispatcher.CreateHub(IRequest request, HubDescriptor descriptor, String connectionId, StateChangeTracker tracker, Boolean throwIfFailedToCreate) 

Zatem właściwym rozwiązaniem byłoby wykorzystanie ExecutionContextScope Przez Piasty ale ten zakres musi być wyraźnie zamknięty, co Sprawia, że ​​sprawy są bardziej skomplikowane ...

Odpowiedz

7

Twoja definicja stylu życia hybrydowego jest nieprawidłowa. Model WebApiRequestLifestyle nie jest w żaden sposób zależny od HttpContext, więc sprawdź, czy HttpContext.Current != null nie będzie działać. Trzeba będzie sprawdzić, czy istnieje aktywny Web API prośba styl życia zakres (lub wykonanie zakres kontekst, który jest w zasadzie taka sama), wywołując container.GetCurrentExecutionContextScope():

var transientHybrid = Lifestyle.CreateHybrid(
    () => container.GetCurrentExecutionContextScope() != null, 
    new WebApiRequestLifestyle(), 
    Lifestyle.Transient); 

Należy pamiętać jednak, że powinno być bardzo ostrożny komponowanie Hybrydowy styl życia o ograniczonym zakresie i przejściowy, ponieważ łatwo przyniesie on złe wyniki. Jest to właściwie domyślne zachowanie niektórych bibliotek DI, ale jest to IMO z design flaw. Zakładam, że bardzo świadomie zarejestrowałeś swój numer MyDbContext w stylu życia, ponieważ musisz upewnić się, że to samo wystąpienie jest używane w całym żądaniu. Korzystanie ze stylu życia oznacza, że ​​podczas żądania możesz otrzymać wiele wiadomości MyDbContext. Może to nie być problem, ponieważ w koncentratorach możesz obecnie mieć tylko jedno odniesienie do swojego MyDbContext, ale kod może się zepsuć po zmianie wykresu obiektu i dodaniu drugiego odwołania do MyDbContext.

Zamiast tego radziłbym nie używać tego połączenia stylów życia.Zamiast tego użyj po prostu WebApiRequestLifestyle lub ExecutionContextScopeLifestyle (są takie same) i upewnij się, że taki zakres kontekstu wykonania zostanie uruchomiony przed rozwiązaniem koncentratora.

A przy okazji, nie zapomnij zarejestrować swoich koncentratorów jawnie w Simple Injector. Dzięki temu Simple Injector może analizować kompletny wykres obiektów dla ciebie, w tym klasy piasty.

+0

Dziękuję bardzo za odpowiedź @Steven. Kiedy powiesz "i upewnij się, że taki zakres kontekstu wykonania zostanie uruchomiony, zanim twój koncentrator zostanie rozwiązany." masz na myśli używanie container.BeginExecutionContextScope()? Ponieważ zakres ten wymaga wyraźnego usunięcia, jak radziłbyś zaimplementować tę część, gdyby nie był używany wzorzec CommandHandler. A także czy firma powinna być zarejestrowana jako WebApiRequest Lifestyles? Dzięki! –

+0

@IljaS. Naprawdę nie mogę odpowiedzieć na to pytanie. Nie wiem jak wygląda twoja implementacja, w którym momencie otrzymasz ślad stosu (i moje doświadczenie SignalR również nie ma). Spróbuj zaktualizować swoje pytanie, dodając więcej kodu i kompletny ślad stosu. Przyjrzę się. – Steven

+0

Nie ma zbyt wiele w realizacji. Dla uproszczenia sake pozwala na stwierdzenie, że jest to bardzo prosta aplikacja WebApi z 1 klasą Repository, do której wstrzykiwany jest DbContext. 1 Hub, w którym chciałbym wstrzyknąć tę klasę Repository. I to wszystko. Dodałem ścieżkę stosu do wywołania Hub SignalR. Nie wiem, co jeszcze dodać. –

2

Niedawno w obliczu tego samego problemu i stwierdzili, że następujące pracuje całkiem dobrze, mam nadzieję, że to pomoże komuś:

public class SignalRDependencyResolver : DefaultDependencyResolver 
{ 
    public SignalRDependencyResolver(IServiceProvider serviceProvider) 
    { 
     _serviceProvider = serviceProvider; 
    } 

    public override object GetService(Type serviceType) 
    { 
     return _serviceProvider.GetService(serviceType) ?? base.GetService(serviceType); 
    } 

    public override IEnumerable<object> GetServices(Type serviceType) 
    { 
     var @this = (IEnumerable<object>) _serviceProvider.GetService(typeof (IEnumerable<>).MakeGenericType(serviceType)); 

     var @base = base.GetServices(serviceType); 

     return @this == null ? @base : @base == null ? @this : @this.Concat(@base); 
    } 

    private readonly IServiceProvider _serviceProvider; 
} 

public class SignalRHubDispatcher : HubDispatcher 
{ 
    public SignalRHubDispatcher(Container container, HubConfiguration configuration) : base(configuration) 
    { 
     _container = container; 
    } 

    protected override Task OnConnected(IRequest request, string connectionId) 
    { 
     return Invoke(() => base.OnConnected(request, connectionId)); 
    } 

    protected override Task OnReceived(IRequest request, string connectionId, string data) 
    { 
     return Invoke(() => base.OnReceived(request, connectionId, data)); 
    } 

    protected override Task OnDisconnected(IRequest request, string connectionId, bool stopCalled) 
    { 
     return Invoke(() => base.OnDisconnected(request, connectionId, stopCalled)); 
    } 

    protected override Task OnReconnected(IRequest request, string connectionId) 
    { 
     return Invoke(() => base.OnReconnected(request, connectionId)); 
    } 

    private async Task Invoke(Func<Task> method) 
    { 
     using (_container.BeginExecutionContextScope()) 
      await method(); 
    } 

    private readonly Container _container; 
} 

public class Startup 
{ 
    public void Configuration(IAppBuilder app) 
    { 
     var container = new Container(); 

     container.Options.DefaultScopedLifestyle = new ExecutionContextScopeLifestyle(); 

     container.Register<DbContext, MyDbContext>(Lifestyle.Scoped); 
     container.Register<ISampleRepository, SampleRepository>(Lifestyle.Scoped); 

     // if you want to use the same container in WebApi don't forget to add 
     app.Use(async (context, next) => { 
      using (container.BeginExecutionContextScope()) 
       await next(); 
     }); 

     // ... configure web api 

     var config = new HubConfiguration 
     { 
      Resolver = new SignalRDependencyResolver(container) 
     } 

     // ... configure the rest of SignalR 

     // pass SignalRHubDispatcher 
     app.MapSignalR<SignalRHubDispatcher>("/signalr", config); 
    } 
} 
+0

Po kilkukrotnym uruchomieniu tego wydania uważam, że ta odpowiedź jest najbardziej odpowiednia dla obecnej konfiguracji SignalR/SI (stan na lipiec 16) - również prosta i przyjemna. Dziękujemy za rejestrację na SO tylko po to, by publikować posty! – keithl8041

+0

Witam Serg. Czy masz go na git? Mam z tym duży problem. Dziękuję – rsegovia