- Overview
- Custom activities
- Migrating Activities to .NET 6
- Release Notes
- Building Workflow Analyzer Rules
- Building Activities Project Settings
- Creating Custom Wizards
- Prioritize Activities by Scope
- UiPath.Activities.Api.Base
- UiPath.Studio.Activities.Api
- UiPath.Studio.Activities.Api.Activities
- UiPath.Studio.Activities.Api.BusyService
- UiPath.Studio.Activities.Api.ExpressionEditor
- UiPath.Studio.Activities.Api.Expressions
- UiPath.Studio.Activities.Api.Licensing
- UiPath.Studio.Activities.Api.Mocking
- UiPath.Studio.Activities.Api.ObjectLibrary
- UiPath.Studio.Activities.Api.PackageBindings
- UiPath.Studio.Activities.Api.ProjectProperties
- UiPath.Studio.Activities.Api.ScopedActivities
- UiPath.Studio.Activities.Api.Settings
- UiPath.Studio.Activities.Api.Wizards
- UiPath.Studio.Activities.Api.Workflow
- UiPath.Studio.Api.Controls
- UiPath.Studio.Api.Telemetry
- UiPath.Studio.Api.Theme
- Robot JavaScript SDK
- Triggers SDK
Writing the activity code
To exemplify how to write activity code, we will recreate a simple Calculator activity that is included in the sample UiPath.Examples.Activities solution that you can download from GitHub. This activity takes two numbers and an operation (add, subtract, multiply, or divide) as input and returns the result of the selected operation.
The activity code consists of two parts - the activity logic and the activity design.
Starting from UiPath.Activities.Template, we'll rename the solution and all related files and references to UiPath.Examples.Activities.
- Rename the file that holds the activity logic from ActivityTemplate.cs to Calculator.cs.
-
Update the references and namespace as follows:
using System.Activities; using System.Diagnostics; using UiPath.Examples.Activities.Helpers; namespace UiPath.Examples.Activities { }
using System.Activities; using System.Diagnostics; using UiPath.Examples.Activities.Helpers; namespace UiPath.Examples.Activities { } -
Declare the input arguments - two numbers (
FirstNumber
andSecondNumber
asint
) and the operation to perform (SelectedOperation
asenum
, with an optional default value set toMultiply
). Mark all three arguments as required using the[RequiredArgument]
attribute. The returned value will be used to set the value of the Result argument.public class Calculator : CodeActivity<int> // This base class exposes an OutArgument named Result { [RequiredArgument] public InArgument<int> FirstNumber { get; set; } //InArgument allows a varriable to be set from the workflow [RequiredArgument] public InArgument<int> SecondNumber { get; set; } [RequiredArgument] public Operation SelectedOperation { get; set; } = Operation.Multiply; // default value is optional /* * The returned value will be used to set the value of the Result argument */ }
public class Calculator : CodeActivity<int> // This base class exposes an OutArgument named Result { [RequiredArgument] public InArgument<int> FirstNumber { get; set; } //InArgument allows a varriable to be set from the workflow [RequiredArgument] public InArgument<int> SecondNumber { get; set; } [RequiredArgument] public Operation SelectedOperation { get; set; } = Operation.Multiply; // default value is optional /* * The returned value will be used to set the value of the Result argument */ } -
Starting the execution part, we will add logging, get the values of the numbers from the workflow context, and add logic to handle the scenario of a division by zero.
protected override int Execute(CodeActivityContext context) { // This is how you can log messages from your activity. logs are sent to the Robot which will forward them to Orchestrator context.GetExecutorRuntime().LogMessage(new Robot.Activities.Api.LogMessage() { EventType = TraceEventType.Information, Message = "Executing Calculator activity" }); var firstNumber = FirstNumber.Get(context); //get the value from the workflow context (remember, this can be a variable) var secondNumber = SecondNumber.Get(context); if (secondNumber == 0 && SelectedOperation == Operation.Divide) { throw new DivideByZeroException("Second number should not be zero when the selected operation is divide"); } return ExecuteInternal(firstNumber, secondNumber); }
protected override int Execute(CodeActivityContext context) { // This is how you can log messages from your activity. logs are sent to the Robot which will forward them to Orchestrator context.GetExecutorRuntime().LogMessage(new Robot.Activities.Api.LogMessage() { EventType = TraceEventType.Information, Message = "Executing Calculator activity" }); var firstNumber = FirstNumber.Get(context); //get the value from the workflow context (remember, this can be a variable) var secondNumber = SecondNumber.Get(context); if (secondNumber == 0 && SelectedOperation == Operation.Divide) { throw new DivideByZeroException("Second number should not be zero when the selected operation is divide"); } return ExecuteInternal(firstNumber, secondNumber); } -
Add the calculations to execute for each selected operation.
public int ExecuteInternal(int firstNumber, int secondNumber) { return SelectedOperation switch { Operation.Add => firstNumber + secondNumber, Operation.Subtract => firstNumber - secondNumber, Operation.Multiply => firstNumber * secondNumber, Operation.Divide => firstNumber / secondNumber, _ => throw new NotSupportedException("Operation not supported"), }; }
public int ExecuteInternal(int firstNumber, int secondNumber) { return SelectedOperation switch { Operation.Add => firstNumber + secondNumber, Operation.Subtract => firstNumber - secondNumber, Operation.Multiply => firstNumber * secondNumber, Operation.Divide => firstNumber / secondNumber, _ => throw new NotSupportedException("Operation not supported"), }; } -
Define the operations.
public enum Operation { Add, Subtract, Multiply, Divide }
public enum Operation { Add, Subtract, Multiply, Divide }
int
data type of the
FirstNumber
and SecondNumber
properties results in
a number editor as the input field for the properties, while for
Operation
, which has the enum
data type, a
dropdown menu will be available in the activity.
The labels and tooltips of the properties can be defined in the file Resources.resx.
The following table describes the most common properties available for each activity property.
Property | Description |
---|---|
DisplayName | The label of the property. |
Tooltip | The text to display when hovering over the property |
IsRequired1 | Whether the property is required. Required properties must also be
marked in the activity using the [RequiredArgument]
attribute.
|
IsPrincipal2 | Whether the property should always be visible in the main category of
the activity. If set to false , the property appears
under a Show advanced options menu that is collapsed by
default.
|
OrderIndex | The order in which to display the property. |
1 Not available for output properties, which are never mandatory.
2 By convention, output properties are placed at the end of the activity under advanced options.
- Rename the file ActivityTemplateViewModel.cs to CalculatorViewModel.cs and add the code for the activity user interface to it.
-
Update the references and namespace as follows:
using System.Activities.DesignViewModels; using System.Diagnostics; namespace UiPath.Examples.Activities.ViewModels { }
using System.Activities.DesignViewModels; using System.Diagnostics; namespace UiPath.Examples.Activities.ViewModels { } -
Declare the input properties. The result property comes from the base class of the activity. The names and type arguments must match the ones from the activity.
public class CalculatorViewModel : DesignPropertiesViewModel { /* * Properties names must match the names and generic type arguments of the properties in the activity * Use DesignInArgument for properties that accept a variable */ public DesignInArgument<int> FirstNumber { get; set; } public DesignInArgument<int> SecondNumber { get; set; } /* * Use DesignProperty for properties that accept a constant value */ public DesignProperty<Operation> SelectedOperation { get; set; } /* * The result property comes from the activity's base class */ public DesignOutArgument<int> Result { get; set; } public CalculatorViewModel(IDesignServices services) : base(services) { } }
public class CalculatorViewModel : DesignPropertiesViewModel { /* * Properties names must match the names and generic type arguments of the properties in the activity * Use DesignInArgument for properties that accept a variable */ public DesignInArgument<int> FirstNumber { get; set; } public DesignInArgument<int> SecondNumber { get; set; } /* * Use DesignProperty for properties that accept a constant value */ public DesignProperty<Operation> SelectedOperation { get; set; } /* * The result property comes from the activity's base class */ public DesignOutArgument<int> Result { get; set; } public CalculatorViewModel(IDesignServices services) : base(services) { } } -
Add the code for the activity design. Optionally, we can add a breakpoint to debug ViewModel initialization by uncommenting the line containing
Debugger.Break();
. We will initialize the properties of the ViewModel, add aPersistValuesChangedDuringInit()
call which is mandatory when you change the property values during initialization, and define the activity input and output properties.The code should look as follows:
protected override void InitializeModel() { //Debugger.Break(); /* * The base call will initialize the properties of the view model with the values from the xaml or with the default values from the activity */ base.InitializeModel(); PersistValuesChangedDuringInit(); // just for heads-up here; it's a mandatory call only when you change the values of properties during initialization var orderIndex = 0; FirstNumber.DisplayName = Resources.Calculator_FirstNumber_DisplayName; FirstNumber.Tooltip = Resources.Calculator_FirstNumber_Tooltip; /* * Required fields will automatically raise validation errors when empty. * Unless you do custom validation, required activity properties should be marked as such both in the view model and in the activity: * -> in the view model use the IsRequired property * -> in the activity use the [RequiredArgument] attribute. */ FirstNumber.IsRequired = true; FirstNumber.IsPrincipal = true; // specifies if it belongs to the main category (which cannot be collapsed) FirstNumber.OrderIndex = orderIndex++; // indicates the order in which the fields appear in the designer (i.e. the line number); SecondNumber.DisplayName = Resources.Calculator_SecondNumber_DisplayName; SecondNumber.Tooltip = Resources.Calculator_SecondNumber_Tooltip; SecondNumber.IsRequired = true; SecondNumber.IsPrincipal = true; SecondNumber.OrderIndex = orderIndex++; SelectedOperation.DisplayName = Resources.Calculator_SelectedOperation_DisplayName; SelectedOperation.Tooltip = Resources.Calculator_SelectedOperation_Tooltip; SelectedOperation.IsRequired = true; SelectedOperation.IsPrincipal = true; SelectedOperation.OrderIndex = orderIndex++; /* * Output properties are never mandatory. * By convention, they are not principal and they are placed at the end of the activity. */ Result.DisplayName = Resources.Calculator_Result_DisplayName; Result.Tooltip = Resources.Calculator_Result_Tooltip; Result.OrderIndex = orderIndex; }
protected override void InitializeModel() { //Debugger.Break(); /* * The base call will initialize the properties of the view model with the values from the xaml or with the default values from the activity */ base.InitializeModel(); PersistValuesChangedDuringInit(); // just for heads-up here; it's a mandatory call only when you change the values of properties during initialization var orderIndex = 0; FirstNumber.DisplayName = Resources.Calculator_FirstNumber_DisplayName; FirstNumber.Tooltip = Resources.Calculator_FirstNumber_Tooltip; /* * Required fields will automatically raise validation errors when empty. * Unless you do custom validation, required activity properties should be marked as such both in the view model and in the activity: * -> in the view model use the IsRequired property * -> in the activity use the [RequiredArgument] attribute. */ FirstNumber.IsRequired = true; FirstNumber.IsPrincipal = true; // specifies if it belongs to the main category (which cannot be collapsed) FirstNumber.OrderIndex = orderIndex++; // indicates the order in which the fields appear in the designer (i.e. the line number); SecondNumber.DisplayName = Resources.Calculator_SecondNumber_DisplayName; SecondNumber.Tooltip = Resources.Calculator_SecondNumber_Tooltip; SecondNumber.IsRequired = true; SecondNumber.IsPrincipal = true; SecondNumber.OrderIndex = orderIndex++; SelectedOperation.DisplayName = Resources.Calculator_SelectedOperation_DisplayName; SelectedOperation.Tooltip = Resources.Calculator_SelectedOperation_Tooltip; SelectedOperation.IsRequired = true; SelectedOperation.IsPrincipal = true; SelectedOperation.OrderIndex = orderIndex++; /* * Output properties are never mandatory. * By convention, they are not principal and they are placed at the end of the activity. */ Result.DisplayName = Resources.Calculator_Result_DisplayName; Result.Tooltip = Resources.Calculator_Result_Tooltip; Result.OrderIndex = orderIndex; } - Add the string values for the
labels and tooltips in the file Resources.resx as in the following image.
For localization purposes, you must use specific comments for the activity name
(
Activity name
) and activity description (Activity description
). For other strings, adding comments is recommended but not mandatory.
In Studio, the configuration results in the following:
1 - Labels (display names) of the three input properties.
FirstNumber
property.