【UiPath】コード化されたワークフローで RPA Challenge を動かす その２ - Xaml 形式のワークフローを呼び出す編

# はじめに
[前回](https://qiita.com/MasahiroHarada1105/items/fa4bf9e96339d0054a5c)は、コード化されたワークフローで RPA Challenge の自動化処理を実装しました。

公式ドキュメントを見ると、コード化されたオートメーションでは既存の Xaml 形式のワークフローとの相互呼び出しが出来ることが書かれています。

> コード化されたワークフローは標準ワークフローと同じように動作します。つまり、[ワークフロー ファイルを呼び出し] アクティビティを使用して標準の UiPath ワークフローから呼び出すことも、その逆も可能です。

https://docs.uipath.com/ja/studio/standalone/2023.10/user-guide/coded-automations-introduction

前回は、Excel ファイルの操作に NPOI を使いましたが、今回は上記メリットを活かして Excel ファイルの操作を既存の Xaml 形式のワークフローに委譲したいと思います。

この記事は、[UiPath (produced with UiPath Friends) Advent Calendar 2023](https://qiita.com/advent-calendar/2023/uipath) の 14日目です。

https://qiita.com/advent-calendar/2023/uipath

# Excel ファイル操作用のワークフローを作成する
UiPath Studio でプロジェクトを作成したら、デザインリボンの``新規``から``シーケンス``を選択します。名前は GetExcelData.xaml としました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3322296/1539dd16-bf5f-b8dc-014d-f09b410e4fc8.png)

こちらがワークフローの実装になります。とてもシンプルですね。
``範囲を読み込み``アクティビティを使ってテーブルデータを Out 引数の ``out_テーブルデータ`` に設定しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3322296/8c17e799-aa57-f08e-453a-6bd9efff8b6a.png)

引数の設定はこちらになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3322296/0ecd550b-fad7-39cc-8f39-5ccc0cd6b0d1.png)

# リファクタリングする
ワークフローの準備が出来たので、コード化されたワークフローの実装を修正していきます。
まずは、先ほど作成した GetExcelData.xaml を呼び出すコードを記述します。既存ワークフローは ``RunWorkflow(Xamlファイル名)``で呼び出すことが出来ます。

:::note info
``RunWorkflow``メソッドの呼び出しは同期となります。非同期で呼び出したい場合は``RunWorkflowAsync``メソッドを使います。
:::

呼び出した結果は ``IDictionary<string, object>`` 型で受け取ることが出来き、Key が引数名、Value が引数の値となっています。今回は ``out_テーブルデータ`` という Out 引数を ``DataTable`` 型として受け取っています。

```csharp:RPAChallenge.cs
IDictionary<string, object> args = RunWorkflow("GetExcelData.xaml");
DataTable dataTable =  (DataTable) args["out_テーブルデータ"];

// 以下コメントアウトします
// IWorkbook book = WorkbookFactory.Create(@".\challenge_ja.xlsx");
// ISheet sheet = book.GetSheetAt(0);
```

続いてループを修正します。今回は ``DataTable`` を使っての処理となるので、``foreach`` に置換えてみましょう。

```csharp:RPAChallenge.cs
// DataTable を使って 1行ずつ処理します
foreach(DataRow row in dataTable.Rows) {

// 以下コメントアウトします
// for (int i = 1; i <= sheet.LastRowNum; i++) {
//     IRow row = sheet.GetRow(i);
//     if (row == null) {
//         break;
//     }
//     if (row.GetCell(0) == null || string.IsNullOrEmpty(row.GetCell(0).ToString())) {
//         break;
//     }
```

最後にデータ入力処理を修正します。``DataRow`` からは ``row["苗字"].ToString()`` の形で値を取ることが出来ます。``TypeInto`` メソッドの第二引数に ``row`` から取得した値を設定しましょう。

```csharp:RPAChallenge.cs
screen.TypeInto(Descriptors.Chrome__Rpa_Challenge_app.Chrome__Rpa_Challenge.苗字, row["苗字"].ToString());
screen.TypeInto(Descriptors.Chrome__Rpa_Challenge_app.Chrome__Rpa_Challenge.名前, row["名前"].ToString());
screen.TypeInto(Descriptors.Chrome__Rpa_Challenge_app.Chrome__Rpa_Challenge.会社名, row["会社名"].ToString());
screen.TypeInto(Descriptors.Chrome__Rpa_Challenge_app.Chrome__Rpa_Challenge.部署, row["部署"].ToString());
screen.TypeInto(Descriptors.Chrome__Rpa_Challenge_app.Chrome__Rpa_Challenge.住所, row["住所"].ToString());
screen.TypeInto(Descriptors.Chrome__Rpa_Challenge_app.Chrome__Rpa_Challenge.メールアドレス, row["メールアドレス"].ToString());
screen.TypeInto(Descriptors.Chrome__Rpa_Challenge_app.Chrome__Rpa_Challenge.電話番号, row["電話番号"].ToString());
```

以上で完成です。リファクタリングしたコードはこちらになります。
```csharp:RPAChallenge.cs
using System;
using System.Collections.Generic;
using System.Data;
using UiPath.CodedWorkflows;
using UiPath.Core;
using UiPath.Core.Activities.Storage;
using UiPath.Orchestrator.Client.Models;
using UiPath.Testing;
using UiPath.Testing.Activities.TestData;
using UiPath.Testing.Activities.TestDataQueues.Enums;
using UiPath.Testing.Enums;
using UiPath.UIAutomationNext.API.Contracts;
using UiPath.UIAutomationNext.API.Models;
using UiPath.UIAutomationNext.Enums;
using CodedWorkflow_RPAChallenge.ObjectRepository;
using NPOI.SS.UserModel;

namespace CodedWorkflow_RPAChallenge
{
    public class RPAChallenge_ExcelActivities : CodedWorkflow
    {
        public TargetAppOptions targetAppOptions => new TargetAppOptions(){
            InteractionMode = NInteractionMode.Simulate,
            OpenMode = NAppOpenMode.IfNotOpen
        };

        [Workflow]
        public void Execute()
        {
            UiTargetApp screen = uiAutomation.Open(Descriptors.Chrome__Rpa_Challenge_app.Chrome__Rpa_Challenge, targetAppOptions);

            screen.Click(Descriptors.Chrome__Rpa_Challenge_app.Chrome__Rpa_Challenge.開始);

            IDictionary<string, object> args = RunWorkflow("GetExcelData.xaml");
            DataTable dataTable =  (DataTable) args["out_テーブルデータ"];

            foreach(DataRow row in dataTable.Rows) {
                screen.TypeInto(Descriptors.Chrome__Rpa_Challenge_app.Chrome__Rpa_Challenge.苗字, row["苗字"].ToString());
                screen.TypeInto(Descriptors.Chrome__Rpa_Challenge_app.Chrome__Rpa_Challenge.名前, row["名前"].ToString());
                screen.TypeInto(Descriptors.Chrome__Rpa_Challenge_app.Chrome__Rpa_Challenge.会社名, row["会社名"].ToString());
                screen.TypeInto(Descriptors.Chrome__Rpa_Challenge_app.Chrome__Rpa_Challenge.部署, row["部署"].ToString());
                screen.TypeInto(Descriptors.Chrome__Rpa_Challenge_app.Chrome__Rpa_Challenge.住所, row["住所"].ToString());
                screen.TypeInto(Descriptors.Chrome__Rpa_Challenge_app.Chrome__Rpa_Challenge.メールアドレス, row["メールアドレス"].ToString());
                screen.TypeInto(Descriptors.Chrome__Rpa_Challenge_app.Chrome__Rpa_Challenge.電話番号, row["電話番号"].ToString());

                screen.Click(Descriptors.Chrome__Rpa_Challenge_app.Chrome__Rpa_Challenge.登録);
            }
        }
    }
}
```

# 実行する
リファクタリング前の実装に比べて Excel が開く分だけ遅くなりましたが入力ミスもなく動作できました。

:::note info
エクセルファイルの読込みを開始ボタンのクリックより前に移動すれば RPA Challenge の計測結果が良くなりますが、処理全体の実行時間を見たかったのであえて開始ボタンのクリック後にファイルを読込みでいます。
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3322296/23a06931-2e28-abfc-4984-f7afbbcabcec.png)

# おわりに
コード化されたオートメーションでも既存の資産を活かしつつ開発できることを実感いただけましたでしょうか？コードで実装するのが難しい、可読性が下がるなど出てきたら既存のワークフローで実装して呼び出すのも手かと思います。実装の幅が広がるので是非活用ください。