Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Una definizione del flusso di lavoro è un albero di oggetti attività configurati. Questo albero di attività può essere definito in molti modi, tra cui la modifica manuale di XAML o l'uso di Progettazione flussi di lavoro per produrre XAML. L'uso di XAML, tuttavia, non è un requisito. È anche possibile creare definizioni del flusso di lavoro a livello di codice. In questo argomento viene fornita una panoramica della creazione di definizioni, attività ed espressioni del flusso di lavoro usando il codice. Per esempi di utilizzo dei flussi di lavoro XAML con il codice, vedi Serializzazione di flussi di lavoro e attività da e verso XAML.
Creazione di definizioni del flusso di lavoro
È possibile creare una definizione del flusso di lavoro creando un'istanza di un tipo di attività e configurando le proprietà dell'oggetto attività. Per le attività che non contengono attività figlie, questa operazione si può realizzare usando alcune righe di codice.
Activity wf = new WriteLine
{
Text = "Hello World."
};
WorkflowInvoker.Invoke(wf);
Annotazioni
Gli esempi in questo argomento usano WorkflowInvoker per eseguire i flussi di lavoro di esempio. Per altre informazioni sulla chiamata di flussi di lavoro, il passaggio di argomenti e le diverse opzioni di hosting disponibili, vedere Uso di WorkflowInvoker e WorkflowApplication.
In questo esempio viene creato un flusso di lavoro costituito da una singola WriteLine attività. L'argomento dell'attività WriteLine è stato impostato e il flusso di lavoro Text è stato richiamato. Se un'attività contiene attività figlie, il metodo di costruzione è analogo. Nell'esempio seguente viene utilizzata un'attività Sequence che contiene due WriteLine attività.
Activity wf = new Sequence
{
Activities =
{
new WriteLine
{
Text = "Hello"
},
new WriteLine
{
Text = "World."
}
}
};
WorkflowInvoker.Invoke(wf);
Uso di inizializzatori di oggetti
Negli esempi di questo argomento viene usata la sintassi di inizializzazione oggetti che può essere utile per creare definizioni del flusso di lavoro in codice in quanto fornisce una visualizzazione gerarchica delle attività nel flusso di lavoro e consente di illustrare la relazione tra le attività. Non è previsto alcun requisito per usare la sintassi di inizializzazione oggetti quando si creano flussi di lavoro a livello di codice. L'esempio seguente rappresenta l'equivalente funzionale dell'esempio precedente.
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);
Per altre informazioni sugli inizializzatori di oggetti, vedere Procedura: Inizializzare oggetti senza chiamare un costruttore (Guida per programmatori C#) e Procedura: Dichiarare un oggetto tramite un inizializzatore di oggetto.
Uso di variabili, valori letterali ed espressioni
Quando si crea una definizione del flusso di lavoro usando codice, tenere presente il codice eseguito come parte della creazione della definizione del flusso di lavoro e del codice eseguito come parte dell'esecuzione di un'istanza del flusso di lavoro. Ad esempio, il flusso di lavoro seguente è progettato per generare un numero casuale e scriverlo nella 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 viene eseguito questo codice di definizione del flusso di lavoro, viene eseguita la chiamata a Random.Next e il risultato viene archiviato nella definizione del flusso di lavoro come valore letterale. È possibile richiamare molte istanze di questo flusso di lavoro e tutti visualizzano lo stesso numero. Per fare in modo che la generazione di numeri casuali venga eseguita durante l'esecuzione del flusso di lavoro, è necessario usare un'espressione che viene valutata ogni volta che viene eseguito il flusso di lavoro. Nell'esempio seguente viene utilizzata un'espressione di Visual Basic con un oggetto VisualBasicValue<TResult>.
new Assign<int>
{
To = n,
Value = new VisualBasicValue<int>("New Random().Next(1, 101)")
}
L'espressione nell'esempio precedente può essere implementata anche usando una CSharpValue<TResult> e un'espressione C#.
new Assign<int>
{
To = n,
Value = new CSharpValue<int>("new Random().Next(1, 101)")
}
Le espressioni C# devono essere compilate prima che venga richiamato il flusso di lavoro che li contiene. Se le espressioni C# non vengono compilate, viene generata un'eccezione NotSupportedException quando il flusso di lavoro viene richiamato con un messaggio simile al seguente: Expression Activity type 'CSharpValue`1' requires compilation in order to run. Please ensure that the workflow has been compiled. Nella maggior parte degli scenari che coinvolgono flussi di lavoro creati in Visual Studio le espressioni C# vengono compilate automaticamente, ma in alcuni scenari, ad esempio flussi di lavoro di codice, le espressioni C# devono essere compilate manualmente. Per un esempio di come compilare espressioni C#, vedere la sezione Uso di espressioni C# nei flussi di lavoro del codice dell'argomento Espressioni C# .
Un VisualBasicValue<TResult> oggetto rappresenta un'espressione nella sintassi di Visual Basic che può essere usata come r-value in un'espressione e un oggetto CSharpValue<TResult> rappresenta un'espressione nella sintassi C# che può essere usata come valore r in un'espressione. Queste espressioni vengono valutate ogni volta che si esegue l'attività che le contiene. Il risultato dell'espressione viene assegnato alla variabile ndel flusso di lavoro e questi risultati vengono usati dall'attività successiva nel flusso di lavoro. Per accedere al valore della variabile n del flusso di lavoro in fase di esecuzione, ActivityContext è necessario . È possibile accedervi usando l'espressione lambda seguente.
Annotazioni
Nota che entrambi questi esempi di codice usano C# come linguaggio di programmazione, ma uno usa un VisualBasicValue<TResult> e uno usa un CSharpValue<TResult>. VisualBasicValue<TResult> e CSharpValue<TResult> possono essere usati sia nei progetti Visual Basic che in C#. Per impostazione predefinita, le espressioni create nella finestra di progettazione del flusso di lavoro corrispondono alla lingua del progetto di hosting. Quando si creano flussi di lavoro nel codice, il linguaggio desiderato è a discrezione dell'autore del flusso di lavoro.
In questi esempi il risultato dell'espressione viene assegnato alla variabile ndel flusso di lavoro e questi risultati vengono usati dall'attività successiva nel flusso di lavoro. Per accedere al valore della variabile n del flusso di lavoro in fase di esecuzione, ActivityContext è necessario . È possibile accedervi usando l'espressione lambda seguente.
new WriteLine
{
Text = new InArgument<string>((env) => "The number is " + n.Get(env))
}
Per altre informazioni sulle espressioni lambda, vedere Espressioni lambda (riferimenti per C#) o Espressioni lambda (Visual Basic).For more information about lambda expressions, see Lambda Expressions (C# reference) or Lambda Expressions (Visual Basic).
Le espressioni lambda non sono serializzabili in formato XAML. Se viene eseguito un tentativo di serializzare un flusso di lavoro con espressioni lambda, viene generata un'eccezione LambdaSerializationException con il messaggio seguente: "Questo flusso di lavoro contiene espressioni lambda specificate nel codice. Queste espressioni non sono serializzabili xaml. Per rendere serializzabile il flusso di lavoro XAML, usare VisualBasicValue/VisualBasicReference o ExpressionServices.Convert(lambda). In questo modo le espressioni lambda verranno convertite in attività di espressione". Per rendere compatibile questa espressione con XAML, usare ExpressionServices e Convert, come illustrato nell'esempio seguente.
new WriteLine
{
//Text = new InArgument<string>((env) => "The number is " + n.Get(env))
Text = ExpressionServices.Convert((env) => "The number is " + n.Get(env))
}
È anche possibile utilizzare un oggetto VisualBasicValue<TResult> . Si noti che non è necessaria alcuna espressione lambda quando si usa un'espressione 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()")
}
In fase di esecuzione, le espressioni di Visual Basic vengono compilate in espressioni LINQ. Entrambi gli esempi precedenti sono serializzabili in XAML, ma se il codice XAML serializzato deve essere visualizzato e modificato nella finestra di progettazione del flusso di lavoro, usare VisualBasicValue<TResult> per le espressioni. I flussi di lavoro serializzati che usano ExpressionServices.Convert possono essere aperti nella finestra di progettazione, ma il valore dell'espressione sarà vuoto. Per altre informazioni sulla serializzazione dei flussi di lavoro in XAML, vedere Serializzazione di flussi di lavoro e attività da e verso XAML.
Espressioni letterali e tipi di riferimento
Le espressioni letterali sono rappresentate nei flussi di lavoro dall'attività Literal<T> . Le attività seguenti WriteLine sono equivalenti a livello funzionale.
new WriteLine
{
Text = "Hello World."
},
new WriteLine
{
Text = new Literal<string>("Hello World.")
}
Non è valido inizializzare un'espressione letterale con qualsiasi tipo di riferimento ad eccezione Stringdi . Nell'esempio seguente, la proprietà Assign di un'attività Value viene inizializzata con un'espressione letterale usando un List<string>.
new Assign
{
To = new OutArgument<List<string>>(items),
Value = new InArgument<List<string>>(new List<string>())
},
Quando il flusso di lavoro contenente questa attività viene convalidato, viene restituito l'errore di convalida seguente: "Literal supporta solo i tipi valore e il tipo non modificabile System.String. Il tipo System.Collections.Generic.List'1[System.String] non può essere usato come valore letterale". Se viene richiamato il flusso di lavoro, viene generata un'eccezione InvalidWorkflowException contenente il testo dell'errore di convalida. Si tratta di un errore di convalida perché la creazione di un'espressione letterale con un tipo riferimento non crea una nuova istanza del tipo di riferimento per ogni istanza del flusso di lavoro. Per risolvere questo problema, sostituire l'espressione letterale con una che crea e restituisce una nuova istanza del tipo di riferimento.
new Assign
{
To = new OutArgument<List<string>>(items),
Value = new InArgument<List<string>>(new VisualBasicValue<List<string>>("New List(Of String)"))
},
Per altre informazioni sulle espressioni, vedere Espressioni.
Richiamo di metodi su oggetti tramite espressioni e l'attività InvokeMethod
L'attività InvokeMethod<TResult> può essere usata per richiamare metodi statici e di istanza di classi in .NET Framework. In un esempio precedente di questo argomento è stato generato un numero casuale usando la Random classe .
new Assign<int>
{
To = n,
Value = new VisualBasicValue<int>("New Random().Next(1, 101)")
}
L'attività InvokeMethod<TResult> potrebbe essere stata usata anche per chiamare il Next metodo della 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
}
Poiché Next non è un metodo statico, viene fornita un'istanza della Random classe per la TargetObject proprietà . In questo esempio viene creata una nuova istanza usando un'espressione visual Basic, ma potrebbe anche essere stata creata in precedenza e archiviata in una variabile del flusso di lavoro. In questo esempio sarebbe più semplice usare l'attività Assign<T> anziché l'attività InvokeMethod<TResult> . Se la chiamata al metodo viene richiamata da una delle attività Assign<T> o InvokeMethod<TResult> a lungo termine, InvokeMethod<TResult> offre un vantaggio poiché dispone di una proprietà RunAsynchronously. Quando questa proprietà è impostata su true, il metodo richiamato verrà eseguito in modo asincrono per quanto riguarda il flusso di lavoro. Se altre attività sono in parallelo, non verranno bloccate mentre il metodo è in esecuzione asincrona. Inoltre, se il metodo da richiamare non ha alcun valore restituito, InvokeMethod<TResult> è il modo appropriato per richiamare il metodo.
Argomenti e attività dinamiche
Una definizione del flusso di lavoro viene creata nel codice assemblando le attività in un albero delle attività e configurando eventuali proprietà e argomenti. Gli argomenti esistenti possono essere associati, ma non è possibile aggiungere nuovi argomenti alle attività. Sono inclusi gli argomenti del flusso di lavoro passati all'attività radice. Nel codice imperativo, gli argomenti del flusso di lavoro vengono specificati come proprietà in un nuovo tipo CLR e in XAML vengono dichiarati usando x:Class e x:Member. Poiché non esiste alcun nuovo tipo CLR creato quando viene creata una definizione del flusso di lavoro come albero di oggetti in memoria, non è possibile aggiungere argomenti. Tuttavia, gli argomenti possono essere aggiunti a un oggetto DynamicActivity. In questo esempio viene creato un oggetto DynamicActivity<TResult> che accetta due argomenti integer, li aggiunge insieme e restituisce il risultato. Viene creato un DynamicActivityProperty per ogni argomento e il risultato dell'operazione viene assegnato all'argomento Result del 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);
Per altre informazioni sulle attività dinamiche, vedere Creazione di un'attività in fase di esecuzione.
Attività compilate
Le attività dinamiche sono un modo per definire un'attività che contiene argomenti usando il codice, ma le attività possono essere create anche nel codice e compilate in tipi. È possibile creare attività semplici che derivano da CodeActivitye attività asincrone che derivano da AsyncCodeActivity. Queste attività possono avere argomenti, valori restituiti e definire la logica usando il codice imperativo. Per esempi di creazione di questi tipi di attività, vedere Classe base CodeActivity e Creazione di attività asincrone.
Le attività che derivano da NativeActivity possono definire la logica utilizzando codice imperativo e possono anche contenere attività figlie che definiscono la logica. Hanno anche accesso completo alle funzionalità del runtime, ad esempio la creazione di segnalibri. Per esempi di creazione di un'attività basata su NativeActivity, vedere Classe di base NativeActivity, Procedura: Creare un'attività e l'esempio Composito personalizzato usando l'attività nativa.
Le attività che derivano da Activity definiscono la loro logica esclusivamente tramite l'uso di attività figlio. Queste attività vengono in genere create tramite la finestra di progettazione del flusso di lavoro, ma possono essere definite anche usando il codice. Nell'esempio seguente viene definita un'attività Square che deriva da Activity<int>. L'attività Square ha un singolo InArgument<T> denominato Valuee ne definisce la logica specificando un'attività Sequence tramite la Implementation proprietà . L'attività Sequence contiene un'attività WriteLine e un'attività Assign<T> . Insieme, queste tre attività implementano la logica dell'attività 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))
}
}
};
}
}
Nell'esempio seguente viene richiamata una definizione del flusso di lavoro costituita da una singola Square attività 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 viene richiamato il flusso di lavoro, viene visualizzato l'output seguente nella console:
Quadratura del valore: 5Risultato: 25