2009-12-22 24 views
23

¿Es posible utilizar Inyección de Dependencia (DI) con Windows PowerShell?Inyección de Dependencia con PowerShell

Mis experimentos iniciales sugieren que no lo es. Si intento usar Constructor Injection en un CmdLet, ni siquiera se registra. En otras palabras, esto no es posible:

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

Si añado un constructor por defecto, el cmdlet puede ser registrado y utilizado, pero sin el constructor por defecto, simplemente no es disponible.

Sé que podría utilizar un Service Locator para recuperar mis dependencias, pero considero que es un antipatrón así que no quiero ir por ese camino.

Hubiera esperado que la API de PowerShell tuviese algún tipo de gancho 'Factory' similar al ServiceHostFactory de WCF, pero si lo hay, no puedo encontrarlo.

+0

Soy curioso también. –

+1

Estoy teniendo dificultades para encontrar un buen caso de uso para esto. – yfeldblum

+0

Estoy usando un localizador de servicios. :) –

Respuesta

10

Una instancia de la clase cmdlet se crea a partir de un constructor vacío cada vez que se utiliza el cmdlet en PowerShell. No tiene control sobre qué constructor elegirá PowerShell, por lo que no puede hacer lo que propone de una manera directa (y realmente me cuesta imaginar por qué lo desea). Entonces, la respuesta simple a esta pregunta es NO.

Para lograr un efecto similar, puede crear una interfaz que se parece a un cmdlet (tiene BeginProcessing/EndProcessing/ProcessRecord/StopProcessing) y se usa para llenar un grupo de cmdlets que son envoltorios delgados sobre el código real. En mi humilde opinión, este sería un enfoque demasiado complicado.

Realmente no veo por qué estás tratando de hacer esto. ¿Podría explicar un poco más sobre el escenario?

+12

La capacidad de prueba sería mi principal razón, pero cualquier razón para usar DI se aplica igualmente bien a los cmdlets: ¿por qué deberían ser diferentes al resto de mi código? –

3

El uso de PSCmdlet como clase base requiere la ejecución de un RunSpace y solo le permite especificar el comando para ejecutarlo como una cadena. Ver this link para un ejemplo.

Volví a Cmdlet como una clase base y utilicé la inyección de propiedades para configurar las dependencias. No es la solución más limpia, pero funcionó para mí. Lo bueno de Cmdlet como clase base es que se puede invocar directamente desde la unidad de prueba como esta:

 var cmdlet = new MyCmdlet { 
          Dependency = myMockDependencyObject 
         }; 

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

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

Sí, estaba pensando en eso, pero estoy tratando de evitar el uso del Cmdlet, estoy usando el estado de sesión actual de Powershell. Así que esto no es una gran opción para mí :( –

2

para expandir en la puesta en Automatización de respuesta:

Esencialmente, usted tendrá su interfaz IView que definiría las operaciones para el PSCmdlet (WriteObject, WriteError, WriteProgress, etc.) tendrá su implementación de esa vista que sería el Commandlet real.

También tendrá un controlador, que es la función real. En el constructor, el Controlador recibe un proveedor de IP (que es el que desea simular) y una IView. el proveedor realiza la llamada al proveedor y escribe los resultados del IView, que se reflejará en IView (Powershell Commandlet).

Durante la inicialización de la Vista creará un Controlador, pasará a lo largo de sí mismo (la IView) y un Proveedor, y luego realizará la operación contra el controlador.

Con este enfoque, su Cmdlet es una capa delgada que no realiza ninguna lógica comercial, y todo está en su controlador, que es un componente que se puede probar.

0

Aunque no puede usar la inyección de constructor para esto, podría usar el cmdlet mismo. Haga una primera llamada a él en un conjunto de parámetros para inicializar, almacene la información relevante en el estado de sesión actual, y haga que las llamadas posteriores recuperen el valor almacenado del estado de la sesión.

Aquí he usado una sola cadena, message para representar el valor almacenado; pero obviamente puedes tener tantos parámetros/de cualquier tipo que te guste.

NB: El siguiente C# está envuelto en PowerShell, por lo que puede probarlo todo directamente en PS.

$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 

Ejemplo de salida

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 
Cuestiones relacionadas