Life is Really Short, Have Your Life!!

ござ先輩の主に技術的なメモ

Prism8のContainerLocatorへの対応

日本でWPFのPrismやってるの100人いるかどうかじゃないかな。なんでWindowsのデスクトップアプリなんか作ったんだろう。

Prism8が2020年の10月頃にリリースされ、破壊的変更が入った。ServiceLocatorがなくなってContainerLocatorに変わりました。それだけ言われてもどこを変えていいかわかんねーよって感じですけど、この2点を変えたら動いたので、共有。

RegisterTypes

Locatorなるものが管理するViewが一括で登録されるのがRegisterTypes。そういうものらしい。 Prism8になってIContainerRegistryからGetContainerメソッドがなくなったので、削除するだけでビルドが通った。

        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
            this.GetType().GetTypeInfo().Assembly
             .DefinedTypes
             .Where(t => t.Namespace?.EndsWith(".Views", System.StringComparison.Ordinal) ?? false)
             .ToList().ForEach(t => {
                //containerRegistry.GetContainer().RegisterTypeForNavigation(t.AsType(), t.Name);
                 containerRegistry.RegisterForNavigation(t.AsType(), t.Name);
             });
       }

RegionManager

ビルドは通ったけど、実行時例外(ぬるぽ)は別問題だよね。

Prismで画面遷移する時、GetRegionManager().RequestNavigate("ContentPage", nameof(HogePage));って書いている。ContentPageMainWindow.xamlに書いたこのコードに呼応してる。RegionNameだね。こうすることで、ヘッダー・フッターは固定でコンテンツだけ差し替えることが簡単にできる。便利まーん。

        <ContentControl prism:RegionManager.RegionName="ContentPage"  />

GetRegionManager()ではIRegionManagerインスタンスServiceLocatorから取得していたが、Prism8になってこのクラスのインスタンスはDIされなくなった。なので、こういうコードを書いたら動いた。

 protected IRegionManager GetRegionManager()
        {
            return ContainerLocator.Container.Resolve<IRegionManager>();
            //return ServiceLocator.Current.GetInstance<IRegionManager>();
        }

VIewModelの初期化時にRegionManagerを渡すことも出来るっぽい。IDialogServiceを使う時に、ViewModelのコンストラクタにインスタンスをDIするのと同じイメージ。以下のリンクは、IDialogServiceの使い方。Prism8になると、InteractionRequestは使えない。気をつけて。

prismlibrary.com

クライアントサイドのプログラム、なんとな〜く文脈をつかんでノリで書いたら動くことが多い。あはは。

Pycharmで"Couldn't refresh skeletons for remote interpreter"が出る

MacのDockerの設定の問題だった。

f:id:gothedistance:20210723230018p:plain

ここにUser Docker Compose v2とある。このチェックを外したらエラーが出なくなった。

$  docker --version
Docker version 20.10.7, build f0df350
$ docker-compose --version
Docker Compose version v2.0.0-beta.6 # suck
$ docker-compose --version
docker-compose version 1.29.2, build 5becea4c # good.

やっておいたほうが良さそうなこと

  • PycharmのInvalidate Cache and Restart
  • Pycharmのヘルパーコンテナ及びイメージの削除
  • 以前作ったPython Interpreterの設定の削除

こんだけやれば動きました。4連休最大の懸念が解消されてちょーうれしい。

HerokuでRoute53で独自ドメイン(サブドメイン)運用

秒で終わったのでメモ。

  • ドメインを追加する。hoge.goza.comとする。
  • 追加後、goza53.herokudns.comみたいなドメインが 発行される。
  • hoge.goza.com のCNAMEに上記のドメインをあてるだけ。
  • あとはHeroku側のACMで色々やってくれて、SSLも作ってくれる。

1分で終わりました。

heroku.ymlでDockerイメージをデプロイするメモ

Heroku簡単じゃん。ビビるわ。

AWSのALB+Fargate+Auroraでちょーモダンな環境作るぜって息巻いていた。ただ、やっぱりAWSはインフラ構築の手順が色々あって(VPCだるい)、AWS力が低い私には一抹の不安があった。

Herokuだと「Dyno+JAWSDB」で、ALB+Fargate+Auroraと似たようなことができる。ホストOSの管理が要らないし、SSH開けなくて良い。ちょっとぐらいダウンしてもいいよインフラなら、$17で構築できる。やっす。アメリカにあるから多少レイテンシーが気になるけど、小規模なWebアプリならこれで充分だと判断。

herokuらしくgit push heroku master でDockerイメージをビルドしてPUSHしてリリースまで一括で可能になっていた。それよ。

そのために必要なのが、heroku.ymlというファイル。こいつを作って、gitに追加しておくのだ。

heroku.yml の中身

build:
  docker:
    web: Dockerfile.web
run:
  web: gunicorn -w2 -b 0.0.0.0:$PORT -t 60 run:app

docker-compose.yamlに慣れてれば秒じゃん。

DBの接続情報やAPI_KEYのような機密情報は、全部HerokuのConfig-Varsに寄せた。

heroku.yamlではConfig-Varsの値を参照できない。参照できるのはDockerFileに宣言した変数ENVとかARGだけ。環境変数は、GitのようなレポジトリにPUSHするとまずい値はConfig-Vars、別に問題ないものはDockerFileにセットするのがお作法かな。

上記で$PORTを宣言しているけど、Herokuの仕様でDynoのポート番号がデプロイ時に決まり、それが環境変数を通して渡される格好になるので、このようになっています。

heroku.yml以前のデプロイ方法との違いが、下記に詳しい。

masutaka.net

バッチ処理も秒で出来た

Heroku Schedulerを使って、秒でできた。python3 hoge.pyで完了。これでコンテナにあるhoge.pyを実行してくれる。そう、そういうの。最高。

今後のタスク

  • Heroku PipeLineでCI/CDとステージング環境の作り方
  • ログの永続化
  • httpからhttpsへのリダイレクト

Heroku簡単じゃん。もっと早く覚えておけばよかったわ。

PC-FAXで初期値に宛先を与えたい

この質問と全く同じ。

detail.chiebukuro.yahoo.co.jp

富士ゼロックスの複合機はパソコンからダイレクトにFAXを送信する機能がありますが、当然ながらFAX番号を指定しなければなりません。
現在つくっている業務アプリでは、FAX送付先をデータとして保有しているので、その番号を使って宛先指定を自動で行い、送信も自動としたいのです

FAXドライバーを入れると、Windowsのプリンタのアイコンみたいのが1個できる。そいつに印刷指示を与えると、こういう画面が出る。

https://www.fujifilm.com/fb/support/mf/dc4_c2260/images/1810_g010.jpg

宛先を与えて送信開始を押すと、FAXが複合機経由で送信される。 印刷データを与えることは余裕でできる。C#でFAXドライバーに対して印字命令を書くだけ。普通の印刷プログラムと全く同じ。

FAXモデムがある前提のコードが多くて辛い

FAXモデムをWinows10にインストールした状態のコードばっかり見つかる。こんな感じ。これは自分のマシンもしくはWindowsのマシンに入ってるFAXモデムに対する送信命令であって、PC-FAX経由ではない。

        public static void SendFaxV3(string printer, string _documentName, string _fileName, string _recipientName, string _faxNumber)
        {
            if (_faxNumber != "")
            {
                FAXCOMEXLib.FaxServer faxServer = new FAXCOMEXLib.FaxServer();
                faxServer.Connect(printer);
                FAXCOMEXLib.FaxDocument faxdoc = new FAXCOMEXLib.FaxDocumentClass();
                faxdoc.Body = _fileName;
                faxdoc.DocumentName = _documentName;
                faxdoc.Priority = FAXCOMEXLib.FAX_PRIORITY_TYPE_ENUM.fptNORMAL;
                faxdoc.Body = _fileName;
                faxdoc.Subject = _documentName;
                faxdoc.DocumentName = _documentName;
                faxdoc.Recipients.Add(_faxNumber, _recipientName);
                faxdoc.ConnectedSubmit(faxServer);
            }
        }

そういうことするにはね、ライブラリを買わないとダメなんですよって話なのかな〜。

男ならWindows32APIだろ

印刷命令を出したあと、ウインドウハンドルで、最前列にいるはずであろうウインドウを捕まえ、フォーカスを当て、TABで移動してSendMessageするという荒業が出来る可能性がちょっとある。

オチもないけど、再燃する可能性があるのでメモ。

AppStore Reviewガイドラインで、アカウント削除機能がマストになった件

qiita.com

If your app supports account creation, you must also offer account deletion within the app.

今後新規に作るアプリが対象になるのかなぁ。5年前に作ったアプリはログイン前提でアカウント登録の動線すらないけど、昨年アップデート申請したら通ったという経験があるため。Androidはノータッチでいいんだけど、iOSは実にうるさい。アカウントに紐付けないサービスなんて、ほとんどないと思うんですけど。まぁ、多分、削除への動線だけ付けたら問題ないやつだとは思いますが。

今ウチが運営しているサービスは、BtoBの受発注アプリ。カタログPDFをアップするとアプリに画像が反映されて、商品データと紐付けて発注ができるよっていうやつ。面倒なのが、アプリなのに完全クローズドな世界であること。アプリのインストールは誰でも出来るよ〜でも使える人はこのサービスを契約している会社が許可したユーザーしかダメだよ〜っていうモデルになっているので、Appleからするとわかりにくい。

ログイン前提でアプリを作ってしまうと、特定の企業向けのアプリなら別のライセンス使えよカスって言われるのよね。なので、必ずアカウント登録の導線が必要になって文句言われた。MetaData Rejected.を2回食らってる。説明したら通ったけど。

アプリの良い点は、だいたいこんな感じ。

  • アイコンがスマホのスクリーンにデフォルトで置ける事
  • プッシュ通知が出来ること
  • カメラの制御ができること(写真アップやバーコードのスキャン)
  • バッジが使えること
  • ローカルにデータを溜め込んで置けること
  • (作り方次第になっているけど)ブラウザに比べると、サクサク動くこと

アプリが概ね完成したので、Nuxtでブラウザベースのクライアントを作り始めている。PWAにする予定。

PWAで(正確にはPWA for iOS)で使えないのが、バッジとPUSH通知。PUSH通知はSlack/LINE通知などに委託すればできるので、あんまり困らない気がする。バッジは、NPMのパッケージがあったりするので、それでいけるのかな。

PWAをなんで作ろうかと思ったかというと、PCユーザーが一定数いるのと、アプリ並みにサクサク動くものが作れるなら運用保守がアプリより楽だから。審査も要らない・課金も自由。AppStoreのガイドラインのせいで、万が一アプリの更新や継続が難しくなったらどうするかという心配が、3.34%ほどある。

アプリで難しかったのが、スクロールビューにおけるピンチイン・ピンチアウト。これがFlutterですごい苦戦した。iOSの写真アプリは、スクロールをサポートしつつも、ピンチイン・ピンチアウトでグリッドの列数が動的に変化する。これが絶妙な制御によって成り立っている。Flutterでどこまでやれるか再現したくて1日費やしたけど、全然ダメでした。 ブラウザだと余裕でピンチイン・ピンチアウトが拾いやすく、iOSの写真アプリっぽいUXを提供できる気がしている。

ちなみに、Gridviewのセル内画像読み込みも結構辛い。GridViewに100枚ぐらい画像を載せてWeb経由で読み込ませると、かなり遅かった。1MBの画像とか絶対に出してはならない。画像については、素直にCloudinaryを使ってある程度画質を犠牲にして軽くした。カタログ画像なので、あんまり横幅が小さいとゴミに見えるのが辛いけど、デバイスを縦に持ったときの横幅ぐらいに収めると、20KBぐらいまで落とせた。

NuxtでPWAを覚えてリリースできれば、クローズドベータは終わりになるぜ。

ChromeDriverでtype="date"の扱いに注意が必要だった

これはちょっとハマった。

thinkami.hatenablog.com

input type="date" を指定したHTMLは、Chromeの場合、以下のように年、月、日で各々別々のフォーカスを持ってしまうため、clear()とかやっても、全然消えない。

f:id:gothedistance:20210222171346p:plain

上記のエントリはC#だったが、PythonSeleniumだとJavaScriptを実行できるので、これが一番楽だった。

self.driver.execute_script('document.getElementsByName("start")[0].value="";' )

セレクタはよしなに。