Compartilhar via


Criando 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 XAML ou usando o Designer de Fluxo de Trabalho para produzir XAML. O uso de XAML, no entanto, não é um requisito. 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 criando uma instância de um tipo de atividade e configurando as propriedades do objeto de atividade. Para atividades que não contêm atividades filho, isso pode ser feito usando algumas linhas de código.

Activity wf = new WriteLine
{
    Text = "Hello World."
};

WorkflowInvoker.Invoke(wf);

Observação

Os exemplos neste tópico usam 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 contiver atividades filhos, o método de criação será 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 a 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 porque 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 objeto, consulte Como inicializar objetos sem chamar um construtor (guia de programação em C#) e como declarar um objeto usando um inicializador de objeto.

Trabalhando com variáveis, valores literais e expressões

Ao criar uma definição de fluxo de trabalho usando código, lembre-se de qual código é executado como parte da criação da definição do 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 exibem o mesmo número. Para que a geração de número aleatório ocorra durante a execução do fluxo de trabalho, uma expressão deve ser usada que seja avaliada sempre que o fluxo de trabalho for executado. No exemplo a seguir, uma expressão do Visual Basic é usada com um VisualBasicValue<TResult>.

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 um 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 erro será gerado quando o fluxo de trabalho for invocado com uma mensagem semelhante à seguinte: NotSupportedException 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 em fluxos de trabalho baseados em código, as expressões C# precisam ser compiladas manualmente. Para obter um exemplo de como compilar expressões em C#, consulte a seção Usando expressões C# na seção fluxos de trabalho de código do tópico Expressões C# .

Um VisualBasicValue<TResult> representa uma expressão na sintaxe do Visual Basic que pode ser usada como um r-value em uma expressão e representa uma CSharpValue<TResult> expressão na sintaxe C# que pode ser usada como um r-value em uma expressão. Essas expressões são avaliadas sempre que a atividade de contenção é executada. O resultado da expressão é atribuído à variável nde fluxo de trabalho e esses resultados são usados pela próxima atividade no fluxo de trabalho. Para acessar o valor da variável de fluxo de trabalho n em tempo de execução, é necessário ActivityContext. Isso pode ser acessado usando a expressão lambda a seguir.

Observação

Observe que esses dois códigos são exemplos que 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 do Visual Basic e do 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 no código, o idioma desejado fica a critério do autor do fluxo de trabalho.

Nesses exemplos, o resultado da expressão é atribuído à variável nde fluxo de trabalho e esses resultados são usados pela próxima atividade no fluxo de trabalho. Para acessar o valor da variável de fluxo de trabalho n em tempo de execução, é necessário ActivityContext. Isso pode ser acessado usando a expressão lambda a seguir.

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 Lambda (referência em C#) ou Expressões Lambda (Visual Basic).

As expressões 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á gerada uma LambdaSerializationException mensagem a seguir: "Esse 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 serializável a XAML de seu fluxo de trabalho, 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))
}

Um 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 for destinado a ser exibido e editado no designer de fluxo de trabalho, use VisualBasicValue<TResult> para suas expressões. 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

Expressões literais são representadas em fluxos de trabalho pela Literal<T> atividade. WriteLine As atividades a seguir 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 Assign de uma atividade Value é inicializada com uma expressão literal usando um List<string>.

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 só dá suporte a tipos de valor e ao tipo imutável System.String. O tipo System.Collections.Generic.List'1[System.String] não pode ser usado como literal." Se o fluxo de trabalho for invocado, será lançada uma InvalidWorkflowException com o texto do erro de validação. Esse é 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 pode ter sido usada para chamar o Next método da Random 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 invocada por uma das atividades Assign<T> ou InvokeMethod<TResult> for de execução prolongada, InvokeMethod<TResult> tem uma vantagem, pois ela possui uma propriedade RunAsynchronously. Quando essa propriedade for 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 em execução assíncrona. Além disso, se o método a ser invocado não tiver valor retornado, será InvokeMethod<TResult> a maneira apropriada de invocar o método.

Argumentos e atividades dinâmicas

Uma definição de fluxo de trabalho é criada no código montando atividades em uma árvore de atividades e configurando quaisquer propriedades e argumentos. Os argumentos existentes podem ser associados, 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 CLR e, em XAML, são declarados usando x:Class e x:Member. Como não há nenhum novo tipo 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 DynamicActivity. Neste exemplo, é criado um DynamicActivity<TResult> que usa dois argumentos inteiros, os adiciona e retorna o resultado. Um 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 no Runtime.

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 no 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, retornar valores e definir sua lógica usando código imperativo. Para obter exemplos de criação desses tipos de atividades, consulte Classe Base CodeActivity e Criando atividades assíncronas.

Atividades derivadas de NativeActivity podem 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 marcadores. Para ver exemplos de criação de uma atividade baseada em NativeActivity, confira Classe base NativeActivity, Como criar uma atividade e o exemplo Composição personalizada usando a atividade nativa.

As atividades que derivam do Activity definem sua lógica somente por meio do uso de atividades filho. Essas atividades normalmente são criadas usando o designer de fluxo de trabalho, mas também podem ser definidas usando código. No exemplo a seguir, uma Square atividade é definida que deriva de Activity<int>. A Square atividade tem um único InArgument<T> chamado 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. Juntas, essas 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:

Quadrado do valor: 5
Resultado: 25