2015-05-19 15 views
51

Problem polega na tym, że pakiet SDK Azure WebJobs obsługuje tylko publiczne metody statyczne jako punkty wejścia w pracy, co oznacza, że ​​nie ma możliwości implementacji wtrysku konstruktora/właściwości.Wstrzyknięcie zależne przy użyciu pakietu Azure WebJobs SDK?

Nie mogę znaleźć niczego na ten temat w oficjalnej dokumentacji/zasobach SDK WebJobs. Jedyne rozwiązanie, na które natknąłem się opiera się na schemacie lokalizacji usługi (anty) opisanym w tym poście: here.

Czy istnieje dobry sposób na zastosowanie "właściwego" wtrysku zależności dla projektów opartych na pakiecie Azure WebJobs SDK?

+0

można opracować trochę? Czy próbujesz uzyskać zakres "zadania" dla obiektów wychodzących z kontenera, podobnie jak w przypadku pojedynczego żądania w standardowej aplikacji internetowej? – Veatch

+0

Tak. Zmieniłem to pytanie. Więcej informacji można znaleźć w poście na blogu, który łączyłem. –

+0

Zakładając, że korzystasz z aplikacji konsolowej, powinieneś używać DI tak jak w dowolnej aplikacji konsolowej. Biorąc to pod uwagę, jedyną metodą, którą znalazłem, jest użycie czegoś podobnego niż anty-wzór w poście na blogu. Mogę podać przykład używając Ninject na życzenie. – lopezbertoni

Odpowiedz

77

Pakiet SDK Azure WebJobs obsługuje teraz metody instancji. Łącząc to z niestandardowym IJobActivatorem, możesz użyć DI.

Najpierw utwórz niestandardowy IJobActivator że mogą rozwiązać typ zadania za pośrednictwem swojego ulubionego kontener DI:

public class MyActivator : IJobActivator 
{ 
    private readonly IUnityContainer _container; 

    public MyActivator(IUnityContainer container) 
    { 
     _container = container; 
    } 

    public T CreateInstance<T>() 
    { 
     return _container.Resolve<T>(); 
    } 
} 

Musisz zarejestrować tej klasy przy użyciu niestandardowego JobHostConfiguration:

var config = new JobHostConfiguration 
{ 
    JobActivator = new MyActivator(myContainer) 
}; 
var host = new JobHost(config); 

Następnie należy może używać prostej klasy z metodami instancji dla twoich zadań (tutaj używam funkcji wtrysku konstruktora Unity):

public class MyFunctions 
{ 
    private readonly ISomeDependency _dependency; 

    public MyFunctions(ISomeDependency dependency) 
    { 
     _dependency = dependency; 
    } 

    public Task DoStuffAsync([QueueTrigger("queue")] string message) 
    { 
     Console.WriteLine("Injected dependency: {0}", _dependency); 

     return Task.FromResult(true); 
    } 
} 
+0

Świetnie! Eliminuje to konieczność ręcznego rozwiązywania wszystkich zależności w ramach funkcji zadania. Szkoda, że ​​nie pojawiłeś się wcześniej - teraz muszę napisać całą masę rzeczy. –

+2

Chcę zauważyć, że jest to tylko wsparcie z wersji 1.0.1 i wyższej biblioteki. – VeldMuijz

+3

W przypadku zadania wyzwalanego (nie używając kolejek itd.), Czy musimy ręcznie uruchomić funkcję, aby użyć aktywatora zadań? 'CallMethod (...)'? – kamranicus

4

Użyłem kilku wzorów, które opierają się na koncepcji pojemników/zakresów dla dzieci (w zależności od terminologii wybranego pojemnika IoC). Nie jestem pewien, które z nich to obsługują, ale mogę powiedzieć, że robią to StructureMap 2.6.x i AutoFac.

Chodzi o to, aby zakodować zakres podrzędny dla każdej przychodzącej wiadomości, wprowadzić dowolny kontekst, który jest unikalny dla tego żądania, rozwiązać obiekt najwyższego poziomu z zakresu podrzędnego, a następnie uruchomić proces.

Oto kilka ogólnych kodów pokazujących to z AutoFac. Wynika to z pojemnika bezpośrednio, podobnie jak w przypadku wzorca, którego próbujesz uniknąć, ale został wyizolowany w jednym miejscu.

W tym przypadku używa ServiceBusTrigger do zwolnienia zadania, ale może być wszystko - host zadania może mieć listę tych dla różnych kolejek/procesów.

public static void ServiceBusRequestHandler([ServiceBusTrigger("queuename")] ServiceBusRequest request) 
{ 
    ProcessMessage(request); 
} 

Ta metoda jest wywoływana przez wszystkie wystąpienia powyższych metod. Zawiesza tworzenie zakresu podrzędnego w bloku używanym, aby upewnić się, że wszystko jest wyczyszczone. Następnie zostaną utworzone wszystkie obiekty, które będą się różnić w zależności od żądania i będą zawierać kontekst używany przez inne zależności (informacje o użytkowniku/kliencie itp.) I wstrzyknięte do kontenera podrzędnego (w tym przykładzie, IRequestContext). W końcu komponent wykonujący pracę zostanie rozwiązany z kontenera podrzędnego.

private static void ProcessMessage<T>(T request) where T : IServiceBusRequest 
{ 
    try 
    { 
     using (var childScope = _container.BeginLifetimeScope()) 
     { 
      // create and inject things that hold the "context" of the message - user ids, etc 

      var builder = new ContainerBuilder(); 
      builder.Register(c => new ServiceRequestContext(request.UserId)).As<IRequestContext>().InstancePerLifetimeScope(); 
      builder.Update(childScope.ComponentRegistry); 

      // resolve the component doing the work from the child container explicitly, so all of its dependencies follow 

      var thing = childScope.Resolve<ThingThatDoesStuff>(); 
      thing.Do(request); 
     } 
    } 
    catch (Exception ex) 
    { 

    } 
} 
+0

Dzięki! Jest to solidne podejście, na podstawie którego zaimplementowałem moją infrastrukturę wtrysku początkowej zależności. Jednak teraz, gdy mamy zaktualizowany SDK, używam tylko wbudowanej metody, która działa bezbłędnie. –

+0

@veatch nie trzeba tworzyć nowego konstruktora kontenerów i aktualizować zakresu, _container.BeginLifetimeScope() pozwala teraz zarejestrować nowe zależności. –

11

W ten sposób radziłem sobie z określaniem zakresu przy użyciu nowego zestawu SDK. Używanie IJobactivatora w sposób opisany przez Alexandra Molenkampa.

public class ScopedMessagingProvider : MessagingProvider 
{ 
    private readonly ServiceBusConfiguration _config; 
    private readonly Container _container; 

    public ScopedMessagingProvider(ServiceBusConfiguration config, Container container) 
     : base(config) 
    { 
     _config = config; 
     _container = container; 
    } 

    public override MessageProcessor CreateMessageProcessor(string entityPath) 
    { 
     return new CustomMessageProcessor(_config.MessageOptions, _container); 
    } 

    private class CustomMessageProcessor : MessageProcessor 
    { 
     private readonly Container _container; 

     public CustomMessageProcessor(OnMessageOptions messageOptions, Container container) 
      : base(messageOptions) 
     { 
      _container = container; 
     } 

     public override Task<bool> BeginProcessingMessageAsync(BrokeredMessage message, CancellationToken cancellationToken) 
     { 
      _container.BeginExecutionContextScope(); 
      return base.BeginProcessingMessageAsync(message, cancellationToken); 

     } 

     public override Task CompleteProcessingMessageAsync(BrokeredMessage message, FunctionResult result, CancellationToken cancellationToken) 
     { 
      var scope = _container.GetCurrentExecutionContextScope(); 
      if (scope != null) 
      { 
       scope.Dispose(); 
      } 

      return base.CompleteProcessingMessageAsync(message, result, cancellationToken); 
     } 
    } 
} 

Można użycia niestandardowej MessagingProvider w JobHostConfiguration jak

var serviceBusConfig = new ServiceBusConfiguration 
{ 
    ConnectionString = config.ServiceBusConnectionString 
}; 
serviceBusConfig.MessagingProvider = new ScopedMessagingProvider(serviceBusConfig, container); 
jobHostConfig.UseServiceBus(serviceBusConfig); 
+0

Tylko wyjaśnić, to da nam zakres pracy? Ale jest to specyficzne dla zadań opartych na wyzwalaczach kolejki Azure Service Bus? –

+0

Spowoduje to ustawienie zakresu każdej wiadomości. Ten przykład dotyczy wyzwalaczy ServiceBus, ale myślę, że MessageProcessor jest otwarty na rozszerzenie także dla innych wyzwalaczy. –

+0

Co to jest "BeginExecutionContextScope()"? Czy ta metoda jest specyficzna dla niektórych implementacji kontenera DI? Jaki byłby odpowiednik Unity? – UserControl

8

Po zapytaniu moich own question o tym, jak obsługiwać scopingu ...Właśnie doszedłem do tego rozwiązania: nie sądzę, że jest to idealne rozwiązanie, ale na razie nie mogłem znaleźć żadnego innego rozwiązania.

W moim przykładzie mam do czynienia z ServiceBusTrigger.

Ponieważ używam SimpleInjector, implementacja interfejsu IJobActivator wygląda tak:

public class SimpleInjectorJobActivator : IJobActivator 
{ 
    private readonly Container _container; 

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

    public T CreateInstance<T>() 
    { 
     return (T)_container.GetInstance(typeof(T)); 
    } 
} 

Tutaj mam do czynienia z wyzwalane webjobs.

Więc mam dwie zależności:

  • pojedyncza:

    public interface ISingletonDependency { } 
    
    public class SingletonDependency : ISingletonDependency { } 
    
  • I jeszcze, że muszą żyć tylko czas moja funkcja jest wyzwalany:

    public class ScopedDependency : IScopedDependency, IDisposable 
    { 
        public void Dispose() 
        { 
         //Dispose what need to be disposed... 
        } 
    } 
    

Tak więc, aby hav e proces, który działa niezależnie od webjob. Mam obudowane mój proces w klasie:

public interface IBrokeredMessageProcessor 
{ 
    Task ProcessAsync(BrokeredMessage incommingMessage, CancellationToken token); 
} 

public class BrokeredMessageProcessor : IBrokeredMessageProcessor 
{ 
    private readonly ISingletonDependency _singletonDependency; 
    private readonly IScopedDependency _scopedDependency; 

    public BrokeredMessageProcessor(ISingletonDependency singletonDependency, IScopedDependency scopedDependency) 
    { 
     _singletonDependency = singletonDependency; 
     _scopedDependency = scopedDependency; 
    } 

    public async Task ProcessAsync(BrokeredMessage incommingMessage, CancellationToken token) 
    { 
     ... 
    } 
} 

Więc teraz, gdy zaczyna webjob, muszę się zarejestrować moje zależności zależności ich zakresy:

class Program 
{ 
    private static void Main() 
    { 
     var container = new Container(); 
     container.Options.DefaultScopedLifestyle = new ExecutionContextScopeLifestyle(); 
     container.RegisterSingleton<ISingletonDependency, SingletonDependency>(); 
     container.Register<IScopedDependency, ScopedDependency>(Lifestyle.Scoped); 
     container.Register<IBrokeredMessageProcessor, BrokeredMessageProcessor>(Lifestyle.Scoped); 
     container.Verify(); 

     var config = new JobHostConfiguration 
     { 
      JobActivator = new SimpleInjectorJobActivator(container) 
     }; 

     var servicebusConfig = new ServiceBusConfiguration 
     { 
      ConnectionString = CloudConfigurationManager.GetSetting("MyServiceBusConnectionString") 
     }; 

     config.UseServiceBus(servicebusConfig); 
     var host = new JobHost(config); 
     host.RunAndBlock(); 
    } 
} 

i to jest wyzwalane zadanie:

  • Ma tylko jedną zależność: kontener IoC. Ponieważ ta klasa jest częścią mojego katalogu głównego kompozycji, powinno być w porządku.
  • Obsługuje zakres do wyzwalanej funkcji.

    public class TriggeredJob 
    { 
        private readonly Container _container; 
    
        public TriggeredJob(Container container) 
        { 
         _container = container; 
        } 
    
        public async Task TriggeredFunction([ServiceBusTrigger("queueName")] BrokeredMessage message, CancellationToken token) 
        { 
         using (var scope = _container.BeginExecutionContextScope()) 
         { 
          var processor = _container.GetInstance<IBrokeredMessageProcessor>(); 
          await processor.ProcessAsync(message, token); 
         } 
        } 
    } 
    
+0

Czy masz literówkę w swojej metodzie SimpleInjectorBootstrapper? Wydaje się, że w taki sam sposób konfiguruje SingletonDependency i ScopedDependency. –

+0

@ SørenBoisen, Edytowałem swoją odpowiedź, miałeś rację. Zależność o zakresie musi być zarejestrowana jako instancja dla każdego połączenia. – Thomas

+0

Gdzie zaczynasz zakres? Oczekuję wywołania 'container.BeginExecutionContextScope()' jako części twojej odpowiedzi. Bez aktywnego zakresu, rozwiązywanie "BrokeredMessageProcessor" zakończy się niepowodzeniem, ponieważ zależy od zależności o zakresie. Gdzie zaczynasz zakres? – Steven

Powiązane problemy