Usar delegados de actividad

Los delegados de actividad permiten a los autores de actividad exponer devoluciones de llamada con firmas específicas, para las que los usuarios de la actividad pueden proporcionar controladores basados en actividades. Hay dos tipos de delegados de actividad disponibles: ActivityAction<T> se usa para definir delegados de actividad que no tienen un valor devuelto y ActivityFunc<TResult> se utiliza para definir delegados de actividad que tienen un valor devuelto.

Los delegados de actividad son útiles en situaciones en que se debe restringir una actividad secundaria para que tenga una firma determinada. Por ejemplo, una actividad While puede contener cualquier tipo de actividad secundaria sin restricciones, aunque el cuerpo de una actividad ForEach<T> es ActivityAction<T> y la actividad secundaria que ForEach<T> ejecuta finalmente debe tener InArgument<T>, que es el mismo tipo de miembros de la colección que ForEach<T> enumera.

Utilizar ActivityAction

Varias actividades de .NET Framework 4.6.1 utilizan acciones de actividad, como la actividad Catch y ForEach<T>. En cada caso, la acción de actividad representa una ubicación donde el autor del flujo de trabajo especifica una actividad para proporcionar el comportamiento deseado cuando se crea un flujo de trabajo mediante una de estas actividades. En el siguiente ejemplo, se utiliza una actividad ForEach<T> para mostrar texto en la ventana de la consola. El cuerpo de ForEach<T> se especifica utilizando un objeto ActivityAction<T> que coincide con el tipo del objeto ForEach<T> que es cadena. La actividad WriteLine especificada en Handler tiene su argumento Text enlazado a los valores de cadena en la colección que la actividad ForEach<T> recorre en iteración.

DelegateInArgument<string> actionArgument = new DelegateInArgument<string>();

Activity wf = new ForEach<string>
{
    Body = new ActivityAction<string>
    {
        Argument = actionArgument,
        Handler = new WriteLine
        {
            Text = new InArgument<string>(actionArgument)
        }
    }
};

List<string> items = new List<string>();
items.Add("Hello");
items.Add("World.");

Dictionary<string, object> inputs = new Dictionary<string, object>();
inputs.Add("Values", items);

WorkflowInvoker.Invoke(wf, inputs);

ActionArgument se utiliza para pasar los elementos individuales de la colección a WriteLine. Cuando se invoca el flujo de trabajo, en la consola se muestra el siguiente resultado.

HelloWorld.

Los ejemplos de este tema usan la sintaxis de inicialización del objeto. Esta sintaxis puede ser una forma útil de crear definiciones de flujo de trabajo en el código porque proporciona una vista jerárquica de las actividades en el flujo de trabajo y muestra la relación entre las actividades. No hay ningún requisito para usar la sintaxis de inicialización de objeto al crear los flujos de trabajo mediante programación. El ejemplo siguiente es equivalente en su funcionamiento al ejemplo anterior.

DelegateInArgument<string> actionArgument = new DelegateInArgument<string>();

WriteLine output = new WriteLine();
output.Text = new InArgument<string>(actionArgument);

ActivityAction<string> body = new ActivityAction<string>();
body.Argument = actionArgument;
body.Handler = output;

ForEach<string> wf = new ForEach<string>();
wf.Body = body;

List<string> items = new List<string>();
items.Add("Hello");
items.Add("World.");

Dictionary<string, object> inputs = new Dictionary<string, object>();
inputs.Add("Values", items);

WorkflowInvoker.Invoke(wf, inputs);

Para más información sobre los inicializadores de objeto, consulte los temas sobre cómo inicializar objetos sin llamar a un constructor (Guía de programación de C#) y cómo declarar un objeto mediante un inicializador de objeto (Visual Basic).

En el siguiente ejemplo, se utiliza una actividad TryCatch en un flujo de trabajo. El flujo de trabajo produce una excepción ApplicationException, que es controlada por una actividad Catch<TException>. El controlador de la acción de actividad de la actividad Catch<TException> es una actividad WriteLinea través de la cual se pasa el detalle de excepción utilizando exDelegateInArgument<T>.

DelegateInArgument<ApplicationException> ex = new DelegateInArgument<ApplicationException>()
{
    Name = "ex"
};

Activity wf = new TryCatch
{
    Try = new Throw()
    {
        Exception = new InArgument<Exception>((env) =>new ApplicationException("An ApplicationException was thrown."))
    },
    Catches =
    {
        new Catch<ApplicationException>
        {
            Action = new ActivityAction<ApplicationException>
            {
                Argument = ex,
                Handler = new WriteLine()
                {
                    Text = new InArgument<string>((env) => ex.Get(env).Message)
                }
            }
        }
    },
    Finally = new WriteLine()
    {
        Text = "Executing in Finally."
    }
};

Cuando cree una actividad personalizada que defina un objeto ActivityAction<T>, use InvokeAction<T> para modelar la invocación de ese objeto ActivityAction<T>. En este ejemplo, se define una actividad WriteLineWithNotification personalizada. Esta actividad está compuesta de una clase Sequence que contiene una actividad WriteLine seguida de InvokeAction<T> que invoca una clase ActivityAction<T> que toma un argumento de tipo cadena.

public class WriteLineWithNotification : Activity
{
    public InArgument<string> Text { get; set; }
    public ActivityAction<string> TextProcessedAction { get; set; }

    public WriteLineWithNotification()
    {
        this.Implementation = () => new Sequence
        {
            Activities =
            {
                new WriteLine
                {
                    Text = new InArgument<string>((env) => Text.Get(env))
                },
                new InvokeAction<string>
                {
                    Action = TextProcessedAction,
                    Argument = new InArgument<string>((env) => Text.Get(env))
                }
            }
        };
    }
}

Cuando se crea un flujo de trabajo con la actividad WriteLineWithNotification, el autor del flujo de trabajo especifica la lógica personalizada deseada en la propiedad Handler de la acción de la actividad. En este ejemplo, se crea un flujo de trabajo que utiliza la actividad WriteLineWithNotification y se usa una actividad WriteLine como Handler.

// Create and invoke the workflow without specifying any activity action
// for TextProcessed.
Activity wf = new WriteLineWithNotification
{
    Text = "Hello World."
};

WorkflowInvoker.Invoke(wf);

// Output:
// Hello World.

// Create and Invoke the workflow with specifying an activity action
// for TextProcessed.
DelegateInArgument<string> msg = new DelegateInArgument<string>();
Activity wf2 = new WriteLineWithNotification
{
    Text = "Hello World with activity action.",
    TextProcessedAction = new ActivityAction<string>
    {
        Argument = msg,
        Handler = new WriteLine
        {
            Text = new InArgument<string>((env) => "Handler of: " + msg.Get(env))
        }
    }
};

// Invoke the workflow with an activity action specified
WorkflowInvoker.Invoke(wf2);

// Output:
// Hello World with activity action.
// Handler of: Hello World with activity action.

Hay varias versiones genéricas de InvokeAction<T> y ActivityAction<T> proporcionadas para pasar uno o varios argumentos.

Usar ActivityFunc

ActivityAction<T> es útil cuando no existe ningún valor de resultado procedente de la actividad y ActivityFunc<TResult> se utiliza cuando se devuelve un valor de resultado. Cuando cree una actividad personalizada que defina un objeto ActivityFunc<TResult>, use InvokeFunc<TResult> para modelar la invocación de ese objeto ActivityFunc<TResult>. En el ejemplo siguiente, se define una actividad WriteFillerText . Para proporcionar el texto de relleno, se especifica una clase InvokeFunc<TResult> que toma un argumento entero y genera una cadena como resultado. Una vez recuperado el texto de relleno, se muestra en la consola utilizando una actividad WriteLine.

public class WriteFillerText : Activity
{
    public ActivityFunc<int, string> GetText { get; set; }
    public InArgument<int> Quantity { get; set; }

    Variable<string> text = new Variable<string>
    {
        Name = "Text"
    };

    public WriteFillerText()
    {
        this.Implementation = () => new Sequence
        {
            Variables =
            {
                text
            },
            Activities =
            {
                new InvokeFunc<int, string>
                {
                    Func = GetText,
                    Argument = new InArgument<int>((env) => Quantity.Get(env)),
                    Result = new OutArgument<string>(text)
                },
                new WriteLine
                {
                    Text = new InArgument<string>(text)
                }
            }
        };
    }
}

Para proporcionar el texto, se debe usar una actividad que tome un argumento int y tenga como resultado una cadena. En este ejemplo se muestra una actividad TextGenerator que cumple estos requisitos.

public class TextGenerator : CodeActivity<string>
{
    public InArgument<int> Quantity { get; set; }
    public InArgument<string> Text { get; set; }

    protected override string Execute(CodeActivityContext context)
    {
        // Provide a quantity of Random Text
        int q = Quantity.Get(context);
        if (q < 1)
        {
            q = 1;
        }

        string text = Text.Get(context) + " ";
        StringBuilder sb = new StringBuilder(text.Length * q);
        for (int i = 0; i < q; i++)
        {
            sb.Append(text);
        }

        return sb.ToString();
    }
}

Para usar la actividad TextGenerator con la actividad WriteFillerText, especifíquela como Handler.

DelegateInArgument<int> actionArgument = new DelegateInArgument<int>();

Activity wf = new WriteFillerText
{
    Quantity = 5,
    GetText = new ActivityFunc<int, string>
    {
        Argument = actionArgument,
        Handler = new TextGenerator
        {
            Quantity = new InArgument<int>(actionArgument),
            Text = "Hello World."
        }
    }
};

WorkflowInvoker.Invoke(wf);