使用PowerShell进行依赖注入

时间:2009-12-22 10:46:20

标签: powershell dependency-injection

是否可以在Windows PowerShell中使用依赖注入(DI)?

我的初步实验表明事实并非如此。如果我尝试在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,但是没有默认的构造函数,它根本就不可用。

我知道我可以使用服务定位器来检索我的依赖项,但我认为这是一种反模式,所以不希望这样。

我希望PowerShell API有一些类似于WCF的ServiceHostFactory的'Factory'钩子,但是如果有的话,我找不到它。

4 个答案:

答案 0 :(得分:10)

每次在PowerShell中使用cmdlet时,都会从空构造函数创建cmdlet类的实例。您无法控制PowerShell将选择哪个构造函数,因此您无法以直接的方式执行您提出的建议(我真的很难想象您为什么要这样做)。所以这个问题的简单答案是否定的。

为了实现类似的效果,您可以创建一个看起来像cmdlet的接口(具有BeginProcessing / EndProcessing / ProcessRecord / StopProcessing)并用于填充一堆cmtrats,这些cmdlet是真实代码上的瘦包装器。恕我直言,这将是一个过于复杂的方法。

我真的不明白你为什么要这样做。你能解释一下这个场景吗?

答案 1 :(得分:3)

使用PSCmdlet作为基类需要执行RunSpace,并且只允许您指定要作为字符串执行的命令。有关示例,请参阅this link

我切换回Cmdlet作为基类,并使用Property注入来设置依赖项。不是最干净的解决方案,但它对我有用。关于Cmdlet作为基类的好处是你可以直接从单元测试中调用它,如下所示:

        var cmdlet = new MyCmdlet {
                             Dependency = myMockDependencyObject
                         };

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

        Assert.IsTrue(result.MoveNext());

答案 2 :(得分:2)

展开 - 自动回答:

基本上你将有你的IView接口来定义PSCmdlet(WriteObject,WriteError,WriteProgress等)的操作,你将实现那个View实际的Commandlet。

您还将拥有一个Controller,它是Actual functionallity。在构造函数上,Controller接收一个IProvider(你想要模拟的那个)和一个IView。提供者执行对提供者的调用并将结果写入IView,这将反映在IView(Powershell Commandlet)上。

在View初始化期间,您将创建一个Controller,传递它自己(IView)和一个Provider,然后它将对控制器执行操作。

使用这种方法,您的Cmdlet是一个不执行任何业务逻辑的薄层,而且一切都在您的控制器上,这是一个可测试的组件。

答案 3 :(得分:0)

虽然您不能使用构造函数注入,但您可以使用cmdlet本身。在一个参数集上首次调用它以初始化,将相关信息存储在当前会话状态,然后让后续调用将存储的值从会话状态拉回。

这里我使用了一个字符串> Enter the list containing the data: Traceback (most recent call last): > File "make_histo.py", line 9, in <module> > df.plot(kind="bar") File "/usr/local/lib/python2.7/dist-packages/pandas/plotting/_core.py", > line 2627, in __call__ > sort_columns=sort_columns, **kwds) File "/usr/local/lib/python2.7/dist-packages/pandas/plotting/_core.py", > line 1869, in plot_frame > **kwds) File "/usr/local/lib/python2.7/dist-packages/pandas/plotting/_core.py", > line 1694, in _plot > plot_obj.generate() File "/usr/local/lib/python2.7/dist-packages/pandas/plotting/_core.py", > line 243, in generate > self._compute_plot_data() File "/usr/local/lib/python2.7/dist-packages/pandas/plotting/_core.py", > line 352, in _compute_plot_data > 'plot'.format(numeric_data.__class__.__name__)) TypeError: Empty 'DataFrame': no numeric data to plot 来表示存储的值;但显然你可以拥有任何类型的参数/你喜欢的任何类型。

注意:下面的C#包含在PowerShell中,因此您可以直接在PS中测试整个内容。

message
$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