Compartilhar via


Criando fluxos de trabalho, atividades e expressões de design usando o código obrigatório

Uma definição de fluxo de trabalho é uma árvore de objetos de atividade configurados. Essa árvore de atividades pode ser definida de muitas maneiras, incluindo pela edição manual do XAML ou usando o Designer de Fluxo de Trabalho para gerar XAML. O uso de XAML, porém, 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 de como criar definições, atividades e expressões de fluxo de trabalho usando código. Para ver exemplos de trabalho com fluxos de trabalho XAML usando o código, confira Como serializar fluxos de trabalho e atividades em 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 da 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 deste tópico usam WorkflowInvoker para executar fluxos de trabalho de exemplo. Para obter mais informações sobre como invocar fluxos de trabalho, transmitir argumentos e as diferentes opções de hospedagem disponíveis, confira Como usar WorkflowInvoker e WorkflowApplication.

Neste exemplo, um fluxo de trabalho que consiste em uma única atividade WriteLine é criado. O argumento WriteLine da atividade Text está 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 atividade Sequence que contém duas atividades WriteLine.

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

WorkflowInvoker.Invoke(wf);

Usando inicializadores de objeto

Os exemplos deste 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 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, confira 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, saiba 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 seguinte fluxo de trabalho é destinado para 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 da definição de fluxo de trabalho é executado, a chamada para Random.Next é feita e o resultado é armazenado na definição do fluxo de trabalho como um valor literal. Muitas instâncias desse fluxo de trabalho podem ser chamadas e todos exibiriam o mesmo número. Para que a geração de números aleatórios ocorra durante a execução do fluxo de trabalho, deve ser usada uma expressão que é avaliada toda vez que o fluxo de trabalho é 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 poderia 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, uma NotSupportedException será gerada 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 que envolvem fluxos de trabalho criados no Visual Studio, as expressões C# são criadas automaticamente, mas, em alguns cenários, como fluxos de trabalho do código, as expressões C# precisam ser compiladas manualmente. Para ver um exemplo de como compilar expressões C#, confira a seção Como usar expressões C# em 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 valor r em uma expressão, e um CSharpValue<TResult> representa uma expressão na sintaxe C# que pode ser usada como um valor r de uma expressão. Essas expressões são avaliadas cada vez que a atividade contentora é executada. O resultado da expressão é atribuído à variável n do 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 do fluxo de trabalho em tempo de execução, o ActivityContext é necessário. Isso pode ser acessado usando a seguinte expressão lambda.

Observação

Observe que esses dois códigos são exemplos que usam C# como a linguagem de programação, mas um usa um VisualBasicValue<TResult> e o outro usa um CSharpValue<TResult>. VisualBasicValue<TResult> e CSharpValue<TResult> podem ser usados em projetos do Visual Basic e C#. Por padrão, as expressões criadas no designer de fluxo de trabalho correspondem à linguagem do projeto de hospedagem. Ao criar fluxos de trabalho no código, a linguagem desejada fica a critério do autor do fluxo de trabalho.

Nestes exemplos, o resultado da expressão é atribuído à variável n do fluxo de trabalho, e esses resultados são usados pela atividade a seguir no fluxo de trabalho. Para acessar o valor da variável n do 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, confira Expressões lambda (referência de C#) ou Expressões lambda (Visual Basic).

As expressões lambda não são serializáveis para o formato XAML. Se uma tentativa para serializar um fluxo de trabalho com expressões lambda for feita, um LambdaSerializationException será gerado com a seguinte mensagem: “Esse fluxo de trabalho contém as expressões lambda especificadas no código. Essas expressões não são XAML serializável. Para tornar serializável a XAML de seu fluxo de trabalho, use VisualBasicValue/VisualBasicReference ou ExpressionServices.Convert(lambda). Isso converterá as 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 poderia 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. Os dois exemplos anteriores são serializáveis para XAML, mas se o XAML serializado for destinado para ser exibido e editado no designer de fluxo de trabalho, use a 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 em XAML, confira Como serializar fluxos de trabalho e atividades em XAML.

Expressões literais e tipos de referência

As expressões literais são representadas nos fluxos de trabalho pela atividade Literal<T>. As seguintes atividades WriteLine são funcionalmente equivalentes.

new WriteLine  
{  
    Text = "Hello World."  
},  
new WriteLine  
{  
    Text = new Literal<string>("Hello World.")  
}  

Não é vá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 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 esta atividade é validado, o seguinte erro de validação será retornado: "O literal somente oferece suporte aos 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á gerada uma InvalidWorkflowException que contém o texto do erro de validação. Este é um erro de validação porque criar 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, confira Expressões.

Invocando métodos em objetos usando expressões e a atividade InvokeMethod

A atividade InvokeMethod<TResult> 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 classe Random.

new Assign<int>
{
    To = n,
    Value = new VisualBasicValue<int>("New Random().Next(1, 101)")
}

A atividade InvokeMethod<TResult> também poderia ter sido usada para chamar o método Next da classe Random.

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 classe Random é fornecida para a propriedade TargetObject. Neste exemplo, uma nova instância é criada usando uma expressão do Visual Basic, mas também poderia ter sido criada anteriormente e armazenada em uma variável do fluxo de trabalho. Neste exemplo, seria mais simples usar a atividade Assign<T> em vez da atividade InvokeMethod<TResult>. Se a chamada do método chamado finalmente pelas atividades Assign<T> ou InvokeMethod<TResult> for longa, o InvokeMethod<TResult> terá uma vantagem porque tem uma propriedade RunAsynchronously. Quando esta propriedade for definida como true, o método chamado 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 de forma assíncrona. Além disso, se o método a ser chamado não tiver nenhum valor de retorno, então InvokeMethod<TResult> será o modo apropriado de chamar 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 atividade e configurando as propriedades e os argumentos. Os argumentos existentes podem ser associados, mas novos argumentos não podem ser adicionados às atividades. Isso inclui os argumentos de fluxo de trabalho passados para a atividade raiz. No código obrigatório, os argumentos de fluxo de trabalho são especificados como propriedades em um novo tipo CLR, e no XAML são declarados usando x:Class e x:Member. Como não há um novo tipo CLR criado quando uma definição de fluxo de trabalho é criada como uma árvore de objetos de memória, os argumentos não poderão ser adicionados. No entanto, os argumentos podem ser adicionados a um DynamicActivity. Neste exemplo, um DynamicActivity<TResult> é criado e usa dois argumentos inteiros, adiciona-os juntos e retorna o resultado. Um DynamicActivityProperty é criado para cada argumento e o resultado da operação é atribuído ao argumento Result 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, confira Como criar uma atividade em runtime.

Atividades compiladas

As atividades dinâmicas são uma forma de definir uma atividade que contém argumentos usando o código, mas as atividades também podem ser criadas em código e compiladas em tipos. As atividades simples podem ser criadas que derivam de CodeActivity e as atividades assíncronas que derivam de AsyncCodeActivity. Essas atividades podem ter argumentos, valores de retorno e definir sua lógica usando o código obrigatório. Para ver exemplos de criação desses tipos de atividades, confira Classe base CodeActivity e Como criar atividades assíncronas.

As atividades que derivam de NativeActivity podem definir sua lógica usando o código obrigatório e elas também podem conter as atividades filho que definem a lógica. Elas também têm acesso completo aos recursos de runtime como criar indicadores. 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 atividade Square é definida que deriva de Activity<int>. A atividade Square tem único InArgument<T> chamado Value e define sua lógica especificando uma atividade Sequence usando a propriedade Implementation. A atividade Sequence contém uma atividade WriteLine e uma atividade Assign<T>. Juntas, essas três atividades implementam a lógica da atividade Square.

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 atividade Square é 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 é chamado, a seguinte saída é exibida no console:

Quadrado do valor: 5
Resultado: 25