.NET Core で画像を縮小する

ふと思い立って、DS用のツイッタークライアントを試作している。ツイッター社からの機能の公開が不十分だし、それぞれのクライアント開発者も続々と撤退しているので、メリットがあるのか?というえば「ない!」んだけど、ASP.NET MVC Core の周辺の調査も兼ねて、というところで。

いずれ、github にでも公開するが、

  • ASP.NET MVC Core + .NET Core
  • CoreTweet

の組み合わせで作っている。以前、作っていた時は PHP でやっていたのだが、PHP にあまり慣れていないところもあって頓挫していしまった。今回は、仮想サーバーも借りたことだし、.NET Core も入れれば、その界隈のライブラリを使えるというメリットがあって、この組み合わせになっている。

.NET Core で画像を扱う

DSブラウザ(写真のはDSiなんだけど)は、結構古いので、絵文字が使えないとか内蔵している証明書が古めとか、そういう問題もある。メモリが限られているというのもある。なので、大き目の画像をはめてしまうとブラウザのメモリから溢れてしまう。

ひとまず、ツイッターで表示されている画像を 320×240 に固定にしてしまおう。

ところで、.NET Core 2.0 には画像関係のライブラリは標準で付いてこない。.NET Standard 3.0 だったかにはその予定はあるのだけど、ひとまず現状はない。

.NET Core向けの画像ライブラリ
https://www.infoq.com/jp/news/2017/04/net-core-imaging

最終的には、ラズパイや外部Linuxサーバーで動かすことになるので、Windows依存にしたくないところなので、.NET Core のままいくとして、NuGet から System.Drawing.Common をダウンロードする。

using System.Drawing;
using System.Net.Http;

public class ImageController : Controller
{
    public IActionResult Index()
    {
        return View();
    }
    [HttpGet("Image/{path}")]
    public IActionResult Sumnail([FromRoute] string path)
    {
        try
        {
            var hc = new HttpClient();
            path = System.Web.HttpUtility.UrlDecode(path);
            var res = hc.GetAsync(path, HttpCompletionOption.ResponseContentRead).Result;
            var st = res.Content.ReadAsStreamAsync().Result;
            var srcbmp = new Bitmap(st);
            int h = 240;
            int w = 320;

            if ( srcbmp.Width > 320 )
            {
                h = srcbmp.Height * w / srcbmp.Width;
                if ( h > 240 )
                {
                    w = w * 240 / h;
                    h = 240;
                }
            }
            if (srcbmp.Height > 240)
            {
                w = srcbmp.Width * h / srcbmp.Height;
                if (w > 320)
                {
                    h = h * 320 / w;
                    w = 320;
                }
            }
            var destbmp = new Bitmap(w, h);
            var g = Graphics.FromImage(destbmp);
            g.FillRectangle(new SolidBrush(Color.LightGray), 0, 0, w, h);
            g.DrawImage(srcbmp, 0, 0, w, h);
            var mem = new System.IO.MemoryStream();
            destbmp.Save(mem, System.Drawing.Imaging.ImageFormat.Jpeg);
            mem.Position = 0;

            return new FileStreamResult(mem, "image/jpeg");
        }
        catch
        {
            return NotFound();
        }
    }
}

こうすると、

http://localhost:5000/Image/http%3a%2f%2fpbs.twimg.com%2fmedia%2fDfa0dCxUEAAgrwg.jpg

な感じで、320×240 の画像に縮小できる。これをタイムラインの index.cshtml に埋め込む。

@foreach ( var it in statuses )
{
<div style=&quot;width:320px;&quot;>
    @{
        var item = it.RetweetedStatus == null ? it : it.RetweetedStatus;
    }
    @if (it.RetweetedStatus != null)
    {
        <div><a href=&quot;Tweet/@it.User.ScreenName&quot;>@it.User.Name</a> さんがリツイート</div>
    }
    <img src=&quot;@item.User.ProfileImageUrl&quot; width=&quot;48&quot; height=&quot;48&quot; align=&quot;left&quot; style=&quot;margin:4px&quot; />
    <div style=&quot;font-weight:bold&quot;><a href=&quot;Tweet/@it.User.ScreenName&quot;>@item.User.Name</a> @item.User.ScreenName @item.CreatedAt</div>
    <div>@item.Text</div>

    @if (item.Entities != null && item.Entities.Media != null)
    {
        @foreach (var m in item.Entities.Media)
        {
            <!-- TODO:画像はサムネール化する -->
            var thum = System.Web.HttpUtility.UrlEncode(m.MediaUrl); 
            <img src=&quot;/Image/@thum&quot; />
        }
    }
</div>

多分、magic.net を使ったほうが高機能なんだろうけど、縮小だけだったらこれで十分かも。Graphics クラスの中身はひと通りあるみたい。

NuGet Gallery | System.Drawing.Common 4.5.0
https://www.nuget.org/packages/System.Drawing.Common/

つい最近(5/30)に正式版になったっぽい

カテゴリー: 開発, C#, NET Core | .NET Core で画像を縮小する はコメントを受け付けていません

Android Things v1.0へsshで接続するなど諸々

せっかく Android Things が v1.0 になったので、再び Raspberry Pi に入れて確認してみる。

Android Things の microSD を作る

Android Things Setup Utillityの使い方 – Qiita
https://qiita.com/masaya3/items/a2fcb2811e74b076be47
Android Things Console
https://partner.android.com/things/console/#/tools

記事を参考にして、Android Things Console から Setup Utility をダウンロードする。zip ファイルを解凍して、android-things-setup-utility-windows を管理者権限で実行する。

– 1. Install Android Things and optionally set up Wi-Fi
– 1 – Raspberry Pi 3
– 1 – Default image: Used for development purposes. No access to the Android

のように選択すればラズパイ用の microSD ができる。WiFi の設定は microSD を作るときに設定できるのだけど、デスクトップPCの場合は WiFi に接続していない場合が多いし、ラズパイ+Android Thingsを起動した後にでもUSB接続のマウスとキーボードを使って、画面から設定が可能なので、この時点ではやらなくてもよい。

HDMI 接続のモニタ上から、network を設定できる。ベータ版とは違って画面上からできるのが便利。

adb shell する

普通の android と同じように adb shell ができる。Xamarin.Android の時と同じように、visual studio から「ツール」→「Android」→「Android adb コマンドプロンプト」を選択する。
既に、Android Things は開発者モード相当になっているので、WiFi が繋がっていれば tcpip で接続できる。

adb connect <Android ThingsのIP>:5555

リモートで画面が表示できる vysor があれば、画面も操作ができる

画面サイズは 640×480 固定になっているらしい。

Androidアプリをインストールする

せっかくなので、Xamarin.Forms を Android Things に入れて動かしてみる。Visual Studio 2017 で Xamarin.Forms のプロジェクトを作って、「Google iot_rpi3(Android 8.1 – API27)」で実行している。

右側に縞が入っているのは、モニタの解像度が合っていないから、このモニタは800×480なので、Android Things の config.txt あたりで設定できるはず。

既存のアプリ(*.apk)をインストールする

Android Things には Google Play のようなものがないので、*.apk ファイルを直接インストールする必要がある。

SSHDroid – Google Play のアプリ
https://play.google.com/store/apps/details?id=berserker.android.apps.sshdroid

SSHDroid が、Android へ SSH で繋げられるアプリなので、https://apps.evozi.com/apk-downloader/ や https://apkpure.com/jp/ から、SSHDroid_v2.1.2_apkpure.com.apk をダウンロードして PC に保存する。

adb install SSHDroid_v2.1.2_apkpure.com.apk

な形でインストールできる。

アプリを起動する

Android Things にはランチャーみたいなものがないので、adb shell am start -n を使わないといけない。このとき、開始する Activity 名が必要なのだが、これが苦労する。

package名のみからadb shell am start -nする – Qiita
https://qiita.com/mattak/items/41b1ce1d48ddb3b2bb4a

pm list package でインストール済みのパッケージ一覧を確認した後で、pm dump で起動するときのアクティビティを得る。既に adb shell しているときは pm list package でよい。

adb shell pm list package

more して android.intent.action.MAIN の部分だけを見る。

adb shell pm dump <パッケージ名>

この場合は、下記のように起動する。

adb shell am start -n com.companyname.App1/md5a445f6db5abf8b5b69aa92721cad9446.MainActivity

ちなみに、ホームへ戻るときには、input keyevent を使う。

adb shell input keyevent KEYCODE_HOME

SSHDroid へ接続する

am start -n berserker.android.apps.sshdroid/.MainActivity

で起動する。

Tera term で ssh:2222 で接続できる。ユーザ名は「root」でパスワードは「admin」になっている。

各種コマンドは busybox だそうで .bin がパスに追加されている。残念ながら su にはなれないが、vi が使えるのがいい。

WinSCP でつなぐことができるので、ファイル転送とかもできると思う。

その他

SSHDroid は SSH 接続するだけなので、Python を動かしたり、apt-get したりできない。このあたりは、Termux を使うといいらしいのだが、これは後で試してみる。

Termux – Google Play のアプリ
https://play.google.com/store/apps/details?id=com.termux

また、Android Things はグラフィック系がごっそりカットされる…予定だったのだが、ディスプレイ関係はごっそり残っている。ラズパイだとCPUが遅く駄目なんだと思うのだが、アクションじゃないゲームのほうが動くんじゃないかな。

例えば、戦艦少女RをAndroid Thingsで起動できる。アズレンはちょっと重たくて初期画面のところで駄目になってしまう。

com.huanmeng.zhanjian_jp_release/org.cocos2dx.cpp.AppActivity

Android アプリを Android Things に入れてもあまり意味がないのだが、ひとまず Android と同じ用にグラフィック系も扱えることが分かる(完全ではないけれど)。プラス、GPIO にセンサーを繋げたりボタンを繋げたりすることができるので、例えばロボット操作なアプリを Google Play で配布しておいて、実際はラズパイ+Android Things+ロボットアームな機器、を操作できるというのも可能ではある。ピン番号とかラズパイ用のハットを合わせないと駄目なんだけど、アプリ自体の配布は楽になるのかも。

あと、GUI 絡みは Xamarin.Forms がそのまま動くし、Android Things の場合は root になれるので、荒っぽいファイル操作も可能になる。まだ試してないのだけど、あらかじめARMでビルドしたバイナリファイルを microSD に入れておけば Android 上で実行できると思うんだけど、どうなんのだろう。

カテゴリー: 開発, Android, RaspberryPi | Android Things v1.0へsshで接続するなど諸々 はコメントを受け付けていません

M5Stack から Slack へ投稿できた編

前回、M5Stack から Slack へ投稿する挑戦中 やっていたものから、slack.com に投稿ができたので、ひとまず解決。

起動時に送信するだけなので、setup 関数に全て埋め込んでしまっているが、本来は M5Stack のスイッチを押したときとか、定期的なタイミングで温度センサーから読み込んで投稿、みたいなのを考えている。

#include <Arduino.h>
#include <WiFi.h>
#include <WiFiMulti.h>
#include <HTTPClient.h>
#include <M5Stack.h>

WiFiMulti wifi;

#define WIFI_SSID &quot;<SSID名>&quot;
#define WIFI_PASS &quot;<SSIDパスワード>&quot;
const char *server = &quot;hooks.slack.com&quot;;
const char *json = &quot;{\&quot;text\&quot;:\&quot;from arduino m5stack message.\&quot;,\&quot;icon_emoji\&quot;:\&quot;:ghost:\&quot;,\&quot;username\&quot;:\&quot;m5stackpost\&quot;}&quot;;

const char* slack_root_ca= \
&quot;-----BEGIN CERTIFICATE-----\n&quot; \
&quot;MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh\n&quot; \
&quot;MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n&quot; \
&quot;d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\n&quot; \
&quot;QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT\n&quot; \
&quot;MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n&quot; \
&quot;b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG\n&quot; \
&quot;9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB\n&quot; \
&quot;CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97\n&quot; \
&quot;nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt\n&quot; \
&quot;43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P\n&quot; \
&quot;T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4\n&quot; \
&quot;gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO\n&quot; \
&quot;BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR\n&quot; \
&quot;TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw\n&quot; \
&quot;DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr\n&quot; \
&quot;hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg\n&quot; \
&quot;06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF\n&quot; \
&quot;PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls\n&quot; \
&quot;YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk\n&quot; \
&quot;CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=\n&quot; \
&quot;-----END CERTIFICATE-----\n&quot; ;
  
HTTPClient http;

void setup() {

  // put your setup code here, to run once:
  wifi.addAP(WIFI_SSID, WIFI_PASS);
  Serial.begin(115200);
  M5.begin();
  M5.Lcd.println(&quot;send slack&quot;);
  
  while (wifi.run() != WL_CONNECTED) {
    delay(500);
    M5.Lcd.printf(&quot;.&quot;);
  }
  M5.Lcd.println(&quot;wifi connect ok&quot;);

  // post slack
  M5.Lcd.println(&quot;connect slack.com&quot;);
  http.begin( server, 443, &quot;/services/XXXX/XXXX/XXXX&quot;, slack_root_ca );
  http.addHeader(&quot;Content-Type&quot;, &quot;application/json&quot; );
  http.POST((uint8_t*)json, strlen(json));
  M5.Lcd.println(&quot;post hooks.slack.com&quot;);
}



void loop() {
  // put your main code here, to run repeatedly:

}

ポイントは,

  1. WiFiMulti でアクセスポイントの接続
  2. HTTPClient を使う。
  3. HTTPClient::begin で hook.slack.com に https 接続する。このとき、slack.com の証明書を指定する。
  4. HTTPClient::POST() で JSON 形式のデータを POST する。

ESP32 絡みで、HTTP プロトコルを作る例はいくつかあるのだけど、きちんと POST できるのは「HTTPClient」であった。標準で「HttpClient」ってのがあるけど、これは動かない。

Windows ではじめるM5Stack – Qiita
https://qiita.com/xzr_tw/items/3363a500b4503e424d8b
espressif/arduino-esp32: Arduino core for the ESP32
https://github.com/espressif/arduino-esp32

の中にある https://github.com/espressif/arduino-esp32/blob/master/libraries/HTTPClient/src/HTTPClient.h を見ると、HTTPClient の各種メソッドが想像できる。マニュアルがあるかどうかわからないのだけど、まあ、github で HTTPClient.cpp を見ればよい。

内部で、WiFi.h と WiFiClientSecure.h がインクルードされているので、WiFiClientSecure を使って HTTP プロトコルのヘッダ部をちまちま作るよりもベターかもしれない。

slack に送る json の形式と URL アドレスは、

C# で Slack に投稿する | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/9117

で作ったものを使っている。

起動して、アクセスポイントに接続、その後 slack に投稿する。

PC の slack クライアントにリアルタイムに表示されるので、なかなか面白い。

何に使うか?

データ蓄積ならば、azure か aws の IoT Hub を使って情報蓄積するところで、それだと HTTP の GET で十分だったりする。ただ、何かの閾値を超えた(高温になったとか、ドアが開いたとか)場合には、何らかの警告をユーザーに示しておきたいわけで、大抵の場合はメール通知を使うか、専用のクライアントアプリを使うのだが(スマホの通知とか?)、もっとお手軽&試作的なところでは slack の通知を使うと便利だろう。まあ line でもいいんだけど、line の場合は電話番号が必要なので slack のように捨てメールアドレスで作れると便利だったりする。

M5Stack の場合は LCD が標準で付いているので、定期的に web api を叩いて表示するってのもできる。同じことはラズパイやPC等でもできるのだが、電力が少なくて済む(5V0.5A程度の範囲でよい)ってのがよい。ああ、Orange Pi でもいいんだけど、そっちのほうは別途ためしてみる。

カテゴリー: 開発, M5Stack, Slack | M5Stack から Slack へ投稿できた編 はコメントを受け付けていません

VBでwebapiするdotnetテンプレートをnuget化する

dotnet テンプレートができたので、nuget からダウンロードできるようにする。

Visual Basic で ASP.NET MVC Core する | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/9206

これをテンプレート化して nuget.org に入れるのだが、以下が参考になる。

NuGet クイックスタート | nupkg をつくる – secretbase.log
http://cointoss.hatenablog.com/entry/2017/03/15/071926
dotnet new のカスタム テンプレートを作成する | Microsoft Docs
https://docs.microsoft.com/ja-jp/dotnet/core/tutorials/create-custom-template
NuGet パッケージの作成方法 | Microsoft Docs
https://docs.microsoft.com/ja-jp/nuget/create-packages/creating-a-package

dotnetテンプレートを作る

色々はまるのだが、以下なフォルダ構造になるらしい。

+ src
 + Moonmile.WebapiTemplate.VB
  - Moonmile.WebapiTemplate.VB.nuspec
  - nuget.exe
 + content
  + .template.config
   - template.json
  - Moonmile.WebapiTemplate.VB.vbproj
  - *.vb 群
  • テンプレートとなるフォルダは「content」になる。
  • テンプレートの設定ファイル(template.json)は、「.template.config」フォルダ内に置く。
  • nuget のための Moonmile.WebapiTemplate.VB.nuspec は、コンテンツのひとつ上になる。

Moonmile.WebapiTemplate.VB.vbproj なフォルダで、Console プロジェクトなどを作ってテンプレート化することになる。

.template.config/template.json

{
    "$schema": "http://json.schemastore.org/template",
    "author": "Tomoaki Masuda",
    "classifications": [ "Web", "WebAPI" ], 
    "name": "ASP.NET Core Web API via Visual Basic",
    "identity": "Moonmile.WebapiTemplate.VB",         
    "groupIdentity":"Moonmile.WebapiTemplate",
    "shortName": "webapivb",                   
    "tags": {
      "language": "VB",         
      "type":"project"
    },
    "sourceName": "Moonmile.WebapiTemplate.VB",
    "preferNameDirectory":true  
  }
  • identity は、nuget でしているするときの名前になるハズ
  • sourceName で *.vbproj と同じ名前にしておくと dotnet new -name で指定したときにプロジェクト名を同じにあわせてくれる

この状態で nuget pack すると、*.nupkg を作ってくれる。nuget pack はプロジェクトファイルを指定しなくても、*.nuspec と名前を合わせておけば大丈夫らしい。

<?xml version=&quot;1.0&quot;?>
<package >
  <metadata>
    <id>Moonmile.WebapiTemplate.VB</id>
    <version>0.1.0</version>
    <title>WebAPI project by Visual Basic</title>
    <authors>Tomoaki Masuda</authors>
    <owners>Tomoaki Masuda</owners>
    <licenseUrl>https://github.com/moonmile/dotnet-template-webapi-vb</licenseUrl>
    <projectUrl>https://github.com/moonmile/dotnet-template-webapi-vb</projectUrl>
    <iconUrl>http://moonmile.net/</iconUrl>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>
      Creates Web API project by Visual Basic.
    </description>
    <releaseNotes>
      sample version.
    </releaseNotes>
    <copyright>Copyright moonmile solutions</copyright>
    <tags>WebAPI VB</tags>
    <packageTypes>
      <packageType name=&quot;Template&quot; />
    </packageTypes>
      </metadata>
</package>

ローカルで *.ngpkg をインストールする

できあがった *.ngpkg ファイルを dotnet new コマンドでインストールする。

dotnet new -i Moonmile.WebapiTemplate.VB.0.1.0.nupkg

こんな風に、自前のテンプレートが追加されていれば ok

アンインストールは、ID である Moonmile.WebapiTemplate.VB を指定すればok.

dotnet new -u Moonmile.WebapiTemplate.VB

nuget にアップロードしたものを取り込む場合は、ID で大丈夫

dotnet new -i Moonmile.WebapiTemplate.VB

試しにプロジェクトを作ってみる

dotnet new webapivb -lang vb -n web

どうやらデフォルトがC#に固定らしく、-lang で言語を指定しないとだめっぽい。

プロジェクトができたら、vscode や visual studio でコーディングができる。

当然のことながら、さっくりと dotnet run ができる。

テンプレートコードとnuget

moonmile/dotnet-template-webapi-vb: dotnet new tamplete create WebAPI by Visual Basic.
https://github.com/moonmile/dotnet-template-webapi-vb
NuGet Gallery | Moonmile.WebapiTemplate.VB
https://www.nuget.org/packages/Moonmile.WebapiTemplate.VB

まあ、普通ならば web api は C# で作るだろうし、F# のような関数型だと C# との違いも明確になるのだけど VB だといまひとつ文法が冗長なだけで手を出しても意味がないような気がしないでもない。このあたりは、単純に好みの問題かな。VBA や VBScript とかうまく融合できればいいのだけど。

おまけ

実は、dotnet new コマンドで取り込むのはC#/VBなどのプロジェクトじゃなくてよい。ファイルを取り込むことができるので、Excel を読み込ませることもできる。試しに Excel 方眼紙を dotnet new で作るパターン

https://www.nuget.org/packages/Moonmile.Excel.Hougan/

install すると、

dotnet new -i Moonmile.Excel.Hougan

こんな風に Excel 方眼紙プロジェクト?を作ることができる。

dotnet new excelhougan -lang Excel 
カテゴリー: 開発, NET Core, VB | VBでwebapiするdotnetテンプレートをnuget化する はコメントを受け付けていません

Visual Basic で ASP.NET MVC Core する

意図的なのかワザとなのか意地悪なのかしらないが、dotnet new には Visual Basic のプロジェクトを作成する機能はない。

機能はないのだが、下記を参照してカスタムテンプレートを作れば VB の ASP.NET MVC Core なプロジェクトテンプレートを作れる。

dotnet new のカスタム テンプレートを作成する | Microsoft Docs
https://docs.microsoft.com/ja-jp/dotnet/core/tutorials/create-custom-template

が、まあ、初手としてまずは「できる」ことを確認するために手作業でテンプレートを作ってみようって訳で。

.NET Core コンソールアプリから始める

プロジェクトで「コンソールアプリ(.NET Core)を作る

プロジェクトファイル(*.vbproj)を開いて、wwwroot と Microsoft.AspNetCore.All を追加する。

<Project Sdk=&quot;Microsoft.NET.Sdk&quot;>

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <RootNamespace>web_vb</RootNamespace>
    <TargetFramework>netcoreapp2.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <Folder Include=&quot;wwwroot\&quot; />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include=&quot;Microsoft.AspNetCore.All&quot; Version=&quot;2.0.8&quot; />
  </ItemGroup>

  <ItemGroup>
    <DotNetCliToolReference Include=&quot;Microsoft.VisualStudio.Web.CodeGeneration.Tools&quot; Version=&quot;2.0.4&quot; />
  </ItemGroup>

</Project>

C# にある「Microsoft.VisualStudio.Web.CodeGeneration.Tools」も追加しているが、スキャフォールディングとかしない(多分できない)ので、必要ないかも。

Program.vb と Startup.vb を追加する

C# と合わせるために2つのファイルを追加する。

Program.vb

Imports System
Imports System.Collections.Generic
Imports System.IO
Imports System.Linq
Imports System.Threading.Tasks
Imports Microsoft.AspNetCore
Imports Microsoft.AspNetCore.Hosting
Imports Microsoft.Extensions.Configuration
Imports Microsoft.Extensions.Logging

Module Program
    Sub Main(args As String())
        Console.WriteLine("Hello ASP.NET MVC and VB World!")
        Dim host = WebHost.CreateDefaultBuilder(args).
            UseStartup(Of Startup)().
            UseUrls("http://*:5000").Build()
        host.Run()
    End Sub
End Module

Startup.vb

Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Threading.Tasks
Imports Microsoft.AspNetCore.Builder
Imports Microsoft.AspNetCore.Hosting
Imports Microsoft.Extensions.Configuration
Imports Microsoft.Extensions.DependencyInjection
Imports Microsoft.Extensions.Logging
Imports Microsoft.Extensions.Options

Public Class Startup
    Public Sub New(configuration As IConfiguration)
        _Configuration = configuration
    End Sub
    Private _Configuration As IConfiguration
    Public ReadOnly Property Configuration As IConfiguration
        Get
            Return _Configuration
        End Get
    End Property
    Public Sub ConfigureServices(services As IServiceCollection)
        services.AddMvc()
    End Sub
    Public Sub Configure(app As IApplicationBuilder, env As IHostingEnvironment)
        If env.IsDevelopment() Then
            app.UseDeveloperExceptionPage()
        End If
        app.UseMvc()
    End Sub
End Class

コントローラクラスを書き替える

ValuesController.vb を作る

Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Threading.Tasks
Imports Microsoft.AspNetCore.Mvc

<Route(&quot;api/[controller]&quot;)>
Public Class ValuesController
    Inherits Controller

    <HttpGet>
    Public Function [Get]() As IEnumerable(Of String)
        Return New String() {&quot;value1&quot;, &quot;value2&quot;}
    End Function

    <HttpGet(&quot;{id}&quot;)>
    Public Function [Get](id As Integer) As String
        Return &quot;value&quot;
    End Function

    <HttpPost>
    Public Sub Post(<FromBody> value As String)

    End Sub

    <HttpPut(&quot;{id}&quot;)>
    Public Sub Put(id As Integer, <FromBody> value As String)

    End Sub

    <HttpDelete(&quot;{id}&quot;)>
    Public Sub Delete(id As Integer)

    End Sub
End Class

HttpGet のところは、Getメソッドが標準とダブるので [Get] のようにしてあるが、GetList とか名前を付けてもよいと思う。

実行する

http://localhost:5000/api/values を実行すると、web api が動いていることが分かる。

Razor は使えるのか?

ASP.NET Core な Web API を VB で作ることができることは解ったのだが、じゃあ MVC の Razor は使えるのかどうか?ってのは未だ試していない。

dotnet new page コマンドを使うと C# の Razor(*.cshtml)を出力してくれるので、このあたりを追加すると VB でも使えるようになるのは?とか。

カテゴリー: 開発, ASP.NET, NET Core | Visual Basic で ASP.NET MVC Core する はコメントを受け付けていません

M5Stack から Slack へ投稿する挑戦中

スイッチサイエンスさんで売り切れだったので、Aliexpress から M5Stackを買いました。ってことで、どうせならば Slack に投稿してみようとして挑戦中

M5Stackでビットコインの価格を表示 – Qiita
https://qiita.com/eggman/items/36d30e01bf64f4a71516
Arduino – ESP32 WiFiClientSecure ライブラリで、安定して https ( SSL )記事をGETする方法 | ページ 3 / 3 | mgo-tec電子工作
https://www.mgo-tec.com/blog-entry-arduino-esp32-ssl-stable-root-ca.html/3

このあたりを参考にして

  • WiFi に接続させる
  • slack.com の証明書をプログラムに埋め込む
  • slack.com に https で POST する

ってことをやります。

下記は、まだ投稿ができていないけど、途中までのコード。

#include <Arduino.h>
#include <WiFi.h>
#include <WiFiMulti.h>
#include <HTTPClient.h>
#include <WiFiClientSecure.h>
#include <M5Stack.h>

WiFiMulti wifi;

#define WIFI_SSID &quot;<WiFiのSSID>&quot;
#define WIFI_PASS &quot;<WiFiのパスワード>&quot;

const char* slack_root_ca= \
&quot;-----BEGIN CERTIFICATE-----\n&quot; \
&quot;MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh\n&quot; \
&quot;MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n&quot; \
&quot;d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\n&quot; \
&quot;QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT\n&quot; \
&quot;MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n&quot; \
&quot;b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG\n&quot; \
&quot;9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB\n&quot; \
&quot;CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97\n&quot; \
&quot;nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt\n&quot; \
&quot;43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P\n&quot; \
&quot;T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4\n&quot; \
&quot;gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO\n&quot; \
&quot;BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR\n&quot; \
&quot;TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw\n&quot; \
&quot;DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr\n&quot; \
&quot;hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg\n&quot; \
&quot;06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF\n&quot; \
&quot;PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls\n&quot; \
&quot;YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk\n&quot; \
&quot;CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=\n&quot; \
&quot;-----END CERTIFICATE-----\n&quot; ;
  
WiFiClientSecure client;

void setup() {

  // put your setup code here, to run once:
  wifi.addAP(WIFI_SSID, WIFI_PASS);
  Serial.begin(115200);
  M5.begin();
  M5.Lcd.println(&quot;send slack&quot;);
  
  while (wifi.run() != WL_CONNECTED) {
    delay(500);
    M5.Lcd.printf(&quot;.&quot;);
  }
  M5.Lcd.println(&quot;wifi connect ok&quot;);
  // post slack
  const char *server = &quot;hooks.slack.com&quot;;
  const char *postdata = &quot;{\&quot;text\&quot;:\&quot;from arduino m5stack message.\&quot;,\&quot;icon_emoji\&quot;:\&quot;:ghost:\&quot;,\&quot;username\&quot;:\&quot;m5stackpost\&quot;}&quot;;


  M5.Lcd.println(&quot;connect slack.com&quot;);
  client.setCACert(slack_root_ca);
  if (!client.connect(server, 443)) {
    M5.Lcd.println(&quot;Connection failed!&quot;);
    return ;
  }
  else 
  {
    M5.Lcd.println(&quot;Connected to server!&quot;);

    // client.println(&quot;POST /services/XXX/XXX/XXX HTTP/1.1&quot;);
    client.println(&quot;POST https://hooks.slack.com/services/XXX/XXX/XXX HTTP/1.0&quot;);
    client.print(&quot;Host: &quot;);
    client.println( server ); 
    client.println(&quot;Content-Type: application/json&quot; );
    char buf[20];
    client.print(&quot;Content-Length: &quot; );
    // client.println(&quot;Connection: close&quot;);
    client.println();
    client.println( postdata ); Serial.println( postdata );
    M5.Lcd.println(&quot;post slack.com&quot;);
  }
}



void loop() {
  // put your main code here, to run repeatedly:

}

WiFiに接続させる

WiFiMultiを使って、addAP でアクセスポイントに接続する。WEP しか繋がらないような気もするので、WEP で用意しておく。

#include <WiFi.h>
#include <WiFiMulti.h>

#define WIFI_SSID &quot;<WiFiのSSID>&quot;
#define WIFI_PASS &quot;<WiFiのパスワード>&quot;

WiFiMulti wifi;
void setup() {

  M5.begin();
  M5.Lcd.println(&quot;send slack&quot;);
  wifi.addAP(WIFI_SSID, WIFI_PASS);
  while (wifi.run() != WL_CONNECTED) {
    delay(500);
    M5.Lcd.printf(&quot;.&quot;);
  }

HTTPSで繋げるための準備

Slackに投稿するのに https が必要なので、相手の証明書をプログラムに埋め込む。

Arduino – ESP32 WiFiClientSecure ライブラリで、安定して https ( SSL )記事をGETする方法 | ページ 2 / 3 | mgo-tec電子工作
https://www.mgo-tec.com/blog-entry-arduino-esp32-ssl-stable-root-ca.html/2

を参考にして、slack.com の証明書を slack_root_ca として保存する。

const char* slack_root_ca= \
"-----BEGIN CERTIFICATE-----\n" \
"MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh\n" \
"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n" \
"d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\n" \
"QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT\n" \
"MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n" \
"b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG\n" \
"9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB\n" \
"CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97\n" \
"nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt\n" \
"43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P\n" \
"T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4\n" \
"gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO\n" \
"BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR\n" \
"TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw\n" \
"DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr\n" \
"hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg\n" \
"06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF\n" \
"PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls\n" \
"YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk\n" \
"CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=\n" \
"-----END CERTIFICATE-----\n" ;
  
WiFiClientSecure client;

  client.setCACert(slack_root_ca);
  if (!client.connect(server, 443)) {
    M5.Lcd.println("Connection failed!");
    return ;
  }
  else 
  {
    M5.Lcd.println("Connected to server!");
  ...
  }

443ポートにWiFiClientSecureで接続すればok. ちなみに証明書が違っている場合は、client.connect がエラーを返す。

SlackへPOSTする。

hook.slack.com へは JSON形式で POST するので、HTTPプロトコルのヘッダを真面目に作る。

void setup() {
  ...

  const char *server = "hooks.slack.com";
  const char *postdata = "{\"text\":\"from arduino m5stack message.\",\"icon_emoji\":\":ghost:\",\"username\":\"m5stackpost\"}";

  M5.Lcd.println("connect slack.com");
  client.setCACert(slack_root_ca);
  if (!client.connect(server, 443)) {
    M5.Lcd.println("Connection failed!");
    return ;
  }
  else 
  {
    M5.Lcd.println("Connected to server!");

    // client.println("POST /services/XXX/XXX/XXX HTTP/1.1");
    client.println("POST /services/XXX/XXX/XXX HTTP/1.0");
    client.print("Host: ");
    client.println( server ); 
    client.println("Content-Type: application/json" );
    char buf[20];
    client.print("Content-Length: " );
    // client.println("Connection: close");
    client.println();
    client.println( postdata ); Serial.println( postdata );
    M5.Lcd.println("post slack.com");
  }
}

POST するときのアドレスは、

C# で Slack に投稿する | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/9117

で作った Incoming WebHooks ものを流用。

で、結果は?

このコードを M5Stack に転送して動かすと、slack.com に繋がって POST しているように見えるのだが…

slack のほうには送られてこないんだよね orz 同じ JSON データを C# で送った場合は大丈夫なので、SSL あたりが違うか、ヘッダ部分が違うのか。

ひとまず、いきなり HTTPS は段階が大変なので、一度 HTTP で繋げられるWEB APIサーバーを作って再開してみる。

カテゴリー: 開発, M5Stack | M5Stack から Slack へ投稿する挑戦中 はコメントを受け付けていません

gmail の SMTP を使ってメールを送信する

クライアントからメールを送信しようとするとき、何処の SMTP を踏み台にして送信しようか悩む。SMTP によって、一度 POP3 しないと駄目だったり、セキュリティ上外部受付していなかったりして、汎用的なモノがない…のだが、結構以前から gmail の smtp.gmail.com が使われていたりする。で、もう一度試しておこうと思って、System.Net.Mail.SmtpClient を使って送っていたのだが、

MailKitが公式に.NETのSmtpClientを置き換えることを明らかにした。
https://www.infoq.com/jp/news/2017/04/MailKit-MimeKit-Official

の問題もあったり、何故か gmail のほうで受け付けなくなったりしていて。

ということで、MailKit で置き換えたものを晒しておきます。

C#でSMTP(STARTTLS と SMTP over SSL)でメール送信する – YoshinoriN’s Memento
https://yoshinorin.net/2016/10/29/csharp-mail-smtps-starttls/

のコードを gmail に変えただけです。

static void Main(string[] args)
{
    Console.WriteLine(&quot;Hello SMTP World!&quot;);

    string id = &quot;<gmailのログインID>&quot;;
    string pass = &quot;<gmailのパスワード>&quot;;
    string from = &quot;<宛先>&quot;;
    string to = &quot;<自分のメール>&quot;;
    string subject = &quot;送信テスト : &quot; + DateTime.Now.ToString();
    string body = &quot;from t.masuda&quot;;

#if false
    var smtp = new System.Net.Mail.SmtpClient();
    smtp.Host = &quot;smtp.gmail.com&quot;; //SMTPサーバ
    smtp.Port = 587;              //SMTPポート
    smtp.EnableSsl = true;
    smtp.Credentials = new System.Net.NetworkCredential(id, pass); //認証
    var msg  = new System.Net.Mail.MailMessage(from, to, subject, body);
    smtp.Send(msg); //メール送信
#else           

    var smtp = new MailKit.Net.Smtp.SmtpClient();
    smtp.Connect(&quot;smtp.gmail.com&quot;, 587, SecureSocketOptions.Auto);
    smtp.Authenticate(id, pass);

    var mail = new MimeKit.MimeMessage();
    var builder = new MimeKit.BodyBuilder();

    mail.From.Add(new MimeKit.MailboxAddress(&quot;&quot;, from));
    mail.To.Add(new MimeKit.MailboxAddress(&quot;&quot;, to));
    mail.Subject = subject;
    builder.TextBody = body + &quot; by gmail&quot;;
    mail.Body = builder.ToMessageBody();

    smtp.Send(mail);
    smtp.Disconnect(true);
#endif

    Console.WriteLine(&quot;メールを送信しました&quot;);
}

NuGet で MailKit をインストールすると、.net standard 対応なので、.net core と .net framework のどちらでもメールを送れるようになります。

カテゴリー: 開発, C# | gmail の SMTP を使ってメールを送信する はコメントを受け付けていません

.NET Coreで作成した Web APIアプリを Windows サービスで動かす

基本は↓に書いてあるのだけど、所々間違っている?というか、バージョンアップあたりで動かなくなっているので、ポイントだけメモ書き。

Windows サービスで ASP.NET Core をホストします。
https://docs.microsoft.com/ja-jp/aspnet/core/host-and-deploy/windows-service?view=aspnetcore-2.0&tabs=aspnetcore2x

https://github.com/aspnet/Docs/tree/master/aspnetcore/host-and-deploy/windows-service/sample

*.csprojでターゲットを.NET Frameworkにする

ASP.NET Core + .NET Core で作成したプロジェクト(*.csproj)を開いて、TargetFramework を「netcoreapp2.0」から「net461」に変える。

  <PropertyGroup>
    <TargetFramework>netcoreapp2.0</TargetFramework>
  </PropertyGroup>

から

  <PropertyGroup>
    <TargetFramework>net461</TargetFramework>
    <RuntimeIdentifier>win7-x64</RuntimeIdentifier>
  </PropertyGroup>

に変更する。

これは Windows サービスを使うために .NET Framework に切り替えているので、Linux上で動かす場合は、netcoreapp2.0 で使うことになる。

NuGetパッケージを変更する

ASP.NET Core を使っているところを、.NET Core から .NET Framework のものに切り替える。あと「Microsoft.AspNetCore.Hosting.WindowsServices」を追加して、Windows サービスで起動するための RunAsService() を使えるようにしておく。

  <ItemGroup>
    <PackageReference Include=&quot;Microsoft.AspNetCore.All&quot; Version=&quot;2.0.8&quot; />
  </ItemGroup>

から

  <ItemGroup>
    <PackageReference Include=&quot;Microsoft.AspNetCore&quot; Version=&quot;2.0.3&quot; />
    <PackageReference Include=&quot;Microsoft.AspNetCore.Mvc&quot; Version=&quot;2.0.4&quot; />
    <PackageReference Include=&quot;Microsoft.AspNetCore.Hosting.WindowsServices&quot; Version=&quot;2.0.3&quot; />
  </ItemGroup>

Program.csを書き替える

ASP.NET Core + .NET Core の場合には、IWebHost インターフェースを使っているのだが、面倒なので、Main に書き替えてしまう。

public class Program
{
    public static void Main(string[] args)
    {
        BuildWebHost(args).Run();
    }

    public static IWebHost BuildWebHost(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>()
            .Build();
}

から

using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.WindowsServices;

public class Program
{
    public static void Main(string[] args)
    {
        bool isService = false;
        if (Debugger.IsAttached || args.Contains(&quot;--console&quot;))
        {
            isService = false;
        }
        if ( args.Contains(&quot;--service&quot;))
        {
            isService = true;
        }
        // パラメータを消しておく
        args = new string[] { };

        if (isService)
        {
            // サービスで動作させる
            var pathToExe = Process.GetCurrentProcess().MainModule.FileName;
            var pathToContentRoot = Path.GetDirectoryName(pathToExe);
            var host =
                WebHost.CreateDefaultBuilder(args)
                .UseContentRoot(pathToContentRoot)
                .UseStartup<Startup>()
                .Build();
            host.RunAsService();
        }
        else
        {
            // コンソールアプリで起動する
            var host = 
                WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>()
                .UseUrls($&quot;http://*:{port}&quot;)
                .Build();
            host.Run();
        }
    }
}

起動スイッチを付けて通常起動とサービスの起動に切り替えている。MSのサンプルでは、デフォルトでサービス起動になっているのだが、こちらは「–service」を付けたときだけ、Windows サービスで起動するようにしている。

WebHost.CreateDefaultBuilder に不要なパラメータを渡すと落ちるので、args の中身を消しておく(なんとなくバグ臭いのだが)。

サービスで起動する

サービスを登録するために sc コマンドを管理者権限のコマンドプロンプトで実行する。

sc create UsbService binpath="D:\work\SG\git\usbblocker\UsbBlocker.Web\bin\Debug\net461\win7-x64\UsbBlocker.Web.exe --service"

binpath には、フルパスを指定する。スイッチを付けるためにダブルクォートで囲ってある。サービス名は UsbService としているが、ダブらないように自前で名前をつける。

サービスの起動は sc start でできる。

sb start UsbBlockerService

サービスを削除するときは sc delete する。

sb delete UsbBlockerService
カテゴリー: 開発, C# | .NET Coreで作成した Web APIアプリを Windows サービスで動かす はコメントを受け付けていません

OpenPop.NET を Xamarin.Forms で使う

常々、スパムメールが沢山あってスマホでメールが受け取れないのにイライラしている訳だが。スマホで閲覧するときは、全てを受け取りたいのではなくて重要そうな(最初に決められた相手とか)メールだけ見たいので、スパムもそうだけど広告メールも外したかったりする。いわゆる、自分なりのホワイトリストが作っておきたい。

つまりは自前のメーラーアプリがあればいいのだけど、(確か)iOSの場合は規約上作れないのでストアには出て来ない。
ならば、作ればいいだろう、ということ。

OpenPop.NET

簡単なPOP3の実装ならばそれほど難しくはないのだけど、できるならば既に実績があるものがよい。

OpenPop.NET – Robust POP3 client and MIME parser written in C#
http://hpop.sourceforge.net/

どうやら .NET Framework 2.0 の頃からある古い実装なのだけど、サンプルもあるのでそのまま使えそう。

ってなわけで、.NET Standard 2.0 でビルドし直してみる、と

moonmile/hpop: OpenPOP.NET code repository
https://github.com/moonmile/hpop

あっさりできてしまったので、これを Xamarin.Forms に組み込む。

Xamarin.Forms+OpenPop.NET

using OpenPop.Pop3;
using System.Net.Mail;

public partial class MainPage : ContentPage
{
	public MainPage()
	{
		InitializeComponent();
        this.LayoutChanged += MainPage_LayoutChanged;
	}

    private void MainPage_LayoutChanged(object sender, EventArgs e)
    {
        var client = new Pop3Client();
        client.Connect("ホスト名", 110, false);
        client.Authenticate(
            "ユーザー名", 
            "パスワード", 
            AuthenticationMethod.UsernameAndPassword);
        int messageCount = client.GetMessageCount();

        var items = new List<MailMessage>();
        int cnt = 0;
        for ( int i = messageCount; i>0; i--  )
        {
            /// 30 件で区切る
            if (cnt++ >= 30) break;

            try
            {
                var msg = client.GetMessage(i);
                if (msg != null)
                    items.Add(msg.ToMailMessage());
            }
            catch { }
        }
        this.lv.ItemsSource = items;

    }
}

途中で try-catch しているのは、Form のコード変換あたりでエラーになっているから。デコードがうまくいかないようなので、ToMailMessageメソッドの実装は自前で行ったほうがよいかもしれない(日本語のコード変換だからだろう)。

ひとまず、ListView にバインドさせるとこんな感じになる。

どうやって使うか?

ちゃんとしたメーラーアプリ標準のものを使う…というか標準のしか使えないので、まるっきり自作アプリとして使う。スマホのメーラーは振り分けが貧弱なので、そのあたりを補えばいい。

  • From を見て、特定の人だけを表示させる
  • Subject に特定の文字がでてきたもののみ表示させる。

な感じでいいだろう。返信するときはコピペしてから標準アプリに受け渡せばいい。

カテゴリー: 開発, C#, Xamarin | OpenPop.NET を Xamarin.Forms で使う はコメントを受け付けていません

ADO.NET Entitiy Data Model を F# で使うための裏技

MySQLやSQLiteでEFが使えるならば、F#からもEFを使えるだろう、ということで探していたのだけど、

F# で Entity Framework Coreる – pocketberserkerの爆走
http://pocketberserker.hatenablog.com/entry/2017/12/06/000405

確かに、Entity Classを生成するコードが F# にはない…というか T4 自体が C# だけしか使えないようになっているので、F# では手作業で Model クラスを作らねばいけないっぽい。
のだが、

EFのクラスをMVVMのINotifyPropertyChangedに対応させる裏技 | Moonmile Solutions Blog http://www.moonmile.net/blog/archives/9030

で試したように T4 のファイル(*.tt)を直接書き替えることによって、生成するコードを自前で作ることができるんですよ。となると、ここで C# の Model クラスを出力している部分を、F# に書き替えればよい。

  1. C# のプロジェクトに ADO.NET Entitiy Data Model を追加
  2. *.tt を F# 用に書き替える。
  3. 生成すると *.fs ファイルができる。
  4. 生成した F# のファイルを、F# のプロジェクトにコピーして使う

という手順でいけるだろう、ってことで、作ってみたのがこれ。

http://github.com/moonmile/sample-fsharp-ef/

C# プロジェクトの Model.tt と Model1.Context.tt を F# コードを出力するように書き替える。本来ならば、F# プロジェクトで TextTemplatingFileGenerator が使えればよいのだけど駄目なので、仕方がないので生成後のコード(Model1.Context.fs, projects.fs, issues.fs)を F# プロジェクトにリンクする。

まずは、普通にC#プロジェクトでADO.NET Entitiy Data Modelを追加する

C#プロジェクトを作って、MySQLのprojectsテーブルとissuesテーブルのエンティティクラスを作る

ここで、使われている T4のファイル(Model1.Context.tt, Model1.tt)を書き替える

Model1.Context.tt, Model1.ttを書き替えてF#のコードを出力

Model1.tt

https://github.com/moonmile/sample-fsharp-ef/blob/master/src/SampleFSharpEF.FSharp/SampleFSharpEF.Model/Model1.tt

ちょっと乱暴だが、CodeStringGenerator::PropertyとEntityClassOpeningを書き替えて、F#のレコード型が出力されるようにする。

public class CodeStringGenerator
{
	...
    public string Property(EdmProperty edmProperty)
    {
        return string.Format(
            CultureInfo.InvariantCulture,
            // &quot;{0} {1} {2} {{ {3}get; {4}set; }}&quot;,
            &quot;{2} : {1} &quot;,
            Accessibility.ForProperty(edmProperty),
            _typeMapper.GetTypeName(edmProperty.TypeUsage),
            _code.Escape(edmProperty),
            _code.SpaceAfter(Accessibility.ForGetter(edmProperty)),
            _code.SpaceAfter(Accessibility.ForSetter(edmProperty)));
    }
	...
    public string EntityClassOpening(EntityType entity)
    {
        return string.Format(
            CultureInfo.InvariantCulture,
			&quot;[<CLIMutable>]&quot; + Environment.NewLine +
            &quot;type {2}{3} =&quot;,
            Accessibility.ForType(entity),
            _code.SpaceAfter(_code.AbstractOption(entity)),
            _code.Escape(entity),
            _code.StringBefore(&quot; : &quot;, _typeMapper.GetTypeName(entity.BaseType)));
    }

Model1.Context.tt

https://github.com/moonmile/sample-fsharp-ef/blob/master/src/SampleFSharpEF.FSharp/SampleFSharpEF.Model/Model1.Context.tt

DbContext を継承するクラスを作成する部分を書き替えてしまう。

type <#=code.Escape(container)#>() = 
    inherit DbContext()
    override x.OnConfiguring( optionsBuilder: DbContextOptionsBuilder ) =
        base.OnConfiguring(optionsBuilder)
        optionsBuilder.UseSqlServer(&quot;connection-string&quot;) |> ignore

<#
    foreach (var entitySet in container.BaseEntitySets.OfType<EntitySet>())
    {
#>
    <#=codeStringGenerator.DbSet(entitySet)#>
<#
    }

    foreach (var edmFunction in container.FunctionImports)
    {
        WriteFunctionImport(typeMapper, codeStringGenerator, edmFunction, modelNamespace, includeMergeOption: false);
    }
#>

これらの *.tt ファイルは保存すると即実行されるので、試行錯誤しながらできる。コンバートされる F# コードを見てちまちまと *.tt ファイルを変更する。

そうすると、こんなコードが出力される。

namespace SampleFSharpEF.Model

    open System
    open System.Collections.Generic
    
    [<CLIMutable>]
    type issues = {
        id : int 
        tracker_id : int 
        project_id : int 
        subject : string 
        description : string 
        due_date : Nullable<System.DateTime> 
        category_id : Nullable<int> 
        status_id : int 
        assigned_to_id : Nullable<int> 
        priority_id : int 
        fixed_version_id : Nullable<int> 
        author_id : int 
        lock_version : int 
        created_on : Nullable<System.DateTime> 
        updated_on : Nullable<System.DateTime> 
        start_date : Nullable<System.DateTime> 
        done_ratio : int 
        estimated_hours : Nullable<float> 
        parent_id : Nullable<int> 
        root_id : Nullable<int> 
        lft : Nullable<int> 
        rgt : Nullable<int> 
        is_private : bool 
        closed_on : Nullable<System.DateTime> 
    }

DbContext を継承した redmineEntities クラス

namespace SampleFSharpEF.Model

open System
open Microsoft.EntityFrameworkCore

type redmineEntities() = 
    inherit DbContext()

    override x.OnConfiguring( optionsBuilder: DbContextOptionsBuilder ) =
        base.OnConfiguring(optionsBuilder)
        optionsBuilder.UseMySQL(&quot;connection-string&quot;) |> ignore

    [<DefaultValue>] val mutable _issues : DbSet<issues>
    member this.issues with get() = this._issues and set v = this._issues <- v
    [<DefaultValue>] val mutable _projects : DbSet<projects>
    member this.projects with get() = this._projects and set v = this._projects <- v

F# のほうは、MySQLとMicrosoft.EntityFrameworkCoreを使うので、OnConfiguringメソッドをオーバーライドして接続文字列を指定する形にしている。

F#プロジェクトにエンティティクラスを含める

F#のプロジェクトにリンクでエンティティクラスを含めておく。F#の場合、ファイルが前方参照にする必要があるので、順番を調節する。

ここでは、.NET Core な F# プロジェクトを作って試している。

エンティティクラスを使うコードを書く

手作業になると100行以上書かないといけないエンティティクラスを、T4 で吐き出すようにしたので、使うのは結構楽になる。


open System
open System.Linq
open SampleFSharpEF.Model


[<EntryPoint>]
let main argv =
    printfn &quot;Hello World from F#!&quot;

    let ent = new redmineEntities()
    for it in ent.projects.ToList() do
        Console.WriteLine(String.Format(&quot;{0} : {1}&quot;, it.id, it.name ))

    0 // return an integer exit code

NuGet で MySql.Data.EntityFrameworkCore をインストールした後に、普通にデータベースにアクセスコードを書けばいい。

dotnet run するとこんな感じになる。

C#から使ってみる

試しに、F#で出力されたエンティティクラスをC#で使ってみる。
コマンドライン版の SampleFSharpEF.FSharp プロジェクトを参照設定して、SampleFSharpEF.Modelの中が使えるようにしていまう。本来ならば、別途F#のモデルプロジェクトを用意すると思う。

using System;
using System.Linq;
using SampleFSharpEF.Model;


namespace SampleFSharpEF.CSharp
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello C# World!");
            var ent = new redmineEntities();
            foreach ( var it in ent.projects.ToList())
            {
                Console.WriteLine(String.Format("{0} : {1}", it.id, it.name));
            }
        }
    }
}

結果はF#と同じになる。

以前は、C#プロジェクトからF#プロジェクトを参照する場合、FSharp.Core を参照設定する必要があった(PCLの関係だと思う)のだが、.NET Standard化されているのか、.NET Coreなプロジェクトだと FSharp.Core への参照設定はいらない。

サンプルコード

http://github.com/moonmile/sample-fsharp-ef/

おまけ

F#では動かないT4なんだけど、C#プロジェクトでF#コードを出力するという裏技を使うと、なんとか使えるようになる。とは言え、いちいちC#プロジェクトを作るのもアレなんだが。

Templatus/README.md at master ・ kerams/Templatus
https://github.com/kerams/Templatus/blob/master/README.md

F#でT4みたいなのが動く Templatus というのがあるので、これを使ってみるとよいかもしれん…が、Model1.tt の中身を全て F# で書き替えなければいかんというのが結構な手間のような気もする。要はエンティティクラスのマイグレーションがやりたいだけなので、適当に MySQLやSQLiteに接続してテーブル構造を引っ張り出して、コードを出力するだけでいいんだが。コードファーストのマイグレーションのような積み重ね方式じゃなくて、単純なスナップショット版でよいし(データは消えるので、実際にあれこれやる場合は、積み重ね方式が必須になるだけど)。

カテゴリー: 開発, F# | ADO.NET Entitiy Data Model を F# で使うための裏技 はコメントを受け付けていません