Внедрение зависимостей с помощью PowerShell - PullRequest
24 голосов
/ 22 декабря 2009

Можно ли использовать Dependency Injection (DI) с Windows PowerShell?

Мои эксперименты показывают, что это не так. Если я пытаюсь использовать Constructor Injection в CmdLet, он даже не регистрируется. Другими словами, это невозможно:

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

Если я добавлю конструктор по умолчанию, CmdLet можно зарегистрировать и использовать, но без конструктора по умолчанию он просто недоступен.

Я знаю, что могу использовать Service Locator для извлечения моих зависимостей, но я считаю, что это анти-паттерн, поэтому я не хочу идти по этому пути.

Я бы надеялся, что в API-интерфейсе PowerShell есть своего рода «фабричный» хук, похожий на WCH ServiceHostFactory, но если он есть, я не могу его найти.

Ответы [ 4 ]

10 голосов
/ 23 декабря 2009

Экземпляр класса командлета создается из пустого конструктора каждый раз, когда командлет используется в PowerShell. У вас нет контроля над тем, какой конструктор PowerShell выберет, поэтому вы не можете сделать то, что предлагаете, простым способом (и мне действительно трудно представить, почему вы этого хотите). Таким образом, простой ответ на этот вопрос - НЕТ.

Чтобы добиться аналогичного эффекта, вы можете создать интерфейс, похожий на командлет (имеет BeginProcessing / EndProcessing / ProcessRecord / StopProcessing), и использовать его для заполнения группы командлетов, которые представляют собой тонкие оболочки для реального кода. ИМХО, это был бы слишком сложный подход.

Я действительно не понимаю, почему вы пытаетесь это сделать. Не могли бы вы объяснить немного больше о сценарии?

3 голосов
/ 11 декабря 2012

Использование PSCmdlet в качестве базового класса требует выполнения RunSpace и позволяет указать только команду для выполнения в виде строки. См. эту ссылку для примера.

Я вернулся к Cmdlet в качестве базового класса и использовал внедрение свойств для установки зависимостей. Не самое чистое решение, но оно сработало для меня. Хорошая особенность Cmdlet как базового класса заключается в том, что вы можете вызывать его непосредственно из модульного теста, например:

        var cmdlet = new MyCmdlet {
                             Dependency = myMockDependencyObject
                         };

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

        Assert.IsTrue(result.MoveNext());
2 голосов
/ 14 марта 2013

Развернуть при запуске Автоматизация ответа:

По сути, у вас будет интерфейс IView, который будет определять операции для PSCmdlet (WriteObject, WriteError, WriteProgress и т. Д.), У вас будет реализация этого представления, которое будет фактическим Commandlet.

Также у вас будет контроллер, который является фактической функциональностью. В конструкторе Контроллер получает IProvider (который вы хотите смоделировать) и IView. провайдер выполняет вызов к провайдеру и записывает результаты в IView, которые отражаются в IView (Powershell Commandlet).

Во время инициализации Представления вы создадите Контроллер, передадите себя (IView) и Провайдера, а затем он выполнит операцию с контроллером.

При таком подходе ваш командлет представляет собой тонкий слой, который не выполняет никакой бизнес-логики, и все находится на вашем контроллере, который является тестируемым компонентом.

0 голосов
/ 14 ноября 2017

Хотя вы не можете использовать инжектор конструктора для этого, вы можете использовать сам командлет. Выполните первый вызов для него с одним параметром, установленным для инициализации, сохраните соответствующую информацию в текущем состоянии сеанса, а последующие вызовы извлекут сохраненное значение из состояния сеанса.

Здесь я использовал одну строку, message для представления сохраненного значения; но, очевидно, вы можете иметь столько параметров / любых типов, которые вам нравятся.

Примечание: приведенный ниже C # обернут в PowerShell, так что вы можете протестировать все это непосредственно в 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 

Пример вывода

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
...