RequiredArgumentAttribute对InOutArguments无法正常工作?

时间:2014-11-21 08:22:03

标签: xaml workflow-foundation-4

我已经实施了WF4活动。它有一些必需的InArguments,一个需要InOutArgument和一个可选的OutArgument。

由于活动是在xaml中实现的(即使用工作流设计器),我必须搜索有关如何在xaml中将属性标记为“必需”的信息(类似于C#中的[RequiredArgument])并找到以下链接该怎么做:

http://msdn.microsoft.com/en-us/library/ee358733(v=vs.100).aspx

<x:Property Name="Operand1" Type="InArgument(x:Int32)">
  <x:Property.Attributes>
    <RequiredArgumentAttribute />
  </x:Property.Attributes>
</x:Property>

这适用于所有InArguments。但是在为它实现测试时,我发现它与InOutArguments无法正常工作。如果我在测试中使用WorkflowInvoker.Invoke运行我的xaml活动,而不提供任何参数,则会出现ArgumentException抱怨所有必需的InArguments,但不会抱怨所需的InOutArgument。如果我使用所有必需的InArguments运行活动,但没有所需的InOutArgument,则不会抛出ArgumentException

这可能是Workflow Foundation中的错误吗?

有趣的是,如果我在工作流中使用此活动而不提供任何参数,我会在工作流设计器中看到这个红色感叹号,它会告诉我哪些参数需要输入。这里提到了InOutArgument ,这是预期的行为。

1 个答案:

答案 0 :(得分:1)

Workflow引擎检查RequiredArgumentAttribute是否有一个活动调用另一个活动。您可以通过创建CodeActivity来查看此操作。使用断点,您可以使用CacheMetadata方法中的调试器停止监视,但在调用Execute方法之前会抛出异常。

但是,正如您所指出的,使用WorkflowInvoker直接调用具有InOut / Out参数的活动不会导致InOut / Out必需参数的异常。 RequiredArgumentAttribute要求参数具有绑定。它不要求参数具有值。这可以通过将引用变量与空值绑定来看出。 WorkflowInvoker类自动绑定到InOut / Out参数,以捕获其返回IDictionary对象的输出值。

为了为所需的InOut参数编写单元测试,您必须创建一个将自身绑定到您正在测试的活动的活动。以下代码段是我最近为此确切方案编写的代码。我正在使用CodePlex中的WorkflowInvokerTest类来封装WorkflowInvoker。

public abstract class ActivityTest
{
    private class ArgumentTester : NativeActivity
    {
        public Collection<Variable> variables = new Collection<Variable>();
        public Activity Test;

        protected override void CacheMetadata(NativeActivityMetadata metadata)
        {
            base.CacheMetadata(metadata);
            metadata.AddImplementationChild(Test);
            foreach (var item in variables)
            {
                metadata.AddImplementationVariable(item);
            }
        }

        protected override void Execute(NativeActivityContext context)
        {
            context.ScheduleActivity(Test);
        }
    }

    protected WorkflowInvokerTest host;

    protected void TestForRequiredArgument(string argName)
    {
        var d = (IDictionary<string, object>)host.InArguments;
        d.Remove(argName);

        try
        {
            dynamic activityToTest = System.Activator.CreateInstance(host.Activity.GetType());

            ArgumentTester tester = new ArgumentTester
            {
                Test = activityToTest
            };

            foreach (var item in d)
            {
                Type t = typeof(Variable<>).MakeGenericType(item.Value.GetType());
                Variable v = (Variable)Activator.CreateInstance(t, item.Key + "_Var", item.Value);
                tester.variables.Add(v);
                var prop = host.Activity.GetType().GetProperty(item.Key);
                object arg = Activator.CreateInstance(prop.PropertyType, v);
                prop.SetValue(activityToTest, arg);
            }

            var h = new WorkflowInvokerTest(tester);
            h.TestActivity();

            Assert.Fail("An exception should have been thrown.");
        }
        catch (InvalidWorkflowException ex)
        {
            Assert.IsTrue(ex.Message.Contains("'" + argName + "'"));
        }
        finally
        {
            host.Tracking.Trace();
        }
    }
}

然后我写了一个这样的测试:

[TestClass]
public class WageTest : ActivityTest
{
    [TestInitialize]
    public void InitializeTest()
    {
        host = new WorkflowInvokerTest(new WageActivity());
        host.InArguments.Wage = 2000;
        host.InArguments.IsFifthQuarter = false;
    }

    [TestMethod]
    public void WageArgumentIsRequired()
    {
        base.TestForRequiredArgument("Wage");
    }

    [TestMethod]
    public void IsFifthQuarterArgumentIsRequired()
    {
        base.TestForRequiredArgument("IsFifthQuarter");
    }

    //...
}

可以用泛型清理一下。我还在努力,但你明白了。