2009-12-22 10 views
23

Ist es möglich, Dependency Injection (DI) mit Windows PowerShell zu verwenden?Abhängigkeitsinjektion mit PowerShell

Meine anfänglichen Experimente deuten darauf hin, dass es nicht ist. Wenn ich versuche, Constructor Injection in einem CmdLet zu verwenden, registriert es sich nicht einmal selbst. Mit anderen Worten, ist dies nicht möglich:

[Cmdlet(VerbsDiagnostic.Test, "Ploeh")] 
public class PloehCmdlet : Cmdlet 
{ 
    public PloehCmdlet(IFoo foo) 
    { 
     if (foo == null) 
     { 
      throw new ArgumentNullException("foo"); 
     } 

     // save foo for later use 
    } 

    protected override void ProcessRecord() 
    { 
     this.WriteObject("Ploeh"); 
    } 
} 

Wenn ich einen Standard-Konstruktor hinzufügen, können die CmdLet registriert und verwendet werden, aber ohne den Standard-Konstruktor, es ist einfach nicht vorhanden.

Ich weiß, ich könnte einen Service Locator verwenden, um meine Abhängigkeiten abzurufen, aber ich denke, dass ein Anti-Muster so nicht so gehen möchte.

Ich hätte gehofft, dass die PowerShell-API eine Art "Factory" -Knopf hätte, der WCFs ServiceHostFactory ähnelt, aber wenn, dann kann ich sie nicht finden.

+0

Ich bin auch neugierig. –

+1

Es fällt mir schwer, einen guten Anwendungsfall dafür zu finden. – yfeldblum

+0

Ich benutze einen Service Locator. :) –

Antwort

10

Eine Instanz der Cmdlet-Klasse wird jedes Mal aus einem leeren Konstruktor erstellt, wenn das Cmdlet in PowerShell verwendet wird. Sie haben keine Kontrolle darüber, welchen Konstruktor PowerShell auswählen wird, so dass Sie das, was Sie vorschlagen, nicht direkt ausführen können (und es fällt mir wirklich schwer, sich vorzustellen, warum Sie das möchten). Also ist die einfache Antwort auf diese Frage NEIN.

Um einen ähnlichen Affekt zu erreichen, können Sie eine Schnittstelle erstellen, die wie ein Cmdlet aussieht (hat BeginProcessing/EndProcessing/ProcessRecord/StopProcessing) und eine Reihe von Cmdlets verwenden, die dünne Wrapper über dem echten Code sind. IMHO wäre das ein zu komplizierter Ansatz.

Ich sehe wirklich nicht, warum Sie das versuchen. Kannst du etwas mehr über das Szenario erklären?

+12

Testbarkeit wäre mein Hauptgrund, aber jeder Grund, DI zu verwenden gilt gleichermaßen für Cmdlets - warum sollten sie anders als der Rest meines Codes sein? –

3

Wenn PSCmdlet als Basisklasse verwendet wird, muss RunSpace ausgeführt werden, und Sie können nur den Befehl angeben, der als Zeichenfolge ausgeführt werden soll. Ein Beispiel finden Sie in this link.

Ich wechselte zurück zu Cmdlet als eine Basisklasse und verwendete Property-Injection, um die Abhängigkeiten festzulegen. Nicht die sauberste Lösung, aber es hat für mich funktioniert. Die nette Sache über Cmdlets als Basisklasse ist, dass man es direkt aus dem Gerät zu testen wie folgt aufrufen können:

 var cmdlet = new MyCmdlet { 
          Dependency = myMockDependencyObject 
         }; 

     var result = cmdlet.Invoke().GetEnumerator(); 

     Assert.IsTrue(result.MoveNext()); 
+1

Ja, ich habe darüber nachgedacht, aber ich versuche, die Verwendung des Cmdlet zu vermeiden, ich benutze den aktuellen Sitzungsstatus der Powershell. Also das ist nicht viel von einer Option für mich :( –

2

auf Antwort-Start Automatisieren zu erweitern:

Im Wesentlichen werden Sie Ihre IView-Schnittstelle haben, die Wenn Sie die Operationen für das PSCmdlet definieren (WriteObject, WriteError, WriteProgress usw.), verfügen Sie über die Implementierung dieser Sicht, bei der es sich um das eigentliche Commandlet handelt.

Auch Sie haben einen Controller, der die tatsächliche Funktionalität ist. Auf dem Konstruktor erhält der Controller einen IProvider (welcher der zu überprüfende ist) und eine IView. Der Provider führt den Aufruf an den Provider durch und schreibt die Ergebnisse der IView, die das IView (Powershell Commandlet) widerspiegeln.

Während der Initialisierung der Ansicht erstellen Sie einen Controller, übergeben sich selbst (die IView) und einen Provider, und dann wird die Operation gegen den Controller ausgeführt.

Mit diesem Ansatz ist Ihr Cmdlet eine dünne Schicht, die keine Geschäftslogik ausführt, und alles ist auf Ihrem Controller, der eine Komponente ist, die testbar ist.

0

Obwohl Sie keine Konstruktorinjektion dafür verwenden können, können Sie das Cmdlet selbst verwenden. Rufen Sie einen ersten Aufruf für einen Parametersatz an, um sie zu initialisieren, speichern Sie die relevanten Informationen im aktuellen Sitzungsstatus, und führen Sie bei nachfolgenden Aufrufen den gespeicherten Wert aus dem Sitzungsstatus zurück.

Hier habe ich eine einzige Zeichenfolge, message verwendet, um den gespeicherten Wert darzustellen; aber natürlich können Sie so viele Parameter/Typen haben, die Sie mögen.

Hinweis: Die folgende C# ist in PowerShell verpackt, so dass Sie das Ganze direkt in PS testen können.

$cs = @' 
using System.Management.Automation; 

[Cmdlet(VerbsDiagnostic.Test, "Ploeh", DefaultParameterSetName = "None")] 
public class PloehCmdlet : PSCmdlet 
{ 
    const string InitialiseParameterSetName = "Initialise"; 
    const string MessageVariable = "Test_Ploeh_Message_39fbe50c_25fc_48b1_8348_d155cad99e93"; //since this is held as a variable in the session state, make sure the name will not clash with any existing variables 

    [Parameter(Mandatory=true, ParameterSetName = InitialiseParameterSetName)] 
    public string InitialiseMessage 
    { 
     set { SaveMessageToSessionState(value); } 
    } 

    protected override void ProcessRecord() 
    { 
     if (this.ParameterSetName != InitialiseParameterSetName) //do not run the cmdlet if we're just initialising it 
     { 
      this.WriteObject(GetMessageFromSessionState()); 
      base.ProcessRecord(); 
     } 
    } 

    void SaveMessageToSessionState(string message) 
    { 
     this.SessionState.PSVariable.Set(MessageVariable, message); 
    } 
    string GetMessageFromSessionState() 
    { 
     return (string)this.SessionState.PSVariable.GetValue(MessageVariable); 
    } 

} 
'@ 
#Trick courtesy of: http://community.idera.com/powershell/powertips/b/tips/posts/compiling-binary-cmdlets 
$DLLPath = Join-Path $env:temp ('CSharpPSCmdLet{0:yyyyMMddHHmmssffff}.dll' -f (Get-Date)) 
Add-Type -OutputAssembly $DLLPath -Language 'CSharp' -ReferencedAssemblies 'System.Management.Automation.dll' -TypeDefinition $cs 
Import-Module -Name $DLLPath -Force -Verbose 

#demo commands 

Test-Ploeh -InitialiseMessage 'this is a test' 
Test-Ploeh 
Test-Ploeh 

Test-Ploeh -InitialiseMessage 'change value' 
Test-Ploeh 
Test-Ploeh 

"NB: Beware, your value can be accessed/amended outside of your cmdlet: $Test_Ploeh_Message_39fbe50c_25fc_48b1_8348_d155cad99e93" 
$Test_Ploeh_Message_39fbe50c_25fc_48b1_8348_d155cad99e93 = "I've changed my mind" 
Test-Ploeh 

Ausgabe

VERBOSE: Loading module from path 'C:\Users\UserNa~1\AppData\Local\Temp\CSharpPSCmdLet201711132257130536.dll'. 
VERBOSE: Importing cmdlet 'Test-Ploeh'. 
this is a test 
this is a test 
change value 
change value 
NB: Beware, your value can be accessed/amended outside of your cmdlet: change value 
I've changed my mind 
Verwandte Themen