2015-05-19 12 views
51

Das Problem ist, dass das Azure WebJobs SDK nur öffentliche statische Methoden als Job-Einstiegspunkte unterstützt, was bedeutet, dass es keine Möglichkeit gibt, die Konstruktor-/Eigenschafteninjektion zu implementieren.Dependency-Injektion mit Azure WebJobs SDK?

Ich kann nichts zu diesem Thema in offiziellen WebJobs SDK Dokumentation/Ressourcen finden. Die einzige Lösung, auf die ich gestoßen bin, basiert auf dem Service Locator (Anti) -Muster, das in diesem Beitrag beschrieben wird here.

Gibt es eine gute Möglichkeit, "richtige" Abhängigkeitsinjektion für Projekte zu verwenden, die auf Azure WebJobs SDK basieren?

+0

Können Sie ein wenig näher erläutern? Versuchen Sie, einen "pro-Job" -Bereich für Objekte zu erhalten, die aus Ihrem Container kommen, ähnlich einer pro-Anfrage in einer Standard-Web-App? – Veatch

+0

Ja. Ich habe die Frage bearbeitet. Bitte beachten Sie den Blog-Post, den ich für weitere Details verlinkt habe. –

+0

Wenn Sie davon ausgehen, dass Sie eine Konsolenanwendung ausführen, sollten Sie DI wie in jeder Konsolenanwendung verwenden. Davon abgesehen ist die einzige Methode, die ich gefunden habe, etwas Ähnliches wie das Anti-Pattern im Blog-Post zu verwenden. Ich kann ein Beispiel mit Ninject auf Anfrage geben. – lopezbertoni

Antwort

77

Azure WebJobs SDK unterstützt jetzt Instanzmethoden. Wenn Sie dies mit einem benutzerdefinierten IJobActivator kombinieren, können Sie DI verwenden.

Erstellen Sie zunächst das benutzerdefinierte IJobActivator, die einen Job-Typ mit Ihrem bevorzugten DI-Container auflösen kann:

public class MyActivator : IJobActivator 
{ 
    private readonly IUnityContainer _container; 

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

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

Sie diese Klasse müssen sich registrieren um einen benutzerdefinierten JobHostConfiguration mit:

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

Dann Sie kann eine einfache Klasse mit Instanzmethoden für Ihre Jobs verwenden (hier verwende ich die Constructor-Injection-Funktion von 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

Großartig! Dadurch müssen alle Abhängigkeiten innerhalb von Job-Funktionen nicht manuell aufgelöst werden. Schade, dass du nicht früher aufgetaucht bist - jetzt muss ich eine ganze Menge Sachen umschreiben. –

+2

Ich möchte darauf hinweisen, dass dies nur Unterstützung von v 1.0.1 und höher der Bibliothek ist. – VeldMuijz

+3

Muss für einen ausgelösten Job (ohne Warteschlangen usw.) die Funktion manuell ausgelöst werden, um den Jobaktivator zu verwenden? 'CallMethod (...)'? – kamranicus

4

Ich habe ein paar Muster verwendet, die auf dem Konzept der Kindcontainer/Scopes beruhen (abhängig von der Terminologie Ihres IoC-Containers Ihrer Wahl). Ich bin mir nicht sicher, welche es unterstützt, aber ich kann Ihnen sagen, dass StructureMap 2.6.x und AutoFac tun.

Die Idee besteht darin, einen untergeordneten Bereich für jede eingehende Nachricht hochzufahren, einen beliebigen Kontext für diese Anforderung einzufügen, das Objekt der obersten Ebene aus dem untergeordneten Bereich aufzulösen und dann den Prozess auszuführen.

Hier ist ein verallgemeinerter Code, der es mit AutoFac zeigt. Es führt eine direkte Auflösung aus dem Container aus, ähnlich wie das Anti-Pattern, das Sie vermeiden möchten, aber es wurde an einer Stelle isoliert.

In diesem Fall verwendet es einen ServiceBusTrigger, um den Job auszulösen, könnte aber alles Mögliche sein - ein Job-Host könnte möglicherweise eine Liste von diesen für die verschiedenen Warteschlangen/Prozesse haben.

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

Diese Methode wird von allen Instanzen der oben genannten Methoden aufgerufen. Es wird die Erstellung des untergeordneten Bereichs in einem Verwendungsblock umbrochen, um sicherzustellen, dass die Objekte bereinigt werden. Dann würden alle Objekte, die je Anforderung variieren und den Kontext enthalten, der von anderen Abhängigkeiten verwendet wird (Benutzer-/Client-Informationen usw.), erstellt und in den untergeordneten Container (in diesem Beispiel IRequestContext) eingefügt. Schließlich wird die Komponente, die die Arbeit ausführt, vom untergeordneten Container aufgelöst.

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

Danke! Dies ist ein solider Ansatz, auf dessen Basis ich meine ursprüngliche Infrastruktur zur Abhängigkeitsinjektion implementiert habe. Jetzt, da wir ein aktualisiertes SDK haben, verwende ich nur die eingebaute Methode, die einwandfrei funktioniert. –

+0

@veatch Sie müssen keinen neuen Container-Builder erstellen und den Bereich aktualisieren. Mit _container.BeginLifetimeScope() können Sie jetzt neue Abhängigkeiten registrieren. –

11

So habe ich das Scoping mit dem neuen SDK durchgeführt. Verwendung des IJobaktivators wie von Alexander Molenkamp beschrieben.

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

können Sie die Ihre benutzerdefinierten MessagingProvider in Ihrem JobHostConfiguration verwenden wie

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

Nur zur Klarstellung, dies wird uns einen Job-Bereich geben? Aber es ist spezifisch für Jobs, die auf Warteschlangenauslösern für Azure Service Bus basieren? –

+0

Dies wird einen Bereich pro Nachricht einrichten. Dieses Beispiel ist für ServiceBus-Trigger gedacht, aber ich denke, dass der MessageProcessor für Erweiterungen auch für andere Trigger offen ist. –

+0

Was ist 'BeginExecutionContextScope()'? Ist diese Methode spezifisch für eine Implementierung eines DI-Containers? Was wäre das Äquivalent für Unity? – UserControl

8

Nach meinem own question über die Frage, wie Scoping zu handhaben ...Ich bin gerade auf diese Lösung gekommen: Ich denke nicht, dass dies ideal ist, aber ich konnte im Moment keine andere Lösung finden.

In meinem Beispiel habe ich es mit ServiceBusTrigger zu tun.

Wie ich SimpleInjector verwende, sieht die Umsetzung der IJobActivator Schnittstelle wie folgt aus:

public class SimpleInjectorJobActivator : IJobActivator 
{ 
    private readonly Container _container; 

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

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

Hier habe ich mit Triggered webjobs zu tun habe.

So habe ich zwei Abhängigkeiten:

  • A Singleton:

    public interface ISingletonDependency { } 
    
    public class SingletonDependency : ISingletonDependency { } 
    
  • Und ein anderer, der nur die Zeit, um meine Funktion zum Leben brauchen ausgelöst wird:

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

Also um zu hav e ein Prozess, der unabhängig vom Webjob abläuft. Ich habe meinen Prozess in eine Klasse gekapselt:

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) 
    { 
     ... 
    } 
} 

So, jetzt, wenn die webjob beginnt, muss ich meine Abhängigkeiten registrieren abhängig Geltungsbereiche:

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

und das ist der getriggerten Job:

  • Nur eine Abhängigkeit haben: den IoC-Container. Da diese Klasse Teil meines Kompositionswurzels ist, sollte sie in Ordnung sein.
  • Es behandelt den Bereich in die ausgelöste Funktion.

    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

Haben Sie einen Tippfehler in Ihrer SimpleInjectorBootstrapper-Methode? Es scheint, SingletonDependency und ScopedDependency in genau derselben Weise zu konfigurieren. –

+0

@ SørenBoisen, ich habe meine Antwort bearbeitet, du hattest Recht. Die Bereichsabhängigkeit muss als eine Instanz pro Aufruf registriert werden. – Thomas

+0

Wo starten Sie den Umfang? Ich erwarte einen Aufruf von 'container.BeginExecutionContextScope()' als Teil Ihrer Antwort. Ohne einen aktiven Bereich schlägt das Auflösen von "BrokeredMessageProcessor" fehl, da dies von einer Bereichsabhängigkeit abhängt. Wo fangen Sie den Umfang an? – Steven

Verwandte Themen