React Router の Link と Material-UI の ListItem を組み合わせる

Material-UI と React Router を使う SPA で、必ずと言って良いほど書いているので、いい加減メモしておく。

import React from 'react';
import {
  NavLink as RouterLink,
  NavLinkProps as RouterLinkProps,
  useLocation,
} from 'react-router-dom';
import { Omit } from '@material-ui/types';
import {
  ListItem,
  ListItemIcon,
  ListItemText,
} from '@material-ui/core';

export interface ListItemLinkProps {
  icon?: React.ReactElement;
  primary: string;
  to: string;
}

export function ListItemLink(props: ListItemLinkProps) {
  const { icon, primary, to } = props;
  const location = useLocation();
  const selected = location.pathname.startsWith(to);

  const renderLink = React.useMemo(
    () => React.forwardRef<HTMLAnchorElement, Omit<RouterLinkProps, 'innerRef' | 'to'>>(
      (itemProps, ref) => (
        <RouterLink to={to} {...itemProps} innerRef={ref} />
      ),
    ),
    [to],
  );

  return (
    <li>
      <ListItem
        button
        selected={selected}
        component={renderLink}
      >
        {icon ? <ListItemIcon>{icon}</ListItemIcon> : null}
        <ListItemText primary={primary} />
      </ListItem>
    </li>
  );
}

アルスラーン戦記(14)

原作読んでないけど、ギーヴがこんなに早く復帰するとは思わなかったな。エクバターナに大分迫ったあたりで復帰すると予想していたのに。アルスラーンのピンチを救う形で登場したので、自分の名誉を自分で回復してしまった。

トゥラーンとの戦は、ナルサスの策略がさえ渡って、もはや独壇場っていう感じ。相手に軍の機密を漏洩したと思わせといて、実はそれすらもナルサスの策とはね。ナルサスの掌の上で転がし、トゥラーン軍を同士討ちさせ、結果的にパルス軍の圧勝に終わった。

次はいよいよ、エクバターナに向かって再び進軍か。そろそろアンドラゴラスが合流しそうな予告はあったけど、どうなることやら。パルス軍のほとんどはアンドラゴラスが本来の主君なわけで、アルスラーンよりもアンドラゴラスに従わざるを得ないだろうな。ナルサスダリューンアルスラーン側にに着くだろうけど。軍が2つに分かれてしまうのかねぇ。

あと、「アルスラーンは有能な臣下にとっての良い止まり木」というナルサスの考えは、自分がマネージャーになるときに思い出したい。まぁ、なることはないだろうけど。

Nintendo Switch

だいぶ前になってしまうけど、Nintendo Switch を購入した。欲しいと思ったのはお盆休みぐらいだったかな。新型コロナが流行して、思うように外出できなくなり、家でやることがなくて困った。夏だったので外で遊ぶには暑過ぎて熱中症が心配だし。家で遊ぶものが欲しいと思ったので、購入を決意した。

ちょうどその頃にはヨドバシやビックカメラが抽選販売に切り替わっていたので、ダメ元で申し込んでは落選。それを何度か繰り返し、3回目か4回目、ソフマップの抽選に運良く当選して、購入することができた。

ゲーム機は PS2 から止まっていて、PS3PS4 は買っていないので、もう10年ぶりくらいになるだろうか。久しぶりの家庭用ゲーム機だ。卒業したわけではないんだけど、自然とやらなくなっていた。また買うことになるとはね。

ソフトは、絶対買おうと思っていたのが『リングフィットアドベンチャー』と『ゼルダの伝説ブレスオブザワイルド』。特にゼルダの伝説は巷の評判がもの凄く良いみたいで、いつかやってみたいと思っていた。リングフィットアドベンチャーは、運動不足なのでダイエットがてら、筋肉をつけるために購入。

ゼルダの伝説 ブレス オブ ザ ワイルド - Switch

ゼルダの伝説 ブレス オブ ザ ワイルド - Switch

  • 発売日: 2017/03/03
  • メディア: Video Game

リングフィット アドベンチャー -Switch

リングフィット アドベンチャー -Switch

  • 発売日: 2019/10/18
  • メディア: Video Game

ゼルダの伝説はダウンロード版をサクッと買えた。一方で、リングフィットアドベンチャーは専用コントローラー必要なので、Amazon やヨドバシとかで買う必要がある。これがなかなか買えなくて、しかもタイミング悪く PS5 の抽選販売が始まってしまい、リングフィットアドベンチャーの抽選販売をまったくやってくれなくて困った。Amazon が毎週火曜日に在庫復活していたので、何度かチャレンジして、これまた3度目か4度目の正直で買うことができた。

まずはゼルダの伝説をクリアし、その後リングフィットアドベンチャーをやることにしている。ちょうど寒くなってきたし、これから筋トレ日和。リングフィットアドベンチャーは筋トレなので、1日おきくらいでやるのがいいんだろうか。クリアよりも続けることが大事なので、筋肉と相談しながら進めるとしよう。

UQ mobile

今まで au ガラケーの一番安いプランと、mineo データのみ 3GB のプランの2 台持ちでずっとやってきたけど、UQ mobile に移行して一本化した。ガラケーはあと何年かで使えなくなるし、 妻が携帯料金高いから格安SIMに乗り換えると言っていたので、ついでに自分も乗り換えた。

www.uqwimax.jp

格安 SIM を色々調べて、移行先候補には UQ mobile の他に、IIJ楽天モバイルなんかが挙がっていた。移行するにあたっての最低条件としては、データ通信で月に 3GB のプランは必須。1GB だと到底足りない。2GB だと心許ない。3GB は最低でも欲しい。そして、3GB 使い切ってしまった場合に、超過分を自動的に課金ではなくて、遅くて良いから通信はできてほしい。あとは、今の利用金額以下も必須。それらの条件に合致したのが UQ mobile だった。

タイミングよくヤマダ電機UQ mobile 乗り換えキャンペーンをやっていて、端末購入なしだったら2ヶ月後に店頭で 10000 円キャッシュバックされるようだった。UQ mobile に入るのに3300 円、MNP に 3000 円くらい、au やめるのにも 3000 円くらい、合計で 10000 円近い移行費用がかかるけど、それが賄えそうなのでお財布的に助かった。

今まで 2 台ポケットに入れて持ち歩いていたのが 1 台に減って、だいぶ違和感あるけど、持ち物が減るのはメリット。通信速度は体感的に mineo と遜色ない。移行前は月 2300 円ちょっとだったのが月 2100 円くらいになったので、200 円ほど安くなったことになる。au 本体がそれくらい安くなればよかったけど、最近のニュースを見た感じだと、あまり期待できそうにないな。

UQ mobile の家族割は 2 台目から料金割引になるだけで、au の家族割にあった家族間通話無料と家族間 SMS 無料が無くなったのが痛い。LINE でやりとりするしかないか。メッセージだけで 3GB 使うことはおそらくないはず。使い切った場合でも mineo のときは TwitterFeedly といったアプリでテキストを見るぶんには支障なかったんで、月が変わるまでは耐えられるはず。

docomo の ahamo が話題だけど、20GB も使わないし、1980 円ぐらいの価格帯のプランが出たら考えるかな。

一成一代

ここ最近、醤油とか塩が続いていたので、豚骨臭いラーメンが食べたいなと思い、東比恵駅出てすぐにある『一成一代』に行ってきた。先週東比恵来たとき、改札出たやたら豚骨の良い匂いさせていて、その匂いで久しぶりに豚骨が食べたくなってしまったもので。

f:id:griefworker:20201129224025j:plain

ラーメンはもちろん食べるけど、餃子もチャーハンも食べてみたかったので、全部食べられる『一成一代セット』を注文。ラーメンは泡系でクリーミー。ベースのタレがちょっと甘めかな、濃厚で結構好みな感じのラーメンだった。同じ泡系でも、一双や一幸舎よりも甘めに感じた。豚骨くさいだけよりは、甘さがあるほうが助かるので、泡系の中でも比較的自分好みのスープ。

f:id:griefworker:20201129224035j:plain

餃子は底がカリッと焼かれていて、それに対して皮や具はフワッとしていて、なかなかの美味さ。お酒が欲しくなるくらいに。レモンサワー飲みながら食べたいなと思った。

f:id:griefworker:20201129224045j:plain

チャーハンは、調理風景を見ていると、あらかめ仕込みの段階で味付けしておいたものを、中華鍋で焼く感じっぽかった。味自体は良かったんだけど、ちょっと炒め足りない気が。チャーハンを食べている感じではなかったな。もうちょっと炒めたら、もっと美味しく仕上がったと思うんで、そこだけが残念ポイント。

f:id:griefworker:20201129224059j:plain

ラーメン自体630円、セットで1080円と、昨今のラーメンの高価格化からすれば比較的良心的。なかなか満足感あった。白めしは100円で、おかわり自由のようだし、近くの東福岡高校の男子生徒あたりがたくさん常連になっていそうだな。

博多 一成一代
〒812-0007 福岡県福岡市博多区東比恵2-17-23 ローズマンション第一博多
1,000円(平均)1,000円(ランチ平均)
r.gnavi.co.jp

Azure SQL Database に接続する WCF サービスを Windows コンテナのプロセス分離モードで動かす実験

はじめに

最近、自分の中で Docker 熱、というか Windows コンテナ熱が再燃。Windows コンテナのプロセス分離モードで WCF サービスをホストするのと、Windows コンテナの中から Azure SQL Database に繋ぐところまでは、以前実験した。

tnakamura.hatenablog.com

tnakamura.hatenablog.com

今度はこれらを合体。Azure SQL Database に接続する WCF サービスを、Windows コンテナのプロセス分離モードで動かしてみる。

WCF サービス

Azure SQL Database に接続して、バージョンを取得する SQL を実行するだけの簡単な WCF サービスを作成。接続文字列は環境変数で渡せるようにしておく。

using System;
using System.Data.SqlClient;
using System.ServiceModel;
using System.Threading;

namespace WcfWCOW
{
    class Program
    {
        static void Main(string[] args)
        {
            if (string.IsNullOrEmpty(TestService.ConnectionString))
            {
                Console.WriteLine("環境変数 CONNECTION_STRING を設定してください。");
                return;
            }

            var host = new ServiceHost(typeof(TestService));
            try
            {
                var binding = new NetTcpBinding();
                binding.Security.Mode = SecurityMode.None;

                host.AddServiceEndpoint(
                    typeof(ITestService),
                    binding,
                    "net.tcp://localhost:8080/TestService");
                host.Open();

                foreach (var endpoint in host.Description.Endpoints)
                {
                    Console.WriteLine(endpoint.ListenUri);
                }

                Thread.Sleep(-1);

            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.StackTrace);
                host.Close();
            }
        }
    }

    [ServiceContract]
    public interface ITestService
    {
        [OperationContract]
        string Test();
    }

    [ServiceBehavior(IncludeExceptionDetailInFaults = true)]
    public class TestService : ITestService
    {
        public static string ConnectionString { get; }

        static TestService()
        {
            ConnectionString = Environment.GetEnvironmentVariable("CONNECTION_STRING");
        }

        public string Test()
        {
            try
            {
                using (var connection = new SqlConnection(ConnectionString))
                {
                    connection.Open();
                    using (var command = connection.CreateCommand())
                    {
                        command.CommandText = @"SELECT @@VERSION";
                        return (string)command.ExecuteScalar();
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.StackTrace);
                throw;
            }
        }
    }
}

WCF クライアント

WCF サービスが動くコンテナの IP アドレスは起動するたび変わるので、コマンドライン引数で渡せるようにしておく。

using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using WcfWCOW;

namespace ConsoleClient
{
    class Program
    {
        static void Main(string[] args)
        {
            if (args.Length == 0)
            {
                Console.WriteLine("ホスト名または IP アドレスを指定してください。");
                return;
            }
            var host = args[0];
            var factory = new ChannelFactory<ITestService>(
                new NetTcpBinding(SecurityMode.None),
                $"net.tcp://{host}:8080/TestService");
            var channel = factory.CreateChannel();

            var result = channel.Test();
            Console.WriteLine(result);

            Console.WriteLine("Enter で終了");
            Console.ReadLine();

            ((IChannel)channel).Close();
        }
    }
}

namespace WcfWCOW
{
    [ServiceContract]
    public interface ITestService
    {
        [OperationContract]
        string Test();
    }
}

Dockerfile

あらかじめビルドしておいた WCF サービスのファイル一式をコピーして起動するように、Dockerfile を記述。

FROM mcr.microsoft.com/dotnet/framework/wcf:4.8
WORKDIR /app
EXPOSE 8080
COPY ./WcfWCOW/bin/Release/ .
ENTRYPOINT ["WcfWCOW.exe"]

起動

Dockerfile からコンテナイメージをビルドし、起動。Azure SQL Database の接続文字列を環境変数で渡している。

> docker build -t tnakamura/wcfwcow:1.0 .
> docker run --rm --isolation process -e CONNECTION_STRING="接続文字列" --name wcfwcow tnakamura/wcfwcow:1.0

エンドポイントのアドレスが表示されたら、WCF サービスの起動に成功したことになる。

f:id:griefworker:20201124103250p:plain

接続

docker inspect で起動したコンテナの IP アドレスを調べ、その IP アドレスを引数にクライアントを起動。

> docker inspect -f "{{ .NetworkSettings.Networks.nat.IPAddress }}" wcfwcow
> .\ConsoleClient.exe コンテナのIPアドレス

Azure SQL Database のバージョンが表示されたら実験成功。

f:id:griefworker:20201124103306p:plain

おわりに

Azure SQL Database に接続する WCF サービスを Windows コンテナのプロセス分離モードで動かすことに成功した。次のステップは、Azure Kubernetes Service かな。実験もいよいよ最終段階。

実をいうと、Windows コンテナのホスト側で動かしている SQL Server Express にも繋ぎたかったけど、host.docker.internal で繋げなかった。Windows コンテナだったから?プロセス分離だったから?今回も解決できず時間切れ。いずれリベンジしたい。

かぐや様は告らせたい(20)

会長とかぐやが無事付き合ったから主人公交代したのか?ってくらいに石上が話の中心。かぐやの助力もあって、目標としていた学年 50 位以内を達成したし、ホント男を上げている。さすがは裏主人公といったところか。何気にモテ期到来しているし。まぁ、そんな石上でもつばめ先輩を落とすのは難しいだろうなぁ、というのは薄々感じている。*1

石上が自らかぶった汚名をどうやってそそぐのかが、本作中で最重要な伏線の1つだったわけだけど、つばめ先輩がこれまで築き上げてきた繋がりを駆使して、石上の悪い噂を良い噂で上書きするとはね。これほどの誠意を見せられたら、告白の返事がどんなであろうと受け入れるしかないでしょ。作中の言葉を借りるなら、そんなつばめ先輩を好きになった石上は正しい。

*1:まぁ本誌ではすでに結果出てるけど。