Pull to refresh

Все свое: туториал по созданию новых действий для UiPath RPA

Reading time12 min
Views21K

Данная статья – обучающий материал, который позволяет по шагам пройтись по процессу создания и публикации нового компонента для платформы UiPath RPA. Это удобно в разных случаях, например, для простой интеграции с системами компании или в ситуациях, когда компания хочет помочь сотрудникам, не особенно разбирающихся в программировании, создавать свои процессы для роботов. Но, как мне кажется, особенно хорошо данный прием работает для случаев, когда вам нужно встроить свой продукт в экосистему роботизации, позволяя пользователям в пару кликов настроить интеграцию внутренних процессов. В качестве примера можно привести коннектор, написанный Abbyy для своего продукта Abbyy Flexicapture Distributed. Еще один пример — сама компания UiPath, которая таким образом реализовала подключение к своему новому ML-модулю компьютерного зрения (Computer Vision в менеджере пакетов), действиям работы с PDF и т.д.


Сейчас в магазине приложений UiPath Go! уже вовсю начали появляться коннекторы к популярным решениям, таким как Salesforce или ServiceNow, и, скорее всего, этот тренд только набирает популярность.


Итак, как же начать разработку своего собственного действия?


N.B. По-английски действие — это Activity, помните об этом, если надо будет что-то погуглить

Если не хочется заниматься всеми настройками, можно попробовать пример кода, сделанного по шагам в этом туториале. Он лежит на GitLab. Код в статье сокращен и упрощен, он доступен на Yandex Disk


Робот строит действие для UiPath


Что нам понадобится перед тем как начать работать:


  1. Visual Studio (прекрасно подойдет бесплатная версия VS Community Edition. При установке надо выбрать .NET Desktop Development или вручную указать следующие пакеты:
    1. NuGet Package Manager
    2. .NET Framework 4.6.1 targeting pack (нужен именно 4.6)
    3. C# and Visual Basic (действия можно писать можно на любом .NET языке, но, традиционно, используется C# или VB.NET). В данном туториале будет использоваться C#.
    4. Windows Workflow Foundation
  2. UiPath Studio (и тут тоже самое, бесплатная CE, надо только заполнить анкету UiPath Community Edition).
    N.B. Для самой разработки действия он нам не понадобится, но, конечно, хочется увидеть, что же мы сделаем.
  3. NuGet Package Manager (идет в комплекте с VS 2017+ или скачивается с Nuget.org

Начинаем творить


Создаем проект в VS


  1. Создаем проект C# Class Library (.NET Framework). Обратите внимание, что вариации на тему (например C# Class Library (.NET Standard)) нам не подходят.

Экран создания проекта


  1. Выбираем ему осмысленное название (обычный формат <Разработчик>.UiPath.Activities.<Название проекта>) и указываем, что хотим использовать .NET Framework 4.6.1.
    N.B ID пакета NuGet должен будет содержать слово Activities. Поскольку мы хотим все брать из VS, лучше сразу называть проект правильно, чтобы потом подхватить это в NuGet.

Экран настройки проекта


  1. В созданном проекте создаем следующие вложенные папки (это необязательный шаг, но так удобнее работать с пакетами действий, когда вы хотите предоставлять пользователям более одного действия за раз и количество файлов начинает накапливаться):
    • Activities (здесь у нас будет код действий)
    • Designer (здесь у нас будет форма окон действий)
    • Resources (здесь мы будем хранить картинки, иконки и прочие нужные штуки)
  2. Переименовываем файл Class1.cs который для нас создал в VS соответственно названию нашего первого действия и перемещаем его в папку Activities.

Структура папок проекта


Простое создание и публикация действия


На этом этапе у нас есть пустой проект, компилируемый в .dll. Теперь перейдем к созданию самого действия.


N.B. Поскольку UiPath поддерживает несколько языков, нам нужно будет, в конце, добавить локализацию, и чуть поменять то, что мы сейчас делаем, но начнем с того, что необходимо и достаточно.

  1. Нам нужно два using:
    using System;
    using System.Activities;
  2. Наследуем наш класс от CodeActivity:
    public class TutorialForHabr: CodeActivity {}
  3. Зададим входные и выходные параметры:
    public class TutorialForHabr: CodeActivity
    {
    public InArgument<Int32> Row { get; set; }
    public InArgument<Int32> Column { get; set; }
    public OutArgument<String> Cell { get; set; }
    }

    N.B. Параметры действия бывают входные InArgument<Тип Данных>, выходные OutArgument<Тип Данных> и смешанные, позволяющие как принимать, так и получать параметры InOutArgument<Тип Данных>
  4. И метод Execute, являющийся единственным методом, который мы должны обязательно задать:
    protected override void Execute(CodeActivityContext context)
    {
    string colstr = "";
    //получаем значение параметра Row
    Int32 row = Row.Get(context);
    //получаем значение параметра Column
    Int32 column = Column.Get(context);
    //вычисляем букву столбца в Excel
    do
    {
        column--;
    //26 это длина английского алфавита, количество столбцов в Excel, после которого их название начинает удваиваться, после Z идет AA
        int modn = column % 26;
        colstr = (char)((int)'A' + modn) + colstr; //A это первый столбец в Excel
        column /= 26;
    } while (column > 0);
    //присваиваем выходному параметру Cell значение
    Cell.Set(context, colstr + row.ToString());
    }

На этом этапе у нас есть работающее действие для UiPath, реализующее простую но нужную функциональность.
Опубликуем его, а потом перейдем ко второй части и займемся, немножко, "украшательствами".


Публикация через NuGet


Создать пакет NuGet можно через GUI NuGet Package Manager или через командную строку с помощью вызова nuget.exe pack. Команда pack принимает на вход два основных параметра — имя файла проекта .csproj или имя файла .nuspec, содержащего в себе мета-информацию пакета. Первый вариант не позволяет нам, без дополнительных ручных модификаций файла .json устанавливать некоторые, важные нам свойства пакета, например тэги, лицензию или URL репозитория, плюс не дает задавать для проекта иконку, которую будет видно в менеджере пакетов.
Во втором варианте мы дублирует некоторые свойства пакета, которые, все-таки, могли бы автоматически браться из Visual Studio.
Поэтому мы пойдем по пути 1.5, и создадим в корне проекта файл с расширением .nuspec (имя неважно, пока такой файл один), который будет содержать ту информацию, которую нельзя получить из .csproj и комбинировать два этих источника данных.


<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
  <metadata>
    <id>$id$</id>
    <title>$title$</title>
    <version>$version$</version>
    <authors>$author$</authors>
    <owners>$author$</owners>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <licenseUrl>https://choosealicense.com/licenses/mit/</licenseUrl>
    <projectUrl>https://gitlab.com/ilyak/ilyak.uipath.activities</projectUrl>
    <iconUrl>http://www.uipath.com/hubfs/Logos/uipath-nuget-logo.png</iconUrl>
    <description>$description$</description>
    <copyright>$copyright$</copyright>
    <tags>UiPath,Activity,Excel</tags>
  </metadata>
  <files>
    <file src="bin\Debug\IlyaK.UiPath.Activities.TutorialForHabr.dll" target="lib\net461\IlyaK.UiPath.Activities.TutorialForHabr.dll" />
  </files>
</package>

Как вы видите, структура .nuspec достаточно очевидна. Если неохота возиться с XML, вы можете ее создать через визуальный редактор NuGet Package Manager и сохранить в папке проекта для дальнейшего использования. Переменные типа $copyright берутся из информации, содержащейся в файле .dll, т.е. из Visual Studio.
Теперь мы хотим использовать наш .nuspec для того, чтобы при каждом билде у нас получался новый пакет. Для этого мы идем в Properties и переходим на таб Build Events.
В Post-build event command line пишем волшебные слова

IF EXIST "$(ProjectDir)$(OutDir)*.nupkg" del "$(ProjectDir)$(OutDir)*.nupkg"
nuget.exe pack "$(ProjectPath)"
IF EXIST "$(ProjectDir)$(OutDir)*.nupkg" xcopy /Y "$(ProjectDir)$(OutDir)*.nupkg" "C:\Program Files (x86)\UiPath\Studio\Packages\"

которые, в переводе на русский, означают


  1. Удалить старые пакеты в папке, где создается билд
  2. Создать новый пакет используя файлы .csproj и .nuspec
  3. Положить его в папку C:\Program Files (x86)\UiPath\Studio\Packages\

Что зто за папка? Это локальный репозиторий пакетов, использующийся UiPath Studio. Как только там появится новый пакет, он автоматически будет доступен через менеджер пакетов и его можно будет добавлять в процессы роботизации.


Окно UiPath Studio Package Manager


N.B Путь к локальному репозиторию можно поменять через настройки UiPath Studio, чтобы было удобно копировать туда файлы, если есть проблема с разрешениями.

Экран UiPath Studio с созданным действием и его настройками


Вот, собственно, и все, поздравляю!


N.B Обратите внимание, что умная Студия сама создала для нашего действия категорию, используя точки как уровни вложенности, и поставила пробелы перед заглавными буквами в названии действия.

Следующая часть статьи будет про "красивости": про то, как сделать для действия визуальный интерфейс, назначить иконку и локализовать тексты.


Делаем серьезно


Атрибуты


Для того, чтобы наше действие работало, первой части вполне достаточно. Но хочется, чтобы мы могли выбирать категорию, чтобы показывались подсказки и т.д.
Для этого нам нужны атрибуты. Есть атрибуты, которые подходят и для самого действия, и для его параметров:


[Category ("Категория действия или параметра, уровни вложенности задаются через точки")]
[DisplayName ("Красивое название действия или параметра")]
[Description ("Длинное описание действия или параметра")]

А есть и такие, которые нужны только параметрам:


[RequiredArgument] //делает входной аргумент обязательным
[DefaultValue(1)]  //задает значение по умолчанию для входного аргумента

Локализация


К сожалению, с атрибутами есть одна тонкость: в русской версии UiPath Studio названия категорий действий и категорий их свойств тоже переведены. Соответственно если мы зададим атрибут [Category("Input")] для входного параметра, он будет показываться корректно в английской версии, но в русской версии он попадет в свою отдельную категорию Input, а не в стандартную категорию Ввод. Тоже самое касается действий, в русской версии App Integration становится Интеграцией приложений.
Значит нам надо выносить текстовые константы в языко-зависимые ресурсы и использовать их оттуда.
Для этого создаем волшебный файл Localization.cs, содержащий функции для локализации атрибутов


Localization.cs
using IlyaK.UiPath.Activities.TutorialForHabr.Properties;
using System;
using System.ComponentModel;

namespace IlyaK.UiPath.Activities.TutorialForHabr
{
    [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Parameter | AttributeTargets.Delegate | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter)]
    public class LocalizedCategoryAttribute : CategoryAttribute
    {
        public LocalizedCategoryAttribute(string category)
            : base(category)
        {
        }

        protected override string GetLocalizedString(string value)
        {
            return Resources.ResourceManager.GetString(value) ?? base.GetLocalizedString(value);
        }
    }

    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Class)]
    public class LocalizedDisplayNameAttribute : DisplayNameAttribute
    {
        public LocalizedDisplayNameAttribute(string displayName)
            : base(displayName)
        {

        }

        public override string DisplayName
        {
            get
            {
                return Resources.ResourceManager.GetString(DisplayNameValue) ?? base.DisplayName;
            }
        }
    }

    public class LocalizedDescriptionAttribute : DescriptionAttribute
    {
        public LocalizedDescriptionAttribute(string displayName)
            : base(displayName)
        {

        }

        public override string Description
        {
            get
            {
                return Resources.ResourceManager.GetString(DescriptionValue) ?? base.Description;
            }
        }
    }
}

Он позволит нам заменить конструкцию


[Category("Name")

на


[LocalizedCategory(nameof(Resources.CategoryName))]

чтобы этот механизм заработал, мы должны:


  1. Создать файлы ресурсов для тех языков, на которые мы собираемся переводить, в нашем случае это Resources.resx и Resources.ru.resx в папке Resources (Add -> New Item -> Resource File). Убедится, что значение Access Modifier для ресурсов стоит в Public.
  2. Добавить в файл с классом действия
    using IlyaK.UiPath.Activities.TutorialForHabr.Properties; //замените на имя вашего проекта + .Properties
  3. И поставить классу и всем параметрам соответствующие атрибуты


    [LocalizedCategory(nameof(Resources.AppIntegrationExcel))]
    [LocalizedDisplayName(nameof(Resources.TutorialForHabrName))]
    [LocalizedDescription(nameof(Resources.TutorialForHabrDescription))]
    public class TutorialForHabr : CodeActivity
    {
        [LocalizedCategory(nameof(Resources.Input))]
        [LocalizedDisplayName(nameof(Resources.RowName))]
        [LocalizedDescription(nameof(Resources.RowDescription))]
        public InArgument<Int32> Row { get; set; }
    
        [LocalizedCategory(nameof(Resources.Input))]
        [LocalizedDisplayName(nameof(Resources.ColumnName))]
        [LocalizedDescription(nameof(Resources.ColumnDescription))]
        public InArgument<Int32> Column { get; set; }
    
        [LocalizedCategory(nameof(Resources.Output))]
        [LocalizedDisplayName(nameof(Resources.CellName))]
        [LocalizedDescription(nameof(Resources.CellDescription))]
    }

  4. Для того, чтобы библиотека с русскими ресурсами попала в пакет NuGet, нужно добавить в файл .nuspec еще одну строку в группу files
    <files>
    <file src="bin\Debug\IlyaK.UiPath.Activities.TutorialForHabr.dll" target="lib\net461\IlyaK.UiPath.Activities.TutorialForHabr.dll" />
    <file src="bin\Debug\ru\**" target="lib\net461\ru\" />
    </files>

Дизайнер


Чтобы наше действие выглядело в процессе красиво и позволяло пользователям вводить данные, не залезая в свойства, нужно добавить UI. Это делается через файл в формате XAML, который, с помощью Visual Studio, можно редактировать в специальном интерфейсе, но часть все равно придется исправлять руками. Я приведу шаблон, с которого можно начать, а дальше стоит поизучать документацию Microsoft.


  • Кладем в папку Resources иконку для действия. В свойствах Build Action ставим в значение Resource (не Embedded Resource)
  • Создаем в папке Designer новый Activity Designer (Add -> New Item -> Activity Designer) и называем его TutorialForHabr

TutorialForHabr.xaml
<sap:ActivityDesigner x:Class="IlyaK.UiPath.Activities.TutorialForHabr.Designer.TutorialForHabrDesigner"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:s="clr-namespace:System;assembly=mscorlib"
    xmlns:resource="clr-namespace:IlyaK.UiPath.Activities.TutorialForHabr.Properties"
    xmlns:sap="clr-namespace:System.Activities.Presentation;assembly=System.Activities.Presentation"
    xmlns:sapc="clr-namespace:System.Activities.Presentation.Converters;assembly=System.Activities.Presentation"
    xmlns:sapv="clr-namespace:System.Activities.Presentation.View;assembly=System.Activities.Presentation">
    <sap:ActivityDesigner.Resources>
        <ResourceDictionary>
            <sapc:ArgumentToExpressionConverter x:Key="ArgumentToExpressionConverter" />
        </ResourceDictionary>
    </sap:ActivityDesigner.Resources>

    <DockPanel Width="200">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="70"></ColumnDefinition>
                <ColumnDefinition Width="130"></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition></RowDefinition>
                <RowDefinition></RowDefinition>
            </Grid.RowDefinitions>
            <TextBlock Grid.Row="0" Grid.Column="0" Text="{x:Static resource:Resources.RowName}" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0,0,5,0"></TextBlock>
            <sapv:ExpressionTextBox Grid.Row="0" Grid.Column="1"  OwnerActivity="{Binding Path=ModelItem}" ExpressionType="{x:Type s:Int32}" HintText="{x:Static resource:Resources.RowDescription}" Expression="{Binding Path=ModelItem.Row, Converter={StaticResource ArgumentToExpressionConverter},ConverterParameter=In, Mode=TwoWay}"/>
            <TextBlock Grid.Row="1" Grid.Column="0" Text="{x:Static resource:Resources.ColumnName}" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="0,0,5,0"></TextBlock>
            <sapv:ExpressionTextBox Grid.Row="1" Grid.Column="1"  OwnerActivity="{Binding Path=ModelItem}" ExpressionType="{x:Type s:Int32}" HintText="{x:Static resource:Resources.ColumnDescription}" Expression="{Binding Path=ModelItem.Column, Converter={StaticResource ArgumentToExpressionConverter},ConverterParameter=In, Mode=TwoWay}"/>
        </Grid>
    </DockPanel>
    <sap:ActivityDesigner.Icon>
        <DrawingBrush>
            <DrawingBrush.Drawing>
                <ImageDrawing>
                    <ImageDrawing.Rect>
                        <Rect Location="0,0" Size="32,32" ></Rect>
                    </ImageDrawing.Rect>
                    <ImageDrawing.ImageSource>
                        <BitmapImage UriSource="/IlyaK.UiPath.Activities.TutorialForHabr;component/Resources/Tutorial_32x32.png"></BitmapImage>
                    </ImageDrawing.ImageSource>
                </ImageDrawing>
            </DrawingBrush.Drawing>
        </DrawingBrush>
    </sap:ActivityDesigner.Icon>
</sap:ActivityDesigner>

Вот так мы задаем локализованную метку:


<TextBlock
    Grid.Row="0" Grid.Column="0"
    Text="{x:Static resource:Resources.RowName}"
    VerticalAlignment="Center"
    HorizontalAlignment="Right"
    Margin="0,0,5,0"
/>

, а вот так — поле для ввода. ExpressionType задает тип поля, HintText — подсказку (серый текст) для него, Expression связывает значение поля с нашим действием


<sapv:ExpressionTextBox
    OwnerActivity="{Binding Path=ModelItem}"
    Grid.Row="0" Grid.Column="1"
    ExpressionType="{x:Type s:Int32}"
    HintText="{x:Static resource:Resources.RowDescription}"
    Expression="{Binding Path=ModelItem.Row, Converter={StaticResource ArgumentToExpressionConverter},ConverterParameter=In, Mode=TwoWay}"
/>

N.B. Обратите внимание на тэг BitmapImage в конце файла. Так мы задаем картинку для иконки с привязкой к ресурсному файлу. Это очень хрупкая функциональность и иногда приходится с ней повозиться.

по итогам должно получиться вот так:


экран дизайнера действия


  • Создаем в папке Activities класс DesignerMetata

DesignerMetata.cs
using System.Activities.Presentation.Metadata;
using System.ComponentModel;

namespace IlyaK.UiPath.Activities.TutorialForHabr.Design
{
    public class DesignerMetadata : IRegisterMetadata
    {
        public void Register()
        {
            AttributeTableBuilder attributeTableBuilder = new AttributeTableBuilder();
            attributeTableBuilder.AddCustomAttributes(typeof(TutorialForHabr), new DesignerAttribute(typeof(Designer.TutorialForHabrDesigner)));
            MetadataStore.AddAttributeTable(attributeTableBuilder.CreateTable());
        }
    }
}

  • Запускаем билд
  • И обновляем пакет в UiPath Studio.
    N.B Чтобы это у вас получилось, нужно, что версия нового пакета отличалась от версии старого. Проще всего это сделать с помощью какого-нибудь плагина к Visual Studio, например Build Version Increment Add-In. Или обновите версию вручную.

Вот финальный вид нашего действия, с иконкой и полями ввода:
готовое действия


Заключение


Надеюсь, что мне удалось показать, что создание даже полностью оформленных действий для UiPath — это не так уже долго. Мне бы очень хотелось, чтобы эта статья была вам полезна и послужила хорошим стартом для написания новых и замечательных действий для UiPath. А когда его создадите — не забудьте опубликовать на UiPath Go!


Дополнительные материалы


Tags:
Hubs:
+8
Comments1

Articles