UiPath Activity Creatorを使ってみる。（その５：スコープを持ったアクティビティの作成）

#このドキュメントの概要
　[前回](https://qiita.com/SuetakeYoichi/items/117126227867d580d590) はActivity Creatorでのアクティビティの外観の設定方法について見ていきました。
　今回はスコープをもったアクティビティの作成方法について見ていきます。

#WizardでのScope指定
　ActivityCreatorのWizardでACtivityの定義画面でType項目をScopeに設定するとScopeをもったアクティビティになります。また同一のWizard画面内でScopeを持ったアクティビティとSimple設定のアクティビティを作成すると、そのSimpleアクティビティはScopeアクティビティのChildアクティビティとして設定されます。
![uac5-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477147/5729d515-9686-4fad-08e2-70d67653b6a4.png)
TestScopeアクティビティのプロパティを以下のように設定しました。
![uac5-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477147/3e43c1bc-5c60-0725-7ba6-7504ffd0e824.png)

またTestChildアクティビティのプロパティを以下のように設定しました。
![uac5-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477147/d9a73501-26a1-5820-bbcc-ab9c41207c57.png)



TestScopeアクティビティとして以下のコードが自動生成されます。

```TestScope.cs
using System;
using System.Activities;
using System.Threading;
using System.Threading.Tasks;
using System.Activities.Statements;
using System.ComponentModel;
using Company.Product3.Activities.Properties;
using UiPath.Shared.Activities;
using UiPath.Shared.Activities.Localization;

namespace Company.Product3.Activities
{
    [LocalizedDisplayName(nameof(Resources.TestScope_DisplayName))]
    [LocalizedDescription(nameof(Resources.TestScope_Description))]
    public class TestScope : ContinuableAsyncNativeActivity
    {
        #region Properties

        [Browsable(false)]
        public ActivityAction<IObjectContainer​> Body { get; set; }

        /// <summary>
        /// If set, continue executing the remaining activities even if the current activity has failed.
        /// </summary>
        [LocalizedCategory(nameof(Resources.Common_Category))]
        [LocalizedDisplayName(nameof(Resources.ContinueOnError_DisplayName))]
        [LocalizedDescription(nameof(Resources.ContinueOnError_Description))]
        public override InArgument<bool> ContinueOnError { get; set; }

        [LocalizedDisplayName(nameof(Resources.TestScope_Str_DisplayName))]
        [LocalizedDescription(nameof(Resources.TestScope_Str_Description))]
        [LocalizedCategory(nameof(Resources.Input_Category))]
        public InArgument<string> Str { get; set; }

        // A tag used to identify the scope in the activity context
        internal static string ParentContainerPropertyTag => "ScopeActivity";

        // Object Container: Add strongly-typed objects here and they will be available in the scope's child activities.
        private readonly IObjectContainer _objectContainer;

        #endregion


        #region Constructors

        public TestScope(IObjectContainer objectContainer)
        {
            _objectContainer = objectContainer;

            Body = new ActivityAction<IObjectContainer>
            {
                Argument = new DelegateInArgument<IObjectContainer> (ParentContainerPropertyTag),
                Handler = new Sequence { DisplayName = Resources.Do }
            };
        }

        public TestScope() : this(new ObjectContainer())
        {

        }

        #endregion


        #region Protected Methods

        protected override void CacheMetadata(NativeActivityMetadata metadata)
        {
            if (Str == null) metadata.AddValidationError(string.Format(Resources.ValidationValue_Error, nameof(Str)));

            base.CacheMetadata(metadata);
        }

        protected override async Task<Action<NativeActivityContext>> ExecuteAsync(NativeActivityContext  context, CancellationToken cancellationToken)
        {
            // Inputs
            var str = Str.Get(context);

            return (ctx) => {
                // Schedule child activities
                if (Body != null)
				    ctx.ScheduleAction<IObjectContainer>(Body, _objectContainer, OnCompleted, OnFaulted);

                // Outputs
            };
        }

        #endregion


        #region Events

        private void OnFaulted(NativeActivityFaultContext faultContext, Exception propagatedException, ActivityInstance propagatedFrom)
        {
            faultContext.CancelChildren();
            Cleanup();
        }

        private void OnCompleted(NativeActivityContext context, ActivityInstance completedInstance)
        {
            Cleanup();
        }

        #endregion


        #region Helpers
        
        private void Cleanup()
        {
            var disposableObjects = _objectContainer.Where(o => o is IDisposable);
            foreach (var obj in disposableObjects)
            {
                if (obj is IDisposable dispObject)
                    dispObject.Dispose();
            }
            _objectContainer.Clear();
        }

        #endregion
    }
}
```

#Childアクティビティへのデータの受け渡し

　ScopeアクティビティからChildアクティビティへのデータの受け渡しは、ActivityCreatorで用意しているobjectContainerを利用します。
　今回はScopeアクティビティに`InArgument<string>`型のプロパティStrがありますので、これをChildアクティビティでも使えるようにします。具体的には以下のように _objectContainerにAddメソッドでデータを追加します。

```TestScope.cs
(略)
            // Inputs
            var str = Str.Get(context);
            _objectContainer.Add(str);
            return (ctx) => {
                // Schedule child activities
                if (Body != null)
				    ctx.ScheduleAction<IObjectContainer>(Body, _objectContainer, OnCompleted, OnFaulted);

                // Outputs
            };
(略)
```

TestChild側ではobjectContainerインスタンスに対して`Get<T>`で値を取得します。
以下の例ではTestScopeアクティビティのプロパティとして取得した値の前に"000"を付加してOutStringプロパティとして返しています。

```TestChild.cs
(略)
       protected override async Task<Action<AsyncCodeActivityContext>> ExecuteAsync(AsyncCodeActivityContext context, CancellationToken cancellationToken)
        {
            // Object Container: Use objectContainer.Get<T>() to retrieve objects from the scope
            var objectContainer = context.GetFromContext<IObjectContainer>(TestScope.ParentContainerPropertyTag);

            // Inputs
            var str = objectContainer.Get<string>();

            ///////////////////////////
            // Add execution logic HERE
            ///////////////////////////
            str = "000" + str;

            // Outputs
            return (ctx) => {
                OutString.Set(ctx, str);
            };
        }
(略)
```
　動作確認のためUiPath Studioで以下のようなワークフローを作成してみます。
![uac5-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477147/da1f5058-dcee-cd49-93bf-24e464e83431.png)

　TestChildには入力プロパティがありませんが、TestScopeで入力した値を内部で参照しています。そのためTestScopeの入力で仮に"123"とした場合、strOutputには"000123"が返ってきます。

#Wizard作成後に別のアクティビティをChildアクティビティにする方法
　Scopeアクティビティを作成した後にChildアクティビティを追加したい場合があると思います。
　この場合はWizardでTypeをSimpleにしたアクティビティを作成した後、以下の設定を加えることによりChildアクティビティにすることができます。
1. using UiPath.Shared.Activities.Utilities; の追加
2. コンストラクタにScope内でのみの利用制限の追加
3. 実行部分にobjectContainerの定義の追加

後で追加したTestChild2アクティビティに対する具体的な変更個所は以下になります。

```TestChild2.cs
(略)

using UiPath.Shared.Activities.Utilities;

（略）
        #region Constructors

        public TestChild2()
        {
            Constraints.Add(ActivityConstraints.HasParentType<TestChild2, TestScope>(string.Format(Resources.ValidationScope_Error, Resources.TestScope_DisplayName)));
        }

        #endregion

（略）
            // Inputs
            var objectContainer = context.GetFromContext<IObjectContainer>(TestScope.ParentContainerPropertyTag);

（略）
```

次回はnupkg周りの設定について説明します。

（その５　おわり）

[その１](https://qiita.com/SuetakeYoichi/items/2bbd7affbbcf32e002c1)　[その２](https://qiita.com/SuetakeYoichi/items/e4383318bfc09de0db51)　[その３](https://qiita.com/SuetakeYoichi/items/29278e6b9c5621fd2358)　[その４](https://qiita.com/SuetakeYoichi/items/117126227867d580d590) その５　[その６](https://qiita.com/SuetakeYoichi/items/1c3f8ce5feec4c1bb36f)