20200225のC#に関する記事は9件です。

[C#/xaml] BitmapResourceやその派生クラスから、System.Drawing.Bitmapに変換する(BmpBitmapEncoderクラス)

もくじ
https://qiita.com/tera1707/items/4fda73d86eded283ec4f

Bitmap関連
BitmapSource派生クラスの使い方

やりたいこと

WPFアプリでよく使うBitmapResourceやその派生のBitmapImageRenderTargetBitmapTransformedBitmapSystem.Drawing.Bitmapに変換したい。
(新しく作る処理ではBitmapResourceを使いたいが、もともとあるロジックでSystem.Drawing.Bitmapを使ってるときに、そこに合わせに行きたい)

やり方

BmpBitmapEncoderを使う。

サンプル

private void Button_Click_1(object sender, RoutedEventArgs e)
{
    // BitmapResourceの派生クラス「RenderTargetBitmap」で、画像を取ってくる
    // 「RouletteWhole」は、Gridの名前。
    var canvas = new RenderTargetBitmap((int)RouletteWhole.ActualWidth, (int)RouletteWhole.ActualHeight, 96, 96, PixelFormats.Pbgra32);
    canvas.Render(RouletteWhole);

    // BmpBitmapEncoderに画像を入れる
    using (var stream = new MemoryStream())
    {
        BitmapEncoder encoder = new BmpBitmapEncoder();
        encoder.Frames.Add(BitmapFrame.Create(canvas));
        encoder.Save(stream);

        // BmpBitmapEncoderからSystem.Drawing.Bitmapをつくる
        var bitmap = new System.Drawing.Bitmap(stream);
        bitmap.Save(@".\aaa.bmp");
    }
}

参考

Convert RenderTargetBitmap to Bitmap
https://stackoverflow.com/questions/20083210/convert-rendertargetbitmap-to-bitmap

親クラスの「BitmapEncoder」クラス
https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.media.imaging.bitmapencoder?view=netframework-4.8

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AWS/S3】C#でS3バケット内に特定のフォルダが存在するか確認する

はじめに

開発環境

 - Windows10
 - VisualStudio2017
 - AWSSDK.S3のバージョンは3.3.104.39

開発言語

 - C#

やりたかったこと

S3にファイルをアップロードする際に、既にディレクトリがアップロードされているものは警告を表示するようにしたかった。

やり方

ソースコードは以下の通り。
既に存在するとtrue,存在しないとfalseが返ります。

 bool isDirExist(string Buketname ,string Path)
{
    basiccredentials = new BasicAWSCredentials(アクセスキーID,シークレットアクセスキー);

    using (var S3Client = new AmazonS3Client(basiccredentials, this.wwRegion))
    {
        ListObjectsRequest request = new ListObjectsRequest();
        request.BucketName = Buketname ;
        request.Prefix = Path;

        var response =S3Client.ListObjects(request);

        if (response.S3Objects.Count!=0) return true;
        else return false; // 存在なし

    }
}

Buketname :S3バケット名
Path:検索したいディレクトリのパス(ex. main/20200225/log1/ など)

ListObjectsのPrefix に確認したいディレクトリパスを指定しListObjectsを呼び出すと
指定したディレクトリパス配下に格納されているオブジェクトのリストを取得できます。

ディレクトリの中にファイルが存在しないのにディレクトリが作成されている・・・
という事は無いはずなので、このS3Objects.Countが0以外であればディレクトリパスが存在している ということです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#でデリゲートを使って遊んだ

C#でデリゲートやラムダ式など使い、いろいろ遊んでみました

program.cs
using System;

namespace CsLamTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Func<int> addDelegate=(        //(1)
                ()=> {                     //(1-1)
                    Console.WriteLine("初期化の関数から出力");     //(1-2)
                    return 0; 
                    }
                );

            f(                             //(2)
                () => {                    //(2-1)
                    Console.WriteLine("ラムダ式から出力");      //(2-2)
                    return 5 * 5;
                    },
                ref addDelegate           //(2-3)
            );
            f(                             //(3)
                delegate () {              //(3-1)
                    Console.WriteLine("デリゲートから出力");     //(3-2)
                    return 3 * 3;
                    },
                ref addDelegate          //(3-3)
            );
        }

        static void f(Func<int> func, ref Func<int> addDelegate)  //(4)
        {
            addDelegate += func;            //(4-1)
            Console.WriteLine(addDelegate());     //(4-2)
        }
    }
}

出力結果は

初期化の関数から出力
ラムダ式から出力
25
初期化の関数から出力
ラムダ式から出力
デリゲートから出力
9

解説

まずは(1)の部分

program.cs
            Func<int> addDelegate=(        //(1)
                ()=> {                     //(1-1)
                    Console.WriteLine("初期化の関数から出力");     //(1-2)
                    return 0; 
                    }
                );

(1)の行は、Func<int>(引数を取らずint型を返す関数)型のaddDelegateを定義しています。
addDelegateの初期化が(1-1)でラムダ式を用いて行われています。


次に(2)の部分

program.cs
            f(                             //(2)
                () => {                    //(2-1)
                    Console.WriteLine("ラムダ式から出力");      //(2-2)
                    return 5 * 5;
                    },
                ref addDelegate           //(2-3)
            );

(2)(4)の関数fを呼び出しています。
その第1引数Func<int> func(2-1)で示されたラムダ式を渡しています。
第2引数ref Func<int> addDelegate(1)addDelegateを渡しています。・・・(2-3)の行。

(3)はラムダ式ではなく無名関数を渡しています。他は(2)と同じです。


次に(4)の部分

program.cs
        static void f(Func<int> func, ref Func<int> addDelegate)  //(4)
        {
            addDelegate += func;            //(4-1)
            Console.WriteLine(addDelegate());     //(4-2)
        }

(4-1)で、第1引数で渡されたfuncを、第2引数で渡されたaddDelegateに加えています。
デリゲートは、+=演算子を用いて、関数を連結できます。
連結された関数は、古いものから順番に実行されます。

実行の流れ

  1. まず(2)で関数fを呼び出し
  2. (4-1)addDelegate(2-1)のラムダ式を加える
  3. (4-2)addDelegate()を実行

    1. (1-2)が出力される
    2. (2-2)が出力される
    3. (2-1)のラムダ式の戻り値が出力される。
  4. 次に(3)で関数fを呼び出し

  5. (4-1)addDelegate(3-1)のデリゲートを加える

  6. 3.と同じ

    1. (1-2) (2-2) (3-2) が順番に出力される
    2. (3-1)のデリゲートの戻り値が出力される

今回のC#いじりで知ったこと

Func<int>には、ラムダ式も無名関数も渡せる、代入できる。
Console.WriteLine()では、デリゲートに複数の関数が+=で連結されていても、最後に実行される関数の戻り値のみ表示する。
デリゲートは値型だった。なので、refをつけています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UnityとARkitでVtuberアプリを作ってみた

はじめに

最近の流行りのVtuberになれるアプリ作ってみました。

このアプリは2つのアプリから成り立っていて
iPhone側でフェイスキャプチャー用が
PC側(Windows10)でLive2Dの制御用です。


動画左がiPhoneのアプリで、右側がPCアプリです。
※この動画では下のサンプルコードで取得してる部位以外も使用しています。

開発環境

Unity2019.2.15f1
Windows10
iPhoneXR
ios13.3
Mac 10.15.1
Xcode 11.3
ARFoundatinは、Unity公式のGitHubからARFoundationの最新プロジェクトをクローンしてきます。
ARFoundatinリンク
Live2DモデルはCubism3.0桃瀬ひよりを使用しました。

※ARFoundationに関しては自分も勉強途中なので間違っているところもあるかもしれません

iPhone側のアプリについて

今回はARKitFaceBlendShapesのシーンをメインで使っていきます。

iPhone側のアプリで参考にするのはARKitBlendShapeVisualizer.csになります。
今回はこれをもとに改造していきます。

ARKitBlendShapeVisualizerの簡単な説明

ARFaceコンポーネントを取得していて、これを軸に作成されています。
ARFaceは顔情報の更新が検知できるので、ここのupdatedに更新ごとの処理を登録していく形になります。

データの取得

c#ARKitBlendShapeVisualizer.cs
 void UpdateFaceFeatures()
    {
        if (skinnedMeshRenderer == null || !skinnedMeshRenderer.enabled || skinnedMeshRenderer.sharedMesh == null)
        {
            return;
        }  

        using (var blendShapes = m_ARKitFaceSubsystem.GetBlendShapeCoefficients(m_Face.trackableId, Allocator.Temp))
        {
            foreach (var featureCoefficient in blendShapes)
            {
                int mappedBlendShapeIndex;
                if (m_FaceArkitBlendShapeIndexMap.TryGetValue(featureCoefficient.blendShapeLocation, out mappedBlendShapeIndex))
                {
                    if (mappedBlendShapeIndex >= 0)
                    {
                        skinnedMeshRenderer.SetBlendShapeWeight(mappedBlendShapeIndex, featureCoefficient.coefficient * coefficientScale);
                    }
                }
                switch (featureCoefficient.blendShapeLocation)
                {
                    case ARKitBlendShapeLocation.EyeBlinkLeft:
                        motionDate.Leye = featureCoefficient.coefficient;
                        break;
                    case ARKitBlendShapeLocation.EyeBlinkRight:
                        motionDate.Reye = featureCoefficient.coefficient;
                        break;
                    case ARKitBlendShapeLocation.JawOpen:
                        motionDate.Mouth = featureCoefficient.coefficient;
                        break;
                }
            }   
        }
    }

基本的に参考にしているscript内のUpdateFaceFeaturesメソッドで実装されています。

NativeArrayの中に構造体で格納されているので必要な部位のデータだけ
取得します。

foreachを使って構造体ごとに取り出していきます。
部位の選択はstruct内の部位情報と自分の欲しい部位が一致しているかで行っています。
ARKitBlendShapeLocation(enum)で部位指定できます。

switch文部分が追加したコードです

データ格納用クラスを作成し
格納クラス→JSON→byteと変換しPCに送信していきます
送信タイミングは顔データが更新されると送信としました。

送信方法はUDPで実装しています。
PC側が受信、iPhone側で送信で実装しています

よく使われるUDPの通信です。
すでに多くの資料が公開されているので通信部分は省略します。
(UDP C# 実装で検索)

ここからはPC側のアプリについてです

データが受信できたら復元しLive2Dに適用していきます
データを復元したタイミングでnullチェックしておくのがオススメです

モデルへのデータ適用はLive2D公式のドキュメントのパラメーター更新と同じ方法です(ドキュメントリンク)

データ適用時の注意点だけ記述します

  1. 取得したデータは個人差があるので1.0〜0.0でキレイに取得できることは無いです なので各部位ごとに平均最大値と平均最小値を算出し 正規化した数値を適用したほうがきれいに見えると思います
  2. 目の開きのデータはLive2Dと最大最小が逆で取得されるので数値の変換が必要になります
  3. 部位によって最大最小値が異なるのでその対応も必要になります
  4. 純粋なARkitの数値が入っているという感じでない部位がある。

作っている中で躓いたポイントです

Wi-Fiでの通信ができない

PC側でWi-Fiの設定をプライベートに変更
あとはポートの設定も変えとく(windows10 ポート開放で検索)

iPhone側でWi-Fi設定からプロキシ設定を手動
サーバに使用するIPアドレスを
ポートに使用するポート番号を入力して保存

上の設定しても通信ができない

多分ルーターとかのセキュリティ問題 (学校の学科専用Wi-Fiでやったらできなかった)

急に接続が切れた

どうやらiPhoneで残り電池残量等のポップアップが出ると接続が切れるっぽいので再接続

その他

NativeArrayの部分を改造するときは開放処理を忘れずに

NativeArrayは指定したAllocatorのタイプごとに開放処理が必要です
今回は、Tempを指定したので1フレーム以内です!
詳しくは Unity NativeArrayで検索!
(UnityDOTSなどでも使われるので、これからは必須の知識になりそう)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ローカル環境で、Go、C#、Java、Pythonそれぞれで簡易サーバを実装して、クラウドサービスのWebhookの通知を受ける

1. やること

ローカル環境のWindows, Mac, Linuxなどの上で、
Go、C#、Java、Pythonのいずれかの言語で簡易サーバを作ります。
作ったローカル環境の簡易サーバで、
クラウドベースの各種チャットサービスやSNSなどのリアルタイムの通知をWebhookで受けます。

2. 必要なもの

インターネットにつながるWindows、Mac、Linuxなどが必要です。
中から外に繋がればいいので、Webhook用のpublicなURL(外からアクセスできるhttpsサーバなど)は必要ありません
インターネット上のWebサイトが見れるような環境であればOKです
ngrok(後述)とプログラミング言語を利用するので、それらをサポートしている環境である必要はあります。

3. ngrokの準備と起動

かなり、ざっくりな説明をすると、
ngrokは、インターネットに抜けられるローカルの環境(インターネット上のWebサイトが見れるような環境)で、
public URLへのリクエストを受けられるトンネリングサービスです。
TCPのトンネリングもできますが、今回はhttpのトンネリングに関してのみ触れます。
あえて日本語で読むと、「エングロック」になります。

こちらが公式サイトです:
https://ngrok.com/

無償版、有償版があります。
無償版では、1分間当たり40コネクション(40リクエストではありません)までですが、
ちょっとしたテストをする分には、充分だと思います。
有償版ではプランによって、できることが増えていきます。

3.1. ngrokのダウンロード

公式サイトの[DOWNLOAD]ページからダウンロードできます。

https://ngrok.com/download

ダウンロードしたら、適当なディレクトリに解凍しましょう。

ngrokのアカウントを作らずに動作させた場合は、アプリ起動ごとに8時間のみ動作します。
起動しなおせば、再度8時間利用できますが、無償アカウントを作っておいた方が制限も緩和されるのでよいと思います。
公式サイトの[SING UP]からアカウントの作成ができます。

3.2. ngrokのアカウントに接続する

ngrokのアカウントを作った場合は、今後、ngrokを起動する場合にアカウントと関連付くようにします。
<YOUR_AUTH_TOKEN>は、SING UP後やLOGIN後に取得できます。
LOGIN後のページトップに『Setup & Installation』が表示されて、
『(3) Connect your account』にまさに実行すべきコマンドがそのまま書かれています。

Windows
> .\ngrok authtoken <YOUR_AUTH_TOKEN>
Mac/Linux
$ ./ngrok authtoken <YOUR_AUTH_TOKEN>

このコマンドを実行すると、各OSのユーザのホームディレクトリ配下の以下の場所に(Ver 2.xの場合)、
{userhome}/.ngrok2/ngrok.yml
というファイルが出来上がって、そこに上記のアカウントのトークン情報などが保存されます。
ngrok.ymlには、その他さまざま設定を書くこともできますが、説明は省略します。
--configオプションで、設定ファイルのパスも指定できるので、例えば、ngrokをdockerコンテナで実行する場合に、
ホスト側のディレクトリに設定ファイル置いて、マウントさせて設定参照などもできます。

3.3. ngrokを起動する

Webhook用のpublicなURLは、ngrok側が準備してくれます。
Webhook用にTLSを利用しない、httpを使うのは、通常はおろかな行為なので、httpsだけ準備されるようにオプションを指定(--bind-tls=true)します。
以下コマンドの、8080は、ローカル環境のサーバのポート番号です。使っていない適切なポート番号を指定します。
この時点では、まだローカル側のサーバは作っていませんが、空いている適切なポートを指定しましょう。

Windows
> .\ngrok http 8080 --bind-tls=true
Mac/Linux
$ ./ngrok http 8080 --bind-tls=true

うまく行くと、以下のような出力が得られるはずです。
{random-id}の部分は、実行するたびに変わります(有償版では、この部分を指定することもできます)。
Regionも指定できますが、今回はデフォルトでよいと思います。

出力例
ngrok by @inconshreveable                                                     (Ctrl+C to quit)

Session Status                online
Account                       Your Name (Plan: Free)
Version                       2.x.yz
Region                        United States (us)
Web Interface                 http://127.0.0.1:4040
Forwarding                    https://{random-id}.ngrok.io -> http://localhost:8080

Connections                   ttl     opn     rt1     rt5     p50     p90
                              0       0       0.00    0.00    0.00    0.00

3.4. ここまでの手順でngrokがやってくれること

Forwarding https://{random-id}.ngrok.io -> http://localhost:8080という出力が示すように、
ngrokサービスが、publicなURLであるhttps://{random-id}.ngrok.ioを準備してくれています。
このURLへの要求は、ローカル環境(ngrokコマンドを実行したマシン)のhttp://localhost:8080にフォワードされます。

つまり、ローカルマシン上では、http://localhost:8080(http://127.0.0.1:8080)へのリクエストを受けられるようにアプリを実装すればよいです。
有償版ではプランによって、ローカルのhttpsサーバ(ローカル側がTLSあり)にフォワードすることもできますが、今回は、ローカル側はTLSなしのhttpサーバとします。

ざっくり図解すると、今回のオプション指定では、以下のような接続になっています。
矢印の方向は接続開始時の要求の方向を示しています。リクエストとレスポンスを処理するので、データのやりとりの観点では双方向です。

Ngrok.png

ローカルにあるngrokのアプリが、クラウドにあるngrokサービスとセキュアに常時接続しています。
ngrokのpublicなURLにリクエストがあると、この常時接続を通じて、ローカル側のngrokのアプリにリクエストが通知されて、
ローカル側のngrokのアプリが、ローカル側のサーバにリクエストを出します。
ngrokが準備したPublicなURLがhttpsの場合は、ここのTLSのサーバ証明書はngrok側のものになります。
この証明書チェーンは、標準的な環境では信頼済みのものであるので、これが非常に便利な場合もあります。

ngrokサービス提供元を信頼するという前提の下では、通信経路は暗号化されてTLSのレベルでデータが守られるということになると思います。

3.5. Web Interfaceも便利

Web Interface http://127.0.0.1:4040と出力されていると思いますが、テストではこれは便利に使えます。
(オプションでWeb Interfaceを無効にすることもできます。)

http://127.0.0.1:4040
にアクセスすることで、ngrokを経由した通信内容を閲覧したりできます。
例えば、httpのリクエストとレスポンスのヘッダやBodyの中身を調べたりすることができます。

また、[Replay]機能が非常に便利で、ngrok経由で過去に行われたhttpなどのリクエストを、
再度ローカルのサーバに好きなタイミングで送信することができます。
リクエストを編集して送信することもできます。
テストでは結構便利で、Webhookの実際の通知を再度発生させることなく、以前の通知内容をちょっと変えて試すということもできます。
(本格的なテストを行うときは、ngrokで本機能を使うのではなく、より高度なテストの自動化をする場合が多いと思います。)

Ngrok_Web_Interface.png

3.6. APIもあります

Web Interfaceを有効にしているとAPIも使えます。

APIの詳細はこちらのページ(https://ngrok.com/docs#client-api)に記載されています。

ngrokサービスが割り当ててくれるpublicなURLは、無償版ではランダムになりますが、
APIで何を割り当ててもらったかを取得することもできます。

Web Interfaceの
http://127.0.0.1:4040/api/tunnelsに、HTTP GETのリクエスト投げると、レスポンスで各種情報が得られます。
リクエスト時には、AcceptContent-Typeを、application/jsonにしておくと、JSON形式でレスポンス返ってきます。

例えば、以下のような感じです。
metricsも興味深いですが、今回は中身省略して記載しています。

/api/tunnels
{
    "tunnels": [
        {
            "name": "command_line",
            "uri": "/api/tunnels/command_line",
            "public_url": "https://{random-id}.ngrok.io",
            "proto": "https",
            "config": {
                "addr": "http://localhost:8080",
                "inspect": true
            },
            "metrics": {
            }
        }
    ],
    "uri": "/api/tunnels"
}

今回はngrok起動コマンドで、明示的にhttpsのみ指定したので、"tunnels"配列には、1つのオブジェクトのみ含まれます。
そのオブジェクトの"public_url"から、ngrok側が割り当てたpublicなURLが取得できます。
"config"オブジェクトの"addr"からは、フォワード先のローカル側のURLが取得できます。

また、先ほどWeb Interfaceで説明したリクエストやレスポンスのヘッダやBodyの中身の時系列での情報取得や、
Replayを行うAPIもあります。

4. ここまでで、もうWebhookの通知は受けられます

ngrokサービスが準備してくれた、public URLをWebhookの通知先として、各種チャットサービスやSNSなどのAPIサービス側に登録すれば、実際に動作するはずです。
この時点ではローカル側にまだサーバーがないので、正常なレスポンスを返すことができませんが、
Web Interface ( http://127.0.0.1:4040 )から、Webhookで通知されたリクエストのヘッダやBodyの中身は確認できます。

Forwarding https://{random-id}.ngrok.io -> http://localhost:8080のように出力されている場合は、
https://{random-id}.ngrok.io/webhook
などを、Webhookの通知先のURLとして登録します。
{random-id}の部分は、ngrokコマンドを実行した環境によって、また無償版では(有償版で指定していない場合も)、起動ごとに変わります。
後ろにくっつけたパス部分の、/webhookは好きなパスに指定できます。
/とかでも良いですが、この後のコードでは、https://{random-id}.ngrok.io/webhookのように、パスは/webhookを指定したものとして記載します。

Webhookの通知先の登録方法は利用するクラウドサービスなどによって異なります。
開発者用のサイトから登録できるものや、APIで登録する場合などあります。
試してみたいサービスごとに登録方法は確認する必要があります。

サービスによっては、登録作業を行った瞬間に、Webhookの通知先にリクエストを出して、
適切なレスポンスを返さないと、登録に失敗する仕組みのものもあります。
こちらの場合でも、登録時のリクエスト内容は、上記のWeb Interfaceで確認できます。

また、ここで、Webブラウザなどで、同じマシンからでも、別のマシンからでも、
https://{random-id}.ngrok.io/webhookにアクセスすると、
ngrokコマンドを実行しているコンソールや、Web Interfaceにも出力があるはずです。
HTTP GET以外でもいろいろ試してみたい場合は、Postman などを使って、とりあえず、動作を見ることはできると思います。

5. 各種言語で簡易サーバを実装する

今のままでは、ngrokがローカルにフォワードする先のサーバがないので、簡易サーバを作っていきます。
httpのリクエストに対して、どんなレスポンスを返すべきかは、サービスごとにことなるので、
今回は、とりあえず、200 OKで、Bodyは以下のようなJSONを固定で返すことにします。
サービスによっては、204 No Contentで、Bodyなしで返せばよいものもあります。

とりあえず、レスポンスBodyに入れる内容(実際は利用するサービスによって適切なものを返す必要あり):

今回の例で固定で返すレスポンスBody
{
  "status" : "OK"
}

この後、例として記載するいずれかのコードを実行した上で、ngrokを実行して、
ngrokが準備したhttpsのURLを元にしたURL(https://{random-id}.ngrok.io/webhookとか)を、
対象のクラウドサービス側にWebhook通知先として登録すると動作します。
例では、ngrokが準備したURLに、"/webhook"のパスが追加されている前提のコードになっています
例では、サービスからの通知されたリクエストのBodyを表示して、固定のレスポンスを返しているだけですが、
利用するサービスに応じて、処理を少し追加すると、いろいろできると思います。

今回は、さくっと例を記載したいだけなので、処理に成功したかどうかのチェック等は省略しています。
煩雑になりすぎないように、必要最小限に近いくらいのコードになるようにしていますが、実際のアプリでは各種チェックが必要になります。

また、Webhookは、クラウドサービスの場合は、publicなURLで受ける場合が多いので、
偽装した通知が送られるような場合も想定しておく必要があると思いますが、その辺りの対策も今回の実装例には盛り込んではいません。

関連してですが、(いろんな意見あると思いますが)、個人的には、偽装通知する人のヒントになるような、
404 Not Foundとか405 Method Not Allowedとかも返すべきでないと思っていますが、その辺も今回の実装例には盛り込んでいません。
不正な通知に対しては、「204 No Contentで成功で返す」、
「レスポンス自体返さず相手はレスポンス待ち状態にする(TCPのコネクションは切断しない)」、
「レスポンスは返さず、TCPのコネクションを切断する」などの対処があると思います。
ただし、コネクションを切断しないパターンは、
サーバ側の残りの接続数やスレッドプール数に悪影響がでやすい(リソースを枯渇させる攻撃が考えられる)ので、
一般的には実装が難しいです(TCP/Socketレベルで実装考えないといけなくなると思います)。

5.1. Goでの実装の例

標準の、net/httpパッケージを利用した例です。
※ 説明用にコメントは多めに書いています。

go1.13 + Windows 10 Pro 64bitで動作確認しています。

webhook_listener.go
package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
)

// Webhookに通知が来た時に呼ばれるハンドラ。
func handleWebhook(w http.ResponseWriter, r *http.Request) {

    // Webhook通知元の仕様にもよるが、HTTPのPOSTかGETかで通知が来る前提にして、
    // それ以外は、"204 No Content"を返す。
    if r.Method != http.MethodPost && r.Method != http.MethodGet {
        w.WriteHeader(http.StatusNoContent)
        return
    }

    // Bodyの内容を読み込んで表示するだけ。
    body, _ := ioutil.ReadAll(r.Body)
    fmt.Println("ReceivedData:", string(body))

    ////////////////////////////////////////////////////////////////
    // 以下、レスポンスで何を返すべきかは通知元のサービス側の仕様にもよる。

    // "204 No Content"を返せばいい場合は、以下にコメントアウトした1行だけでBody出力不要。
    // w.WriteHeader(http.StatusNoContent)

    // レスポンスヘッダで、Content-Type: application/jsonにする。
    w.Header().Set("Content-Type", "application/json")

    // レスポンスのBodyは決め打ち。
    fmt.Fprint(w, "{\"status\" : \"OK\"}")
}

func main() {

    // ローカルサーバの"/webhook"にリクエストが来た時に呼ばれるハンドラを登録。
    http.HandleFunc("/webhook", handleWebhook)

    // ローカルの8080番ポート(ngrok起動時のオプションで指定した番号)で待ち受け開始。
    // ngrokが同じローカルマシンで動いているので、"127.0.0.1"だけで待ち受ければよい。
    // (外部の環境からリクエストを受ける必要がない)
    http.ListenAndServe("127.0.0.1:8080", nil)
}

5.2. C#での実装の例

.NET Core 2.0以降や、.NET Frameworkなどで利用できるSystem.Net.HttpListenerを利用した例です。
※ 説明用にコメントは多めに書いています。

.NET Core 2.0 + Windows 10 Pro 64bitで動作確認しています。

WebhookListener.cs
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace WebhookListener
{
    class Program
    {
        static void Main(string[] args)
        {
            // System.Net.HttpListenerを利用してサーバを実装します。
            using (var listener = new HttpListener())
            {
                // ローカルの8080番ポート(ngrok起動時のオプションで指定した番号)で待ち受け開始。
                // ngrokが同じローカルマシンで動いているので、"127.0.0.1"だけで待ち受ければよい。
                // (外部の環境からリクエストを受ける必要がない)
                // また、"/webhook"のパスも入れておく。Prefix指定時は、"/"で終わるようにしておく必要がある。
                listener.Prefixes.Add("http://127.0.0.1:8080/webhook/");

                // HttpListenerの待ち受けを開始します。
                listener.Start();

                // スレッドプール上で待ち受けるようにする。
                // 今回は1スレッドだが、例えばループで64回Task作成すれば、64スレッドで待ち受けるようになる。
                Task.Run(
                    async () =>
                    {
                        // HttpListenerが待ち受け中はループする。
                        while(listener.IsListening)
                        {
                            ////////////////////////////////////////////////////////////////
                            // このサンプルの実装では、whileブロック内で例外が発生すると後続の待ち受けも中断されます。
                            // 実際には、適切に例外を処理する必要があります。
                            // どの例外をcatchすべきかは、whileブロック内での処理内容にもよります。
                            // catchしすぎると、意図せずループが続く場合もあるので注意が必要です。
                            // 基本的には、例外が発生しても待ち受けを継続したいような場合の例外は、whileブロックの中、
                            // 待ち受けを継続しても意味がないような例外は、whileブロックの外側で受けるように実装します。

                            // 先ほど登録したアドレス、ポート、パスに合致するリクエストが来ると、処理用のContextが取得できる。
                            var context = await listener.GetContextAsync();

                            // リクエストとレスポンス処理用のインスタンス取得。
                            var request  = context.Request;
                            var response = context.Response;

                            try
                            {
                                if (request.HttpMethod != "POST" && request.HttpMethod != "GET")
                                {
                                    // Webhook通知元の仕様にもよるが、HTTPのPOSTかGETかで通知が来る前提にして、
                                    // それ以外は、"204 No Content"を返す。
                                    response.StatusCode = 204;
                                }
                                else
                                {
                                    // Bodyの内容を読み込んで表示するだけ。
                                    if (request.HasEntityBody)
                                    {
                                        using (var reader = new StreamReader(request.InputStream, request.ContentEncoding))
                                        {
                                            Console.WriteLine("RequestData: {0}", reader.ReadToEnd());
                                        }
                                    }

                                    ////////////////////////////////////////////////////////////////
                                    // 以下、レスポンスで何を返すべきかは通知元のサービス側の仕様にもよる。

                                    // "204 No Content"を返せばいい場合は、以下にコメントアウトした1行だけでBody出力不要。
                                    // response.StatusCode = 204;

                                    // レスポンスヘッダで、Content-Type: application/jsonにする。
                                    response.ContentType = "application/json";

                                    // レスポンスのBodyは決め打ちで書き込む。
                                    using (var writer = new StreamWriter(response.OutputStream))
                                    {
                                        writer.Write("{\"status\" : \"OK\"}");
                                    }
                                }
                            }
                            finally
                            {
                                // ResponseはClose()を呼ぶ必要があります。
                                response.Close();
                            }
                        }
                    });

                // 何かキーを押したら終了させる。
                Console.WriteLine("終了するには何かキーを押してください。");
                Console.ReadKey(false);
            }
        }
    }
}

5.3. Javaでの実装の例

Java 1.6以降利用可能なcom.sun.net.httpserver.HttpServerを利用した例です。
パッケージは、com.sun.net配下になっていますが、OpenJDKでも利用できます。
※ 説明用にコメントは多めに書いています。

OpenJDK11(AdoptOpenJDK) + Windows 10 Pro 64bitで動作確認しています。

WebhookListener.java
package thrzn41.samples;

import java.io.IOException;
import java.net.InetSocketAddress;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

public class WebhookListener {

    /**
     * リクエストが来た時に処理するハンドラクラス。
     */
    private static class WebhookHandler implements HttpHandler {

        /**
         * HttpServer.createContext()で登録したパスにHTTPリクエストがあると、このメソッドが呼ばれる。
         */
        @Override
        public void handle(HttpExchange exchange) throws IOException {

            // Webhook通知元の仕様にもよるが、HTTPのPOSTかGETかで通知が来る前提にして、
            // それ以外は、"204 No Content"を返す。
            String method = exchange.getRequestMethod();

            if( !method.equals("POST") && !method.equals("GET") ) {
                exchange.sendResponseHeaders(204, -1);
                return;
            }

             // Bodyの内容を読み込んで表示するだけ。
            try(var input = exchange.getRequestBody()) {

                // 本来は、ちゃんとリクエストヘッダのエンコーディングを見た方がいいですが、
                // 今回は、"utf-8"である前提で変換しています。
                String body = new String(input.readAllBytes(), "utf-8");

                System.out.printf("RequestData: %s%n", body);
            }


            ////////////////////////////////////////////////////////////////
            // 以下、レスポンスで何を返すべきかは通知元のサービス側の仕様にもよる。

            // "204 No Content"を返せばいい場合は、以下にコメントアウトした1行だけでBody出力不要。
            // exchange.sendResponseHeaders(204, -1);

            // レスポンスのBodyは決め打ちで書き込む。
            var responseBytes = "{\"status\" : \"OK\"}".getBytes("utf-8");

            try(var output = exchange.getResponseBody()) {

                // レスポンスヘッダで、Content-Type: application/jsonにする。
                exchange.getResponseHeaders().set("Content-Type", "application/json");
                exchange.sendResponseHeaders(200, responseBytes.length);

                output.write(responseBytes);
            }
        }

    }

    public static void main(String[] args) {

        try {
            // ローカルの8080番ポート(ngrok起動時のオプションで指定した番号)で待ち受けます。
            // ngrokが同じローカルマシンで動いているので、"127.0.0.1"だけで待ち受ければよい。
            // (外部の環境からリクエストを受ける必要がない)
            var server = HttpServer.create(new InetSocketAddress("127.0.0.1", 8080), -1);

            // ローカルサーバの"/webhook"にリクエストが来た時に呼ばれるハンドラを登録。
            server.createContext("/webhook", new WebhookHandler());

            // 今回はデフォルトのExecutorを利用してHTTPリクエストを処理するように指定(null)。
            server.setExecutor(null);

            // HttpServerの待ち受けを開始します。
            server.start();

            System.out.println("終了するには何かキーを押してください。");
            System.in.read();

            // HttpServerの待ち受けを停止します。
            server.stop(0);

        } catch(IOException ioex) {
            ioex.printStackTrace();
        }
    }

}

5.4. Pythonでの実装の例

標準のhttp.serverモジュールを使っても実装できますが、今回はflaskを使っちゃいます。
※ 説明用にコメントは多めに書いています。

flask入ってなかったら、以下でインストール(pip使う場合の例)。

pip使ってflaskインストールする例
$ pip install flask

Python 3.6 + flask 1.1 + Windows 10 Pro 64bitで動作確認しています。

webhook_listener.py
from flask import Flask, request

# 起動時の自分の名前からFlaskのインスタンス作成。
app = Flask(__name__)

# Webhookに通知が来た時に呼ばれるハンドラ。
# 以下の指定では、"/webhook"に対するHTTP POST, GET, PUT, DELETEリクエストの場合に呼ばれます。
# パスが違うと"404 Not Found", リストにないメソッドの場合は、"405 Method Not Allowed"が返ります。
@app.route("/webhook", methods=["POST", "GET", "PUT", "DELETE"])
def handle_webhook():

    # Webhook通知元の仕様にもよるが、HTTPのPOSTかGETかで通知が来る前提にして、
    # それ以外は、"204 No Content"を返す。
    if request.method != 'POST' and request.method != 'GET':
        # レスポンスのBodyとStatus Codeをタプルで返します(make_response()でタプル指定するのと同じ)。
        return ('', 204)

    # Bodyの内容を読み込んで表示するだけ。
    print(request.get_data(as_text=True))


    # ==============================================================
    # 以下、レスポンスで何を返すべきかは通知元のサービス側の仕様にもよる。

    # "204 No Content"を返せばいい場合は、以下にコメントアウトした1行だけでBody出力不要。
    # return ('', 204)

    # レスポンスのBodyは決め打ち。
    # ディクショナリで返せば、jsonに変換(jsonify()を呼ぶのと同じ)して、Content-Typeヘッダも"application/json"にしてくれます。
    # より厳密には、make_response()が呼ばれて、その中でjsonify()が呼ばれて、その中で、Content-Typeが"application/json"に設定される。
    return { 'status' : 'OK' }


if __name__ == '__main__':

    # ローカルの8080番ポート(ngrok起動時のオプションで指定した番号)で待ち受け開始。
    # ngrokが同じローカルマシンで動いているので、"127.0.0.1"だけで待ち受ければよい。
    # (外部の環境からリクエストを受ける必要がない)
    app.run('127.0.0.1', 8080)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Windows FormsアプリからWindows 設定を開く

機内モードのステータスに応じて設定画面出したくて困ったメモ

URIスキームを指定してLaunchUriAsyncで呼び出せば良さそう。
Windows 設定アプリの起動

だけども、これはWindowsFormsアプリでは使えない。
LaunchUriAsyncがUWPアプリ用。
こまった(´・ω・`)

What package is Windows.System.Launcher in?

Process.Start("ms-settings:network-airplanemode")

これで良いみたい(o・ω・o)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【.NET】HttpClient�の初回が遅いのはプロクシの自動検出が原因

はじめに

サーバーリプレース作業(Windows Server 2008R2 + Oracle 11g → Windows Server 2016 + PostgreSQL 9.6)で、Delphi5で作成されたアプリケーション(インターフェイス)をC#に作り替えました。
別会社によりC++で作成されたアプリケーションからパラメータを引数としてexe形式(Delphi5→C#)で呼び出され、Web(レガシーASP)で処理した結果を返すというものです。

性能検証してDelphi5で作成されたものと同性能であることを確認していました。ところがユーザーにいざ導入してみると体感として3秒くらい遅いとして却下され、Delphi5版をそのまま使用することになってしまいました。

ユーザーのPCは古いPCということもあって、C#(.NET Framework)にしたことで遅くなったのかと思って、Ngen.exe(ネイティブ イメージ ジェネレーター)など導入してもらったのですが結果は変わりませんでした。

調査

導入から4ヶ月くらい経ってようやくユーザーからC#版の調査の許可を得ること出来ました。
何が悪さをしているのか?

  • PC性能の問題
  • ネットワーク環境の問題

Windows 7のPCで確かに3秒くらいかかる現象が発生しました。しかし、Windows 10のPCでは現象が発生しませんでした。
Windows 7のPCは、こちらで持参し性能検証しても問題なかったPCでした。そのPCで現象が発生したということは性能の問題ではないということです。
ネットワークが怪しいということで、そういえばプロキシあたりが悪さをしているのかもとネット上で「プロキシ 遅い C#」で検索してみたところ、下記サイトが見つかりました。
プロキシ設定によりWebページへのアクセスを高速化するには?

プロキシを使用しないようにするconfigファイルの設定

xxx.exe.config
<configuration >
  <system.net>
    <defaultProxy>
      <proxy usesystemdefault="false" />
    </defaultProxy>
  </system.net>
</configuration>

この設定をexeと一緒に配置して実行したところ、3秒くらいかかる現象が発生しなくなりました。

会社に持ち帰り詳細に調査することにしました。会社とユーザーとの環境の違いとして、Widows 7のPCのサーバーへの接続方法があります。
これまで動的IPアドレスで接続していたのですが、固定IPアドレスに切り替えました。ただしプロキシは未指定です。
すると、会社でも固定IPアドレスにすると現象が発生することが分かりました。
また、IEのインターネットオプションの接続タブのLANの設定にて「設定を自動的に検出する」のチェックをオフにすることで、現象が発生しなくなることを確認しました。

原因

調査を踏まえて、どういうことなのかってことですね。キーワードとしては、「自動プロキシ検出」となります。

固定IPアドレスの場合、プロキシの自動検出機能によりプロキシ構成スクリプトの検出で遅くなっている。IPアドレスの自動取得の場合、DHCPサーバーがあることでプロキシ構成スクリプトがすぐ見つかるため遅くならない。

C#(.NET Framework)のWebClientクラスやHttpWebRequestクラスやHttpClientクラスでは、デフォルト設定では自動プロキシ検出が行われるようになっている。Delphi5で問題なかったのは、自動プロキシ検出など使わない方式だったためです。

対応

今回は、configファイルは使用せずにプログラム上で自動プロキシ検出をしないように修正した。

// プロキシを使用しない
HttpClientHandler handler = new HttpClientHandler();
handler.UseProxy = false;

using (var client = new HttpClient(handler))
{
    string uri = "http://localhost/";

今回はexe形式のインターフェイスで起動するたびにインスタンスを生成するため、HttpClientをusingで囲んでいますが、本来はHttpClientをusingで囲わない方がいいです。

最後に

性能検証でユーザー環境と同じ固定IPアドレスにするという観点は見逃していました。そんなところで問題が出るとは思わなかったです。
今回の対応はするとして、自動プロキシ検出に10〜15秒かかるという記事がある中で3秒くらいであること、持参したWindows 10では現象が発生してないことなど、まだ謎が残っている。これは何か分かり次第追記していきます。

.NET Frameworkの初回起動が遅いのは有名ですが、Web系アプリケーションだと自動プロキシ検出で余分にかかっているかも知れませんね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【.NET】HttpClientの初回が遅いのはプロクシの自動検出が原因

はじめに

サーバーリプレース作業(Windows Server 2008R2 + Oracle 11g → Windows Server 2016 + PostgreSQL 9.6)で、Delphi5で作成されたアプリケーション(インターフェイス)をC#に作り替えました。
別会社によりC++で作成されたアプリケーションからパラメータを引数としてexe形式(Delphi5→C#)で呼び出され、Web(レガシーASP)で処理した結果を返すというものです。

性能検証してDelphi5で作成されたものと同性能であることを確認していました。ところがユーザーにいざ導入してみると体感として3秒くらい遅いとして却下され、Delphi5版をそのまま使用することになってしまいました。

ユーザーのPCは古いPCということもあって、C#(.NET Framework)にしたことで遅くなったのかと思って、Ngen.exe(ネイティブ イメージ ジェネレーター)など導入してもらったのですが結果は変わりませんでした。

調査

導入から4ヶ月くらい経ってようやくユーザーからC#版の調査の許可を得ること出来ました。
何が悪さをしているのか?

  • PC性能の問題
  • ネットワーク環境の問題

Windows 7のPCで確かに3秒くらいかかる現象が発生しました。しかし、Windows 10のPCでは現象が発生しませんでした。
Windows 7のPCは、こちらで持参し性能検証しても問題なかったPCでした。そのPCで現象が発生したということは性能の問題ではないということです。
ネットワークが怪しいということで、そういえばプロキシあたりが悪さをしているのかもとネット上で「プロキシ 遅い C#」で検索してみたところ、下記サイトが見つかりました。
プロキシ設定によりWebページへのアクセスを高速化するには?

プロキシを使用しないようにするconfigファイルの設定

xxx.exe.config
<configuration >
  <system.net>
    <defaultProxy>
      <proxy usesystemdefault="false" />
    </defaultProxy>
  </system.net>
</configuration>

この設定をexeと一緒に配置して実行したところ、3秒くらいかかる現象が発生しなくなりました。

会社に持ち帰り詳細に調査することにしました。会社とユーザーとの環境の違いとして、Widows 7のPCのサーバーへの接続方法があります。
これまで動的IPアドレスで接続していたのですが、固定IPアドレスに切り替えました。ただしプロキシは未指定です。
すると、会社でも固定IPアドレスにすると現象が発生することが分かりました。
また、IEのインターネットオプションの接続タブのLANの設定にて「設定を自動的に検出する」のチェックをオフにすることで、現象が発生しなくなることを確認しました。

原因

調査を踏まえて、どういうことなのかってことですね。キーワードとしては、「自動プロキシ検出」となります。

固定IPアドレスの場合、プロキシの自動検出機能によりプロキシ構成スクリプトの検出で遅くなっている。IPアドレスの自動取得の場合、DHCPサーバーがあることでプロキシ構成スクリプトがすぐ見つかるため遅くならない。

C#(.NET Framework)のWebClientクラスやHttpWebRequestクラスやHttpClientクラスでは、デフォルト設定では自動プロキシ検出が行われるようになっている。Delphi5で問題なかったのは、自動プロキシ検出など使わない方式だったためです。

対応

今回は、configファイルは使用せずにプログラム上で自動プロキシ検出をしないように修正した。

// プロキシを使用しない
HttpClientHandler handler = new HttpClientHandler();
handler.UseProxy = false;

using (var client = new HttpClient(handler))
{
    string uri = "http://localhost/";

今回はexe形式のインターフェイスで起動するたびにインスタンスを生成するため、HttpClientをusingで囲んでいますが、本来はHttpClientをusingで囲わない方がいいです。

最後に

性能検証でユーザー環境と同じ固定IPアドレスにするという観点は見逃していました。そんなところで問題が出るとは思わなかったです。
今回の対応はするとして、自動プロキシ検出に10〜15秒かかるという記事がある中で3秒くらいであること、持参したWindows 10では現象が発生してないことなど、まだ謎が残っている。これは何か分かり次第追記していきます。

.NET Frameworkの初回起動が遅いのは有名ですが、Web系アプリケーションだと自動プロキシ検出で余分にかかっているかも知れませんね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#でMySQLからSELECTした結果を取り出したい

C#は素人なのと、あとコードは再現なので不適切なコードになっている可能性が高いです。
おそらくもっといい解法があるはずですが、調べた限りではよくわかりませんでした。

課題

テーブルAからSELECTする、テーブルBからSELECTする、その後ふたつのデータを色々やって最後にテーブルCにインサートする、みたいなことがやりたかったわけですよ。
コード側で色々と処理を行う必要があるため、一度コード側にデータを引き取るのが前提です。

問題

ベストプラクティスがわからない。
適切なサンプルコードが見付からない。

信頼できるドキュメントはMicrosoft公式くらいしかないわけですが、そこに載ってるサンプルコードはどうにも役に立ちません。

https://docs.microsoft.com/ja-jp/azure/mysql/connect-csharp
https://docs.microsoft.com/ja-jp/dotnet/api/overview/azure/mysql?view=azure-dotnet

公式サンプルの例
using (MySqlConnection conn = new MySqlConnection(connectionString))
{
    conn.Open();

    // SQL発行
    MySqlCommand selectCommand = new MySqlCommand("SELECT * FROM MyTable", conn);
    MySqlDataReader results = selectCommand.ExecuteReader();

    // 行ごとにループ
    while(results.Read())
    {
        Console.WriteLine("Column 0: {0} Column 1: {1}", results[0], results[1]);
    }
}

ループ中でログ出力する例しか載ってない。
そんな役に立たないコードじゃなくてもっとこう、取得結果をreturnして他所で使い回す方法はないんですかね。

しかもresults[0]とか、今どき列番号で取ってくるなんて有り得ないじゃろ。
列名で取ってきてくれよ。

やってみた

// メイン
public static void Main{
    MySqlConnection mySqlConnection = new MySqlConnection("ConnectionString");
    mySqlConnection.Open();

    var tableA = getTable("tableA");
    var tableB = getTable("tableB");
}

// 取得
public async Task<DataReader> getTable(string tableName){
    using (var command = mySqlConnection.CreateCommand()) {
        command.CommandText = $"SELECT * FROM {tableName}";
        using (var reader = command.ExecuteReaderAsync()) {
            return reader;
        }
    }
}

できた。

はい、これThere is already an open DataReader associated with this Connection which must be closed firstとか言われて死にます。
どうもDataReaderは同時にひとつしか開けないらしい。
二つ目のSQLを投げる前に、一つ目のSQLはCloseしなくてはなりません。
Closeすると当然データが取れなくなるので、Closeする前に値を取り出しておかねばならないということです。

少し変更した

DataReaderから値を取得して、Dictionaryに突っ込んで返すようにしました。

static MySqlConnection mySqlConnection;

public static void Main{
    mySqlConnection = new MySqlConnection("ConnectionString");
    mySqlConnection.Open();

    var tableA = getTable("tableA");
    var tableB = getTable("tableB");
}

// 取得
public Dictionary<string, Dictionary<string, string>> getTable(string tableName){
    using (var command = mySqlConnection.CreateCommand()) {
        command.CommandText = $"SELECT * FROM {tableName}";
        using (var reader = command.ExecuteReader()) {
            if (reader.HasRows) {
                while (reader.Read()) {
                    JObject line = new JObject();
                    for (int i = 0; i < reader.FieldCount; i++) {
                        line.Add(reader.GetName(i), reader.GetString(i));
                    }
                    ret.Add(reader.GetString("id"), line);
                }
            }
        }
    }
    return ret;
}

これでできた!
と思いきや、何故かこれでもalready an open DataReaderが発生することがあります。
なんで?returnする前にusingの外に出てるやろ?
原因がさっぱりわからなかったので諦めました。

しかも、どうやらその行の列を全て取得するみたいなメソッドがどうも存在しないっぽい。
いちいちFieldCountで列数を調べて全列をループで回して取り出しています。
SQL発行する度に毎回行×列のループを回さねばならないとか、本当かこれ???

そもそも最初からfetchAllがあれば解決なのですよ。
どうしてこの程度のメソッドが用意されてないのですかね?

とりあえず完成

mySqlConnectionを毎回開けばええんや。

public static void Main{
    var tableA = getTable("tableA");
    var tableB = getTable("tableB");
}

// 取得
public Dictionary<string, Dictionary<string, string>> getTable(string tableName){
    using (MySqlConnection mySqlConnection = new MySqlConnection("ConnectionString")) {
        mySqlConnection.Open();
        using (var command = mySqlConnection.CreateCommand()) {
            command.CommandText = $"SELECT * FROM {tableName}";
            using (var reader = command.ExecuteReader()) {
                if (reader.HasRows) {
                    while (reader.Read()) {
                        var line = new Dictionary<string, string>();
                        for (int i = 0; i < reader.FieldCount; i++) {
                            line.Add(reader.GetName(i), reader.GetString(i));
                        }
                        ret.Add(reader.GetString("id"), line);
                    }
                }
            }
        }
    }
    return ret;
}

いや……一応これで動いたんだけどさあ、どう考えてもおかしいだろ、これ。
SQLを発行するたび毎回MySqlConnectionをnewしてるってことは、PHPで言うと毎回new PDOってしてるってことですよね。
そんな書き方ありえなーい。
そもそも高々SQL発行するだけなのに何重に括らせる気なんだよ。

なんかもっとこう、ちゃんとした正しい書き方ってのがあるよね?
あると思うのですよ。
間違いなく存在するでしょう。

実はありまぁす

DataTableってのがあった。

public static void Main{
    var tableA = getTable("tableA");
    var tableB = getTable("tableB");
}

public DataTable getTable(string tableName) {
    DataTable tbl = new DataTable();
    using(MySqlConnection mySqlConnection = new MySqlConnection(builder.ConnectionString)) {
        mySqlConnection.Open();
        using(var command = mySqlConnection.CreateCommand()) {
            command.CommandText = $"SELECT * FROM {tableName}";
            using(var reader = command.ExecuteReader()) {
                tbl.Load(reader);
            }
        }
    }
    return tbl;
}

これでDataTableに全データが入ってくるようで、MySqlConnectionを閉じた後でも問題なく参照することができました。

どう考えても真っ先にこれを紹介すべきなのに、普通に調べたら全然出てこないとかいう。
ここまで調べるのにめっちゃ時間かかった。

なおMySqlConnectionを閉じないとエラーになるのは相変わらずです。
どうやったら使い回せるんですかね?
それ以前に、そもそも本当にコレが最終解なんですかね?

その他

C#では特に目立つ気がするのですが、Stackoverflowを機械訳したようなゴミURLばっかり引っかかってくるのはどうにかならないんですかね?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む