[OutSystems]ODCでC#を取り込んでみる（External Logic）

C#で作成したメソッドを、ODCのServer Actionとして利用する機能。
OutSystems11でいうと、Extensionに相当する。

# 修正履歴
2023/10/01 InterfaceとClassのメソッド引数にデフォルト値を渡すことで、Service ActionのInput ParameterのIs MandatoryとDefault Valueに反映されることがわかった。それに伴う修正を行った
2023/11/27 テストAppの利用の項目を追記

# 環境情報
ODC Studio(Version 1.2.3)
OutSystemsExternalLibraries.SDK.1.4.0
.NET SDK 6.0
Microsoft Visual Studio Community 2022 (Version 17.7.4)

# サンプル
後ほど追加

# 1. C#でLibraryを書く
## Visual Studioでプロジェクトを作る
Class Libraryを作るということなので、プロジェクト　テンプレートはC#の「クラス　ライブラリ」を選択。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/aa5c06fd-d147-937c-cb2b-902d83f01283.png)

フレームワークは、前提条件に従って.NET 6を選択。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/1ff853d3-c788-cb84-4102-bbea819994a6.png)

NuGetよりOutSystems.ExternalLibraries.SDKを参照する。
まず、プロジェクトを右クリック（プロジェクトは下のスクリーンショットで選択中の位置が該当）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/3f8e31ae-8c37-c7dc-1bdf-356c87355420.png)

「NuGet パッケージの管理(N)」メニューを選択。出てきたダイアログで、参照タブを開いて、「OutSystems.ExternalLibraries.SDK」を検索し、インストール。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/2104c417-7e5f-6073-16c1-af3f4a781364.png)

## プログラムを書く
### Interfaceを書く
Interfaceには、ODCに提供するServer ActionのI/Fを定義する。
C#の慣習に従い、Interface名は「I\<Class名\>」とする（ここではコンポーネント名をJapaneseCalendarとする予定なので、IJapaneseCalendarとした）。またそのファイル名もInterface名に合致させる（同じく、IJapaneseCalendar.cs）。

定義したIntefaceには[OSInterface]属性を付与する。
OSInterfaceのDescriptionプロパティがLibraryのDescriptionに設定される。

``` cs:IJapaneseCalendar.cs
using OutSystems.ExternalLibraries.SDK;

namespace JapaneseCalendarODC
{
    [OSInterface(Description = "This External Logic provides actions to handle Japanese Calendar (Japanese Era) ")]
    public interface IJapaneseCalendar
    {
        [OSAction(Description = "Get formatted text of the inputted text as Japanese Calendar.", ReturnName = "FormattecText")]
        public string FormatDateAsJapaneseCalendar(DateTime OriginalDate, bool TreatFirstYearSpecially = false, bool MakeNumbersFullWidth = false);
        [OSAction(Description = "Get era and year (in era) of specified Date.", ReturnName = "JapaneseCalendarYear")]
        public JapaneseCalendarYear DateToJapaneseCalendarYear(DateTime OriginalDate, bool TreatFirstYearSpecially = false, bool MakeNumbersFullWidth = false);
    }
}
```

このInterfaceはpublicにしておかないと、「The interface decorated with OSInterface 'JapaneseCalendarODC.IJapaneseCalendar' is not public. (OS-ELG-MODL-05004)」というエラーが出た。

### Interfaceに定義するメソッドについて
Interface内のメソッドに[OSAction]属性を付与し、そのDescriptionプロパティを設定することで、Server ActionのDescriptionに反映させている。
また、放っておくと、Output Parameterの名前がメソッド名と一致してしまう。
そこで、[OSAction]のReturnNameプロパティを設定することで、Output Parameterの名前を変更できる。

I/Fとなるメソッドが使えるデータ型には以下の制約がある
- 基本データ型： string, int, long, bool, byte[], decimal, float, double, DateTime
- OSStructure属性が付与されたStruct
- 上記2種類のList (IEnumerableを実装したもの）

ActionのOutput Parameterについて。OutSystems 11のExtensionでは、C#メソッドのout付き引数だったが、ODCでは普通に戻り値を返す
その戻り値に複数の値を持たせたい場合はStructを定義して返す

### Structを書く
publicにする。
Server ActionのI/Fに出すStructureは、C#上でStructとして作成し、[OSStructure]属性を付与する。フィールドには[OSStructureField]属性を付与。同じくDescriptionプロパティは設定する。

2番目のDateToJapaneseCalendarYearメソッドは以下のStructを返す。

``` cs: Struct.cs
using OutSystems.ExternalLibraries.SDK;

namespace JapaneseCalendarODC
{
    [OSStructure(Description = "A structure that has Japanese era and year expressed in AD")]
    public struct JapaneseCalendarYear
    {
        [OSStructureField(Description = "1: Meiji, 2: Taisho, 3: Showa, 4: Heisei, 5: Reiwa")]
        public int Era;
        [OSStructureField(Description = "Era expressed in Japanese")]
        public string EraText;
        [OSStructureField(Description = "Year expressed in AD")]
        public int YearInEra;
        [OSStructureField(Description = "Year expressed in AD in text format")]
        public string YearInEraText;
    }
}

```

### Classを書く
publicにする
ここの記述方法は通常と変わらない（このコードでは別のクラスを使って記述しているので注意）。

``` cs:JapaneseCalendar.cs
using JapaneseCalendarODC.Library;

namespace JapaneseCalendarODC
{
    public class JapaneseCalendar : IJapaneseCalendar
    {
        public JapaneseCalendarYear DateToJapaneseCalendarYear(DateTime OriginalDate, bool TreatFirstYearSpecially = false, bool MakeNumbersFullWidth = false)
        {
            var result = new JapaneseCalendarYear();
            var calendar = new JapaneseCalendarLibrary(OriginalDate,
                                             TreatFirstYearSpecially, MakeNumbersFullWidth);
            result.Era = calendar.Era;
            result.EraText = calendar.EraText;
            result.YearInEra = calendar.YearInEra;
            result.YearInEraText = calendar.YearInEraText;

            return result;
        }

        public string FormatDateAsJapaneseCalendar(DateTime OriginalDate, bool TreatFirstYearSpecially = false, bool MakeNumbersFullWidth = false)
        {
            var calendar = new JapaneseCalendarLibrary(OriginalDate,
                                                        TreatFirstYearSpecially, MakeNumbersFullWidth);
            return calendar.DateText;
        }
    }
}

```

# 2. ODC PortalにPublish
## publish
Visual Studio上でソリューション（通常は画面右にあるソリューションエクスプローラー内で最上位に表示される）を右クリックし、「ターミナルで開く」を選択。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/fa9ccbc3-7c51-6c72-b0f0-4831948cf337.png)

以下は、チュートリアルにあったコマンド例「dotnet publish -c Release -r linux-x64 --self-contained false」を実行してみたときの、出力（プログラムに起因する警告は省略してある）。
「bin\Release\net6.0\linux-x64\publish\」フォルダに結果が出力されていることがわかる。
```
PS C:\Users\test\source\repos\JapaneseCalendarODC> dotnet publish -c Release -r linux-x64 --self-contained false
MSBuild のバージョン 17.7.3+8ec440e68 (.NET)
  復元対象のプロジェクトを決定しています...
  C:\Users\test\source\repos\JapaneseCalendarODC\JapaneseCalendarODC\JapaneseCalendarODC.csproj を復元しました (327 ms)。
  JapaneseCalendarODC -> C:\Users\test\source\repos\JapaneseCalendarODC\JapaneseCalendarODC\bin\Release\net6.0\linux-x64\JapaneseC
  alendarODC.dll
  JapaneseCalendarODC -> C:\Users\test\source\repos\JapaneseCalendarODC\JapaneseCalendarODC\bin\Release\net6.0\linux-x64\publish\
```

.NET SDKのpublishコマンドのオプションで利用しているものは以下の通り
- -c: 構成をDebug/Releaseのどちらで公開するか。Releaseを指定するのでRelease構成
- -r: ランタイムの指定。[ランタイム識別子](https://learn.microsoft.com/ja-jp/dotnet/core/rid-catalog)というもので、アプリケーションが実行される対象プラットフォームを指定する。linux-x64という指定は、linuxのデスクトップディストリビューション向けであるらしい
- --self-configured: .NETランタイムをアプリケーションと一緒に含めるか。falseを指定しているので、含めないということか

## ODCにアップロード
新しいExternal Logicを作成するときの手順。登録済みのExternal Logicを更新するときは、登録済みのExternal Logicのページを開き、「Upload new revision」ボタンから。

1. 「bin\Release\net6.0\linux-x64\publish\」フォルダを開き、その中身のファイルを選択し、zipファイルに圧縮する（作成したzipファイルのサイズは90MB未満の必要がある）
1. ODC PortalでサイドバーからExternal Logicを選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/019bf8e4-ded3-9e0e-90fe-9ef515aa9591.png)
1. 右上の「Create library」ボタンをクリック
1. 「Browse」ボタンか、そのエリアへのドラッグ＆ドロップで作成しておいたzipファイルをアップロードし、右上の「Continue」ボタンをクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/180fa347-a758-50b2-c767-5e4301134477.png)
1. 自動的に一覧画面に戻るが、アップロード↓ファイルにエラーが無ければ、テーブルの上にアップロードしたzipファイル名が表示される。その右にある「Review file contents」ボタンをクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/acd139f9-0528-64b5-2a93-6c8b6caa3025.png)
1. 結果を確認して「Publish library」ボタンをクリック。[OSInterface]を付与したInterfaceのメソッドがAction、[OSStructure」を付与したStructがStructureにマッピングされているはず
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/e3bb9b51-bb49-7da3-4038-f5ef8a75ac06.png)
1. 結果確認。右下にPublishの進行状況が表示される。最後まで行くとPublication finishedになる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/8980077d-8907-784c-c8a4-1b0c6513c89b.png)

# 3. Publishした機能を利用する
## Library
[OSInterface]を付与したInterfaceの名前がLibrary名にマッピングされている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/ec1f831a-d363-dc01-2d0c-dd8fea3cc097.png)

## Action
[OSInterface]を付与したInterfaceに含まれるメソッドがActionにマッピングされる。
また、必須ではないがメソッドに[OSAction]属性を付与し、そのDescriptionプロパティを設定するとActionのDescriptionプロパティに、ReturnNameプロパティを設定するとActionのOutput Parameterの名前に設定される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/fcc210b1-b188-8f95-67a2-89bc4e76dd64.png)

C#のメソッド定義においてデフォルト引数を渡しておくと、以下の通り、Is Mandatory=No、Default Value=C#のパラメータに設定したデフォルト引数、となる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/9e607b74-1ef5-ce61-edff-a8f7e594d08e.png)

対応するコード
``` cs
public string FormatDateAsJapaneseCalendar(DateTime OriginalDate, bool TreatFirstYearSpecially = false, bool MakeNumbersFullWidth = false);
```

## Structure
[OSStructure]を付与したStructが、Structureにマッピングされる。
同じく[OSStructure]のDescriptionプロパティを設定すると、ODCのStructureのDescriptionプロパティが設定される。
StructureのAttributeにもDescriptionを設定したい場合は、C#上でStructのメンバーフィールドに[OSStructureField]を付与し、そのDescriptionプロパティを設定する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/9ee214f3-dedc-d285-e496-f025460cd7d8.png)

# 注意すべき事項
[External Libraries SDK README - App architecture using external logic](https://success.outsystems.com/documentation/outsystems_developer_cloud/building_apps/extend_your_apps_with_external_logic_using_custom_code/external_libraries_sdk_readme/#app-architecture-using-external-logic)によると、External Logicで作成したServer Action実行は、内部的にはHTTPS通信で実現される。
OutSystems11のときはインメモリだったので、その差に注意する必要がある。

# テストAppの利用
ODCのLibraryはRelese Libraryをするまでの間、他のAppやLibraryから使えない。
しかし、Appを1つテスト用のAppとして指定すると、そのAppだけはRelease前の機能も使える。

External Logicも扱いとしてはLibraryなのでこの機能を使える。
しかしその操作の起点が異なることに注意。

- Libraryの場合：LibraryをPublishしたときにテストAppを指定できる
- External Logicの場合：ODC Portal左のExternal logic > 目標のExternal logic > Version historyタブ > 右の「…」 > Try library in an app　と選ぶとODC Studioが開き、テストAppを指定できる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332987/f352776e-003d-34e4-38a0-2dd4f8be566d.png)