Criação de fluxos de trabalho, atividades e expressões usando código imperativo
Uma definição de fluxo de trabalho é uma árvore de objetos de atividade configurados. Essa árvore de atividades pode ser definida de várias maneiras, inclusive editando manualmente o XAML ou usando o Designer de Fluxo de Trabalho para produzir XAML. O uso de XAML, no entanto, não é um requisito. As definições de fluxo de trabalho também podem ser criadas programaticamente. Este tópico fornece uma visão geral da criação de definições, atividades e expressões de fluxo de trabalho usando código. Para obter exemplos de como trabalhar com fluxos de trabalho XAML usando código, consulte Serializando fluxos de trabalho e atividades de e para XAML.
Criando definições de fluxo de trabalho
Uma definição de fluxo de trabalho pode ser criada instanciando uma instância de um tipo de atividade e configurando as propriedades do objeto de atividade. Para atividades que não contêm atividades infantis, isso pode ser feito usando algumas linhas de código.
Activity wf = new WriteLine
{
Text = "Hello World."
};
WorkflowInvoker.Invoke(wf);
Nota
Os exemplos neste tópico são usados WorkflowInvoker para executar os fluxos de trabalho de exemplo. Para obter mais informações sobre como invocar fluxos de trabalho, passar argumentos e as diferentes opções de hospedagem disponíveis, consulte Usando WorkflowInvoker e WorkflowApplication.
Neste exemplo, um fluxo de trabalho que consiste em uma única WriteLine atividade é criado. O WriteLine argumento da Text atividade é definido e o fluxo de trabalho é invocado. Se uma atividade contém atividades infantis, o método de construção é semelhante. O exemplo a seguir usa uma Sequence atividade que contém duas WriteLine atividades.
Activity wf = new Sequence
{
Activities =
{
new WriteLine
{
Text = "Hello"
},
new WriteLine
{
Text = "World."
}
}
};
WorkflowInvoker.Invoke(wf);
Usando inicializadores de objeto
Os exemplos neste tópico usam sintaxe de inicialização de objeto. A sintaxe de inicialização de objeto pode ser uma maneira útil de criar definições de fluxo de trabalho no código, pois fornece uma exibição hierárquica das atividades no fluxo de trabalho e mostra a relação entre as atividades. Não há nenhum requisito para usar a sintaxe de inicialização de objeto quando você cria fluxos de trabalho programaticamente. O exemplo a seguir é funcionalmente equivalente ao exemplo anterior.
WriteLine hello = new WriteLine();
hello.Text = "Hello";
WriteLine world = new WriteLine();
world.Text = "World";
Sequence wf = new Sequence();
wf.Activities.Add(hello);
wf.Activities.Add(world);
WorkflowInvoker.Invoke(wf);
Para obter mais informações sobre inicializadores de objetos, consulte Como inicializar objetos sem chamar um construtor (Guia de programação em C#) e Como declarar um objeto usando um inicializador de objetos.
Trabalhando com variáveis, valores literais e expressões
Ao criar uma definição de fluxo de trabalho usando código, esteja ciente de qual código é executado como parte da criação da definição de fluxo de trabalho e qual código é executado como parte da execução de uma instância desse fluxo de trabalho. Por exemplo, o fluxo de trabalho a seguir destina-se a gerar um número aleatório e gravá-lo no console.
Variable<int> n = new Variable<int>
{
Name = "n"
};
Activity wf = new Sequence
{
Variables = { n },
Activities =
{
new Assign<int>
{
To = n,
Value = new Random().Next(1, 101)
},
new WriteLine
{
Text = new InArgument<string>((env) => "The number is " + n.Get(env))
}
}
};
Quando esse código de definição de fluxo de trabalho é executado, a chamada para Random.Next
é feita e o resultado é armazenado na definição de fluxo de trabalho como um valor literal. Muitas instâncias desse fluxo de trabalho podem ser invocadas e todas exibiriam o mesmo número. Para que a geração de números aleatórios ocorra durante a execução do fluxo de trabalho, uma expressão deve ser usada que é avaliada cada vez que o fluxo de trabalho é executado. No exemplo a seguir, uma expressão do Visual Basic é usada com um VisualBasicValue<TResult>arquivo .
new Assign<int>
{
To = n,
Value = new VisualBasicValue<int>("New Random().Next(1, 101)")
}
A expressão no exemplo anterior também pode ser implementada usando uma CSharpValue<TResult> e uma expressão C#.
new Assign<int>
{
To = n,
Value = new CSharpValue<int>("new Random().Next(1, 101)")
}
As expressões C# devem ser compiladas antes que o fluxo de trabalho que as contém seja invocado. Se as expressões C# não forem compiladas, um NotSupportedException será lançado quando o fluxo de trabalho for invocado com uma mensagem semelhante à seguinte: Expression Activity type 'CSharpValue`1' requires compilation in order to run. Please ensure that the workflow has been compiled.
Na maioria dos cenários envolvendo fluxos de trabalho criados no Visual Studio, as expressões C# são compiladas automaticamente, mas em alguns cenários, como fluxos de trabalho de código, as expressões C# devem ser compiladas manualmente. Para obter um exemplo de como compilar expressões C#, consulte a seção Usando expressões C# em fluxos de trabalho de código do tópico Expressões C#.
A VisualBasicValue<TResult> representa uma expressão na sintaxe do Visual Basic que pode ser usada como um valor r em uma expressão e a CSharpValue<TResult> representa uma expressão na sintaxe C# que pode ser usada como um valor r em uma expressão. Essas expressões são avaliadas cada vez que a atividade de contenção é executada. O resultado da expressão é atribuído à variável n
de fluxo de trabalho , e esses resultados são usados pela próxima atividade no fluxo de trabalho. Para acessar o valor da variável n
de fluxo de trabalho em tempo de execução, o ActivityContext é necessário. Isso pode ser acessado usando a seguinte expressão lambda.
Nota
Observe que ambos os códigos são exemplos estão usando C# como a linguagem de programação, mas um usa um VisualBasicValue<TResult> e outro usa um CSharpValue<TResult>. VisualBasicValue<TResult> e CSharpValue<TResult> pode ser usado em projetos Visual Basic e C#. Por padrão, as expressões criadas no designer de fluxo de trabalho correspondem ao idioma do projeto de hospedagem. Ao criar fluxos de trabalho em código, a linguagem desejada fica a critério do autor do fluxo de trabalho.
Nesses exemplos, o resultado da expressão é atribuído à variável n
de fluxo de trabalho , e esses resultados são usados pela próxima atividade no fluxo de trabalho. Para acessar o valor da variável n
de fluxo de trabalho em tempo de execução, o ActivityContext é necessário. Isso pode ser acessado usando a seguinte expressão lambda.
new WriteLine
{
Text = new InArgument<string>((env) => "The number is " + n.Get(env))
}
Para obter mais informações sobre expressões lambda, consulte Expressões do Lambda (referência C#) ou Expressões do Lambda (Visual Basic).
As expressões do Lambda não são serializáveis para o formato XAML. Se for feita uma tentativa de serializar um fluxo de trabalho com expressões lambda, será lançada uma LambdaSerializationException mensagem com a seguinte mensagem: "Este fluxo de trabalho contém expressões lambda especificadas no código. Essas expressões não são serializáveis em XAML. Para tornar seu fluxo de trabalho serializável em XAML, use VisualBasicValue/VisualBasicReference ou ExpressionServices.Convert(lambda). Isso converterá suas expressões lambda em atividades de expressão." Para tornar essa expressão compatível com XAML, use ExpressionServices e Convert, conforme mostrado no exemplo a seguir.
new WriteLine
{
//Text = new InArgument<string>((env) => "The number is " + n.Get(env))
Text = ExpressionServices.Convert((env) => "The number is " + n.Get(env))
}
A VisualBasicValue<TResult> também pode ser usado. Observe que nenhuma expressão lambda é necessária ao usar uma expressão do Visual Basic.
new WriteLine
{
//Text = new InArgument<string>((env) => "The number is " + n.Get(env))
//Text = ExpressionServices.Convert((env) => "The number is " + n.Get(env))
Text = new VisualBasicValue<string>("\"The number is \" + n.ToString()")
}
Em tempo de execução, as expressões do Visual Basic são compiladas em expressões LINQ. Ambos os exemplos anteriores são serializáveis para XAML, mas se o XAML serializado se destinar a ser exibido e editado no designer de fluxo de trabalho, use VisualBasicValue<TResult> para suas expressões. Os fluxos de trabalho serializados que usam ExpressionServices.Convert
podem ser abertos no designer, mas o valor da expressão ficará em branco. Para obter mais informações sobre como serializar fluxos de trabalho para XAML, consulte Serializando fluxos de trabalho e atividades de e para XAML.
Expressões literais e tipos de referência
As expressões literais são representadas nos fluxos de trabalho pela Literal<T> atividade. As seguintes WriteLine atividades são funcionalmente equivalentes.
new WriteLine
{
Text = "Hello World."
},
new WriteLine
{
Text = new Literal<string>("Hello World.")
}
É inválido inicializar uma expressão literal com qualquer tipo de referência, exceto String. No exemplo a seguir, a propriedade de Value uma Assign atividade é inicializada com uma expressão literal usando um List<string>
arquivo .
new Assign
{
To = new OutArgument<List<string>>(items),
Value = new InArgument<List<string>>(new List<string>())
},
Quando o fluxo de trabalho que contém essa atividade é validado, o seguinte erro de validação é retornado: "Literal suporta apenas tipos de valor e o tipo imutável System.String. O tipo System.Collections.Generic.List'1[System.String] não pode ser usado como um literal." Se o fluxo de trabalho for invocado, será lançado um InvalidWorkflowException que contém o texto do erro de validação. Este é um erro de validação porque a criação de uma expressão literal com um tipo de referência não cria uma nova instância do tipo de referência para cada instância do fluxo de trabalho. Para resolver isso, substitua a expressão literal por uma que cria e retorna uma nova instância do tipo de referência.
new Assign
{
To = new OutArgument<List<string>>(items),
Value = new InArgument<List<string>>(new VisualBasicValue<List<string>>("New List(Of String)"))
},
Para obter mais informações sobre expressões, consulte Expressões.
Invocando métodos em objetos usando expressões e a atividade InvokeMethod
A InvokeMethod<TResult> atividade pode ser usada para invocar métodos estáticos e de instância de classes no .NET Framework. Em um exemplo anterior neste tópico, um número aleatório foi gerado usando a Random classe.
new Assign<int>
{
To = n,
Value = new VisualBasicValue<int>("New Random().Next(1, 101)")
}
A InvokeMethod<TResult> atividade também poderia ter sido usada para chamar o NextRandom método da classe.
new InvokeMethod<int>
{
TargetObject = new InArgument<Random>(new VisualBasicValue<Random>("New Random()")),
MethodName = "Next",
Parameters =
{
new InArgument<int>(1),
new InArgument<int>(101)
},
Result = n
}
Como Next não é um método estático, uma instância da Random classe é fornecida para a TargetObject propriedade. Neste exemplo, uma nova instância é criada usando uma expressão do Visual Basic, mas também pode ter sido criada anteriormente e armazenada em uma variável de fluxo de trabalho. Neste exemplo, seria mais simples usar a Assign<T> atividade em vez da InvokeMethod<TResult> atividade. Se a chamada de método finalmente invocada Assign<T> pelas atividades ou InvokeMethod<TResult> for de longa execução, tem uma vantagem, InvokeMethod<TResult> pois tem uma RunAsynchronously propriedade. Quando essa propriedade é definida como true
, o método invocado será executado de forma assíncrona em relação ao fluxo de trabalho. Se outras atividades estiverem em paralelo, elas não serão bloqueadas enquanto o método estiver sendo executado de forma assíncrona. Além disso, se o método a ser invocado não tiver valor de retorno, então InvokeMethod<TResult> é a maneira apropriada de invocar o método.
Argumentos e Atividades Dinâmicas
Uma definição de fluxo de trabalho é criada em código montando atividades em uma árvore de atividades e configurando quaisquer propriedades e argumentos. Os argumentos existentes podem ser vinculados, mas novos argumentos não podem ser adicionados às atividades. Isso inclui argumentos de fluxo de trabalho passados para a atividade raiz. No código imperativo, os argumentos de fluxo de trabalho são especificados como propriedades em um novo tipo de CLR e, em XAML, são declarados usando x:Class
e x:Member
. Como não há nenhum novo tipo de CLR criado quando uma definição de fluxo de trabalho é criada como uma árvore de objetos na memória, os argumentos não podem ser adicionados. No entanto, os argumentos podem ser adicionados a um DynamicActivityarquivo . Neste exemplo, é criado um DynamicActivity<TResult> que usa dois argumentos inteiros, adiciona-os juntos e retorna o resultado. A DynamicActivityProperty é criado para cada argumento, e o resultado da operação é atribuído ao Result argumento do DynamicActivity<TResult>.
InArgument<int> Operand1 = new InArgument<int>();
InArgument<int> Operand2 = new InArgument<int>();
DynamicActivity<int> wf = new DynamicActivity<int>
{
Properties =
{
new DynamicActivityProperty
{
Name = "Operand1",
Type = typeof(InArgument<int>),
Value = Operand1
},
new DynamicActivityProperty
{
Name = "Operand2",
Type = typeof(InArgument<int>),
Value = Operand2
}
},
Implementation = () => new Sequence
{
Activities =
{
new Assign<int>
{
To = new ArgumentReference<int> { ArgumentName = "Result" },
Value = new InArgument<int>((env) => Operand1.Get(env) + Operand2.Get(env))
}
}
}
};
Dictionary<string, object> wfParams = new Dictionary<string, object>
{
{ "Operand1", 25 },
{ "Operand2", 15 }
};
int result = WorkflowInvoker.Invoke(wf, wfParams);
Console.WriteLine(result);
Para obter mais informações sobre atividades dinâmicas, consulte Criando uma atividade em tempo de execução.
Atividades compiladas
As atividades dinâmicas são uma maneira de definir uma atividade que contém argumentos usando código, mas as atividades também podem ser criadas em código e compiladas em tipos. Atividades simples podem ser criadas que derivam de CodeActivity, e atividades assíncronas que derivam de AsyncCodeActivity. Essas atividades podem ter argumentos, valores de retorno e definir sua lógica usando código imperativo. Para obter exemplos de criação desses tipos de atividades, consulte Classe base CodeActivity e Criação de atividades assíncronas.
As atividades derivadas podem NativeActivity definir sua lógica usando código imperativo e também podem conter atividades filhas que definem a lógica. Eles também têm acesso total aos recursos do tempo de execução, como a criação de favoritos. Para obter exemplos de criação de uma NativeActivityatividade baseada em Native, consulte Classe base NativeActivity, Como criar uma atividade e o exemplo Composto personalizado usando atividade nativa .
Atividades que derivam da definição de Activity sua lógica unicamente através do uso de atividades infantis. Essas atividades geralmente são criadas usando o designer de fluxo de trabalho, mas também podem ser definidas usando código. No exemplo a seguir, é definida uma Square
atividade que deriva de Activity<int>
. A Square
atividade tem um único InArgument<T> nome Value
, e define sua lógica especificando uma Sequence atividade usando a Implementation propriedade. A Sequence atividade contém uma WriteLine atividade e uma Assign<T> atividade. Em conjunto, estas três atividades implementam a lógica da Square
atividade.
class Square : Activity<int>
{
[RequiredArgument]
public InArgument<int> Value { get; set; }
public Square()
{
this.Implementation = () => new Sequence
{
Activities =
{
new WriteLine
{
Text = new InArgument<string>((env) => "Squaring the value: " + this.Value.Get(env))
},
new Assign<int>
{
To = new OutArgument<int>((env) => this.Result.Get(env)),
Value = new InArgument<int>((env) => this.Value.Get(env) * this.Value.Get(env))
}
}
};
}
}
No exemplo a seguir, uma definição de fluxo de trabalho que consiste em uma única Square
atividade é invocada usando WorkflowInvoker.
Dictionary<string, object> inputs = new Dictionary<string, object> {{ "Value", 5}};
int result = WorkflowInvoker.Invoke(new Square(), inputs);
Console.WriteLine("Result: {0}", result);
Quando o fluxo de trabalho é invocado, a seguinte saída é exibida no console:
Quadratura do valor: 5
Correspondências da procura: 25