Vytváření pracovních postupů, aktivit a výrazů pomocí imperativního kódu
Definice pracovního postupu je strom nakonfigurovaných objektů aktivity. Tento strom aktivit lze definovat mnoha způsoby, včetně ruční úpravy XAML nebo pomocí Návrháře pracovních postupů k vytvoření XAML. Použití XAML ale není požadavkem. Definice pracovního postupu lze vytvořit také prostřednictvím kódu programu. Toto téma obsahuje přehled vytváření definic pracovních postupů, aktivit a výrazů pomocí kódu. Příklady práce s pracovními postupy XAML pomocí kódu naleznete v tématu Serializace pracovních postupů a aktivit do a z XAML.
Vytváření definic pracovních postupů
Definici pracovního postupu lze vytvořit vytvořením instance typu aktivity a konfigurací vlastností objektu aktivity. U aktivit, které neobsahují podřízené aktivity, je to možné provést pomocí několika řádků kódu.
Activity wf = new WriteLine
{
Text = "Hello World."
};
WorkflowInvoker.Invoke(wf);
Poznámka:
Příklady v tomto tématu slouží WorkflowInvoker ke spuštění ukázkových pracovních postupů. Další informace o vyvolání pracovních postupů, předávání argumentů a různých dostupných možností hostování najdete v tématu Použití WorkflowInvoker a WorkflowApplication.
V tomto příkladu se vytvoří pracovní postup, který se skládá z jedné WriteLine aktivity. Argument WriteLine aktivity Text je nastavený a pracovní postup se vyvolá. Pokud aktivita obsahuje podřízené aktivity, metoda konstrukce je podobná. Následující příklad používá Sequence aktivitu, která obsahuje dvě WriteLine aktivity.
Activity wf = new Sequence
{
Activities =
{
new WriteLine
{
Text = "Hello"
},
new WriteLine
{
Text = "World."
}
}
};
WorkflowInvoker.Invoke(wf);
Použití inicializátorů objektů
Příklady v tomto tématu používají syntaxi inicializace objektů. Syntaxe inicializace objektů může být užitečný způsob, jak vytvořit definice pracovního postupu v kódu, protože poskytuje hierarchické zobrazení aktivit v pracovním postupu a zobrazuje vztah mezi aktivitami. Při programovém vytváření pracovních postupů není nutné používat syntaxi inicializace objektů. Následující příklad je funkčně ekvivalentní předchozímu příkladu.
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);
Další informace o inicializátorech objektů naleznete v tématu Postupy: Inicializace objektů bez volání konstruktoru (Průvodce programováním v C#) a Postupy: Deklarace objektu pomocí inicializátoru objektů.
Práce s proměnnými, literálními hodnotami a výrazy
Při vytváření definice pracovního postupu pomocí kódu mějte na paměti, jaký kód se provádí jako součást vytvoření definice pracovního postupu a jaký kód se provádí v rámci provádění instance tohoto pracovního postupu. Například následující pracovní postup je určený k vygenerování náhodného čísla a jeho zápisu do konzoly.
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))
}
}
};
Při spuštění tohoto kódu definice pracovního postupu se provede volání Random.Next
a výsledek se uloží do definice pracovního postupu jako hodnota literálu. Je možné vyvolat mnoho instancí tohoto pracovního postupu a všechny by zobrazily stejné číslo. Pokud chcete, aby během provádění pracovního postupu došlo k náhodnému generování čísel, musí se použít výraz, který se vyhodnotí při každém spuštění pracovního postupu. V následujícím příkladu se výraz jazyka Visual Basic používá s .VisualBasicValue<TResult>
new Assign<int>
{
To = n,
Value = new VisualBasicValue<int>("New Random().Next(1, 101)")
}
Výraz v předchozím příkladu lze také implementovat pomocí výrazu jazyka CSharpValue<TResult> C#.
new Assign<int>
{
To = n,
Value = new CSharpValue<int>("new Random().Next(1, 101)")
}
Před vyvolání pracovního postupu obsahujícího výrazy musí být zkompilovány výrazy jazyka C#. Pokud výrazy jazyka C# nejsou kompilovány, NotSupportedException vyvolá se při vyvolání pracovního postupu se zprávou podobnou této: Expression Activity type 'CSharpValue`1' requires compilation in order to run. Please ensure that the workflow has been compiled.
Ve většině scénářů zahrnujících pracovní postupy vytvořené v sadě Visual Studio se výrazy jazyka C# kompilují automaticky, ale v některých scénářích, jako jsou pracovní postupy kódu, je nutné výrazy jazyka C# zkompilovat ručně. Příklad kompilace výrazů jazyka C# najdete v části Používání výrazů jazyka C# v části Pracovní postupy kódu tématu Výrazy jazyka C#.
A VisualBasicValue<TResult> představuje výraz v syntaxi jazyka Visual Basic, který lze použít jako r-hodnota ve výrazu CSharpValue<TResult> , a představuje výraz v syntaxi jazyka C#, který lze použít jako r-hodnota ve výrazu. Tyto výrazy se vyhodnocují při každém spuštění obsahující aktivity. Výsledek výrazu je přiřazen k proměnné n
pracovního postupu a tyto výsledky jsou používány další aktivitou v pracovním postupu. Pro přístup k hodnotě proměnné n
pracovního postupu za běhu ActivityContext se vyžaduje. K tomuto přístupu je možné získat přístup pomocí následujícího výrazu lambda.
Poznámka:
Všimněte si, že oba tyto kódy jsou příklady použití jazyka C# jako programovacího jazyka, ale jeden používá a jeden používá VisualBasicValue<TResult> znak CSharpValue<TResult>. VisualBasicValue<TResult> a CSharpValue<TResult> lze je použít v projektech jazyka Visual Basic i C#. Ve výchozím nastavení výrazy vytvořené v návrháři pracovního postupu odpovídají jazyku hostitelského projektu. Při vytváření pracovních postupů v kódu je požadovaný jazyk podle uvážení autora pracovního postupu.
V těchto příkladech je výsledek výrazu přiřazen k proměnné n
pracovního postupu a tyto výsledky jsou používány další aktivitou v pracovním postupu. Pro přístup k hodnotě proměnné n
pracovního postupu za běhu ActivityContext se vyžaduje. K tomuto přístupu je možné získat přístup pomocí následujícího výrazu lambda.
new WriteLine
{
Text = new InArgument<string>((env) => "The number is " + n.Get(env))
}
Další informace o výrazech lambda najdete v tématu Výrazy lambda (referenční dokumentace jazyka C#) nebo výrazy lambda (Visual Basic).
Výrazy lambda nejsou serializovatelné pro formát XAML. Pokud se provede pokus o serializaci pracovního postupu s výrazy lambda, LambdaSerializationException vyvolá se následující zpráva: "Tento pracovní postup obsahuje výrazy lambda zadané v kódu. Tyto výrazy nejsou serializovatelné pomocí XAML. Pokud chcete, aby byl váš pracovní postup serializovatelný, použijte buď VisualBasicValue/VisualBasicReference nebo ExpressionServices.Convert(lambda). Tím se výrazy lambda převedou na aktivity výrazů." Pokud chcete, aby byl tento výraz kompatibilní s XAML, použijte ExpressionServices a Convert, jak je znázorněno v následujícím příkladu.
new WriteLine
{
//Text = new InArgument<string>((env) => "The number is " + n.Get(env))
Text = ExpressionServices.Convert((env) => "The number is " + n.Get(env))
}
VisualBasicValue<TResult> Lze také použít. Všimněte si, že při použití výrazu Jazyka Visual Basic se nevyžaduje žádný výraz lambda.
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()")
}
Za běhu se výrazy jazyka Visual Basic kompilují do výrazů LINQ. Oba předchozí příklady jsou serializovatelné pro XAML, ale pokud je serializovaný XAML určen k zobrazení a úpravě v návrháři pracovního postupu, použijte VisualBasicValue<TResult> pro výrazy. Serializované pracovní postupy, které lze použít ExpressionServices.Convert
, lze otevřít v návrháři, ale hodnota výrazu bude prázdná. Další informace o serializaci pracovních postupů do XAML naleznete v tématu Serializace pracovních postupů a aktivit do a z XAML.
Literální výrazy a odkazové typy
Literální výrazy jsou reprezentovány v pracovních postupech aktivitou Literal<T> . Následující WriteLine aktivity jsou funkčně ekvivalentní.
new WriteLine
{
Text = "Hello World."
},
new WriteLine
{
Text = new Literal<string>("Hello World.")
}
Inicializace literálového výrazu s libovolným typem odkazu s výjimkou Stringje neplatná. V následujícím příkladu Assign je vlastnost aktivity Value inicializována literál výrazem pomocí List<string>
.
new Assign
{
To = new OutArgument<List<string>>(items),
Value = new InArgument<List<string>>(new List<string>())
},
Při ověření pracovního postupu obsahujícího tuto aktivitu se vrátí následující chyba ověření: Literál podporuje pouze typy hodnot a neměnný typ System.String. Typ System.Collections.Generic.List'1[System.String] nelze použít jako literál." Pokud je pracovní postup vyvolán, InvalidWorkflowException vyvolá se chyba, která obsahuje text chyby ověření. Jedná se o chybu ověření, protože vytvoření literálového výrazu s odkazovým typem nevytvoří novou instanci typu odkazu pro každou instanci pracovního postupu. Chcete-li tento problém vyřešit, nahraďte literál výrazem, který vytvoří a vrátí novou instanci typu odkazu.
new Assign
{
To = new OutArgument<List<string>>(items),
Value = new InArgument<List<string>>(new VisualBasicValue<List<string>>("New List(Of String)"))
},
Vyvolání metod u objektů pomocí výrazů a aktivity InvokeMethod
Aktivitu InvokeMethod<TResult> lze použít k vyvolání statických metod a metod instancí tříd v rozhraní .NET Framework. V předchozím příkladu v tomto tématu se pomocí třídy vygenerovalo Random náhodné číslo.
new Assign<int>
{
To = n,
Value = new VisualBasicValue<int>("New Random().Next(1, 101)")
}
Aktivita InvokeMethod<TResult> byla také použita k volání Next metody Random třídy.
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
}
Vzhledem k tomu Next , že není statická metoda, instance Random třídy je zadána TargetObject pro vlastnost. V tomto příkladu se vytvoří nová instance pomocí výrazu jazyka Visual Basic, ale dříve byla vytvořena a uložena v proměnné pracovního postupu. V tomto příkladu by bylo jednodušší místo aktivity používat Assign<T> aktivitu InvokeMethod<TResult> . Pokud volání metody je nakonec vyvoláno buď dlouhou Assign<T> dobu, nebo InvokeMethod<TResult> aktivity, InvokeMethod<TResult> má výhodu RunAsynchronously , protože má vlastnost. Pokud je tato vlastnost nastavena na true
, vyvolána metoda se spustí asynchronně s ohledem na pracovní postup. Pokud jsou další aktivity paralelně, nebudou blokovány, zatímco metoda se asynchronně spouští. Také, pokud metoda, která má být vyvolána nemá žádnou vrácenou hodnotu, pak InvokeMethod<TResult> je vhodný způsob, jak vyvolat metodu.
Argumenty a dynamické aktivity
Definice pracovního postupu je vytvořena v kódu sestavením aktivit do stromu aktivit a konfigurací všech vlastností a argumentů. Existující argumenty mohou být svázané, ale nové argumenty nelze přidat do aktivit. To zahrnuje argumenty pracovního postupu předané kořenové aktivitě. V imperativním kódu jsou argumenty pracovního postupu zadány jako vlastnosti nového typu CLR a v XAML jsou deklarovány pomocí x:Class
a x:Member
. Vzhledem k tomu, že neexistuje žádný nový typ CLR vytvořený při vytvoření definice pracovního postupu jako strom objektů v paměti, argumenty nelze přidat. Argumenty však lze přidat do .DynamicActivity V tomto příkladu se vytvoří vzorec DynamicActivity<TResult> , který vezme dva celočíselné argumenty, sečte je dohromady a vrátí výsledek. Pro každý argument se vytvoří A DynamicActivityProperty a výsledek operace se přiřadí Result argumentu 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);
Další informace o dynamických aktivitách naleznete v tématu Vytvoření aktivity za běhu.
Kompilované aktivity
Dynamické aktivity představují jeden ze způsobů, jak definovat aktivitu, která obsahuje argumenty pomocí kódu, ale aktivity je možné vytvořit také v kódu a zkompilovat do typů. Lze vytvořit jednoduché aktivity, které jsou odvozeny od CodeActivity, a asynchronní aktivity odvozené z AsyncCodeActivity. Tyto aktivity mohou mít argumenty, návratové hodnoty a definovat jejich logiku pomocí imperativního kódu. Příklady vytvoření těchto typů aktivit najdete v tématu CodeActivity Base Class a Vytváření asynchronních aktivit.
Aktivity odvozené z NativeActivity můžou definovat logiku pomocí imperativního kódu a mohou také obsahovat podřízené aktivity, které definují logiku. Mají také úplný přístup k funkcím modulu runtime, jako je vytváření záložek. Příklady vytvoření aktivity založené na NativeActivitynativní aktivitě naleznete v tématu NativeActivity Base Class, How to: Create an Activity a Custom Composite using Native Activity sample.
Aktivity, které jsou odvozeny od Activity definice své logiky výhradně prostřednictvím použití podřízených aktivit. Tyto aktivity se obvykle vytvářejí pomocí návrháře pracovního postupu, ale dají se také definovat pomocí kódu. V následujícím příkladu je definována Square
aktivita, která je odvozena z Activity<int>
. Aktivita Square
má jeden InArgument<T> pojmenovaný Value
a definuje jeho logiku zadáním Sequence aktivity pomocí Implementation vlastnosti. Aktivita Sequence obsahuje WriteLine aktivitu a Assign<T> aktivitu. Tyto tři aktivity společně implementují logiku Square
aktivity.
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))
}
}
};
}
}
V následujícím příkladu je definice pracovního postupu sestávající z jedné Square
aktivity vyvolána pomocí WorkflowInvoker.
Dictionary<string, object> inputs = new Dictionary<string, object> {{ "Value", 5}};
int result = WorkflowInvoker.Invoke(new Square(), inputs);
Console.WriteLine("Result: {0}", result);
Při vyvolání pracovního postupu se v konzole zobrazí následující výstup:
Squaring the value: 5
Výsledek: 25