[go: up one dir, main page]
More Web Proxy on the site http://driver.im/

SATOXのシテオク日記

~ふもっふ、ふもふも~

.NET時代の自動バージョン埋込実装方法

補足:この記事では、「.NET Framework」を.NET Framework 4.8までのこと、「.NET」.NET5以降主に.NET8の事を示します。

.NET前の.NET Framework時代のアプリケーションでは、AssemblyInfo.csに次のように書いておけば、ビルドするたびにビルドバージョンとリビジョンに日付と時刻を使ったバージョン(プロダクトバージョン)を付与して自動的に埋め込むことができました。

[assembly: AssemblyVersion("1.0.*")]
// 取り出し方
var version = Assembly.GetExecutingAssembly().GetName().Version;

埋め込んだバージョンは、exeファイルのプロパティから参照でき、プログラムからも取り出すことができます。

さらに、ビルドバージョンは2000年1月1日からの経過日数、リビジョンは0時からの秒数÷2で埋め込まれるため、バージョン番号からビルド日時を求めることができる、といったメリットがあります。

.NET時代でもアスタリスクを使ったバージョン付与は可能ですが、非推奨のプロジェクト設定「Deterministic」をFalse(出力の一意性を無効)にしなければならないうえ、同様の方法ではプロダクトバージョンには反映されず、プロパティから参照できなくなってしましました。(プログラムから取り出すことは可能)

つまり、とても中途半端になってしまったわけです。

プロジェクトのビルド前処理でアセンブリを更新する

そこでいろいろ調べた結果、プロジェクトファイル(.csproj)を編集してアセンブリ情報を更新する記述を行うことで自動的に任意のアセンブリ情報を更新できることが分かりました。

具体的には、下記XMLをプロジェクトファイル(.csproj)に挿入します。

  <PropertyGroup>
    <MajorVersion>1</MajorVersion>
    <MinorVersion>0</MinorVersion>
  </PropertyGroup>
  <Target Name="UpdateVersion" BeforeTargets="BeforeBuild">
    <PropertyGroup>
      <!-- Generate Version (Major.Minor.Build.Revision) Build=Days Rvision=Seconds/2 per oneday -->
      <BaseDate>2000-01-01</BaseDate>
      <CurrentDate>$([System.DateTime]::Now)</CurrentDate>
      <TicksPerDay>864000000000</TicksPerDay> <!-- Oneday Ticks -->
      <DaysSinceBaseDate>$([MSBuild]::Divide($([MSBuild]::Subtract($([System.DateTime]::Parse($(CurrentDate)).Ticks), $([System.DateTime]::Parse($(BaseDate)).Ticks))),$(TicksPerDay)))</DaysSinceBaseDate>
      <SecondsOfDay>$([System.DateTime]::Parse($(CurrentDate)).TimeOfDay.TotalSeconds)</SecondsOfDay>
      <RoundedSeconds>$([System.Math]::Round($(SecondsOfDay)))</RoundedSeconds>
      <Revision>$([MSBuild]::Divide($(RoundedSeconds), 2))</Revision>
      <VersionString>$(MajorVersion).$(MinorVersion).$(DaysSinceBaseDate).$(Revision)</VersionString>
      <AssemblyVersion>$(VersionString)</AssemblyVersion>
      <ProductVersion>$(VersionString)</ProductVersion>
      <InformationalVersion>$(VersionString)</InformationalVersion>
    </PropertyGroup>
  </Target>

この例ではアプリケーションの任意のメジャー、マイナーバージョンをMajorVersion、MinorVersionで指定することを想定しています。

BeforeTargets="BeforeBuild"がビルド前に行う処理であることを示し、AssemblyVersion、ProductVersion、InformationalVersionの部分が作り出したVersionStringを定義している箇所です。

バージョン取り出し、日時への変換コード例

using System.Diagnostics;
using System.Reflection;

var assembly = Assembly.GetExecutingAssembly();

// アセンブリのバージョン情報を取得
var version = assembly.GetName().Version;
Debug.Assert(version != null);

var dateTime = new DateTime(2000, 1, 1);
dateTime = dateTime.AddDays(version.Build).AddSeconds(version.Revision * 2);

Console.WriteLine($"アセンブリバージョン: {version}");
Console.WriteLine($"ビルド日時時刻      : {dateTime.ToString("yyyy-MM-dd HH:mm:ss")}");

VisualStudio2022、.NET8で動作確認しています。

出力結果例

プロパティ表示例

冷凍寿司に挑戦してみた

ある日、X(Twitter)でこんなPRツイートを見かけました。

https://x.com/greenbeansjp/status/1841005290239770861

Green Beans(グリーンビーンズ)、冷凍の新常識おためしキャンペーン90%OFF……まぁいかにもって感じで怪しい!と思ったわけですが、よくよく調べるとイオン系列のネットスーパーの模様。

そして、冷凍寿司、冷凍鮭、ハーゲンダッツ箱アイス(6個入)で税込401円。

……冷凍寿司!?🍣

気になって調べたら、レビューもわりと散々でこりゃ面白そう、安いなら今しかない!と思い立って注文してみたわけです。

……広告的にはしてやられていて、大成功ですね😀

Green Beans(グリーンビーンズ)

https://greenbeans.com/products/14580776974178/details#reviews-title

【冷凍】にぎり寿司 ふじ 9貫

これがその冷凍お寿司。9巻のお寿司で、通常なんと税込2,139円!

少しいいお寿司屋さんの価格。

★5段階中、2.5のレビューが付いてます。

早速届いた

冷凍状態のお寿司

ちゃんと届きました。配送の方、ありがとう。

さて、早速お昼に食べようと思い、どうやって解凍するかなーと裏面の解凍方法を読んでみたところ……なになに?

真空パックのまま約3時間解凍してください」

……がーん、お昼のウチに食べられないじゃん😭

まぁそりゃそうだよね、レンジに掛けたらネタに火が通っちゃうもんね🔥

成分表示、解凍方法など

せっかくなら最高の状態で食べたいので、しっかり待ちましたよ、3時間😊

ちなみにネタはまぐろ×2、車海老、サーモン、カマスサワラ、帆立貝柱、いか、ぶり、真鯛の計9貫、加えてガリが入っています。

3時間自然解凍後のお寿司

上の写真が自然解凍3時間後

中の空気が暖まって、真空状態が緩くなってます。

色合いもなんだか鮮やかで、おいしそうじゃないか🤤

お皿に並べてみる

お皿に並べてみた解凍お寿司

見栄え重視でお皿に並べてみました。

仕切りのバランも入ってますね。

レビューではこっぴどくご飯はパサパサ……なんて書かれていましたが、反してごはんが崩れることなく、意外としっかりしてる!

ネタにわさびをちょいちょいと乗せて、……おお、良い見栄えじゃないですか。

ガリもいいね。

実食

お寿司の質感

食べてみました。

正直に言えばお米はちょっと水分が少なく、ぼそぼそ感はあるけど、全然ダメと言うほどではなく、いける。

ネタの方はよく解凍したときの水っぽさも特になく、しっかりとした歯ごたえもあって、言い切ってしまえば結構おいしい。刺身の多くはそもそも冷凍だったりするので、これは想定内の品質。

ただ、米はいわゆる「冷凍焼け」って言うんでしょうか、米が一度溶けて白くなってしまったような部分があり、この部分ははっきり言ってマズい。その範囲は1つのお寿司の10粒くらいだったのですが、品質的にこの範囲が広くなってしまった「外れ」個体ももしかしたらあるかも、と思いました。(Green Beansは配送のために特殊な保冷トラックを用意しているみたいですが……)

そして美味しくないネタが一つありました。

それはイカ

これは控えめに言ってマズい。ぐにゅ~っとかみ切れないゴムの食感で、これはレギュラーメンバーから外した方が良い。イカ🦑って冷凍に向かないですよね。

総評

意外とテカテカの米

写真を見てもらうと分かると思いますが、意外とお米の水分は保っていて、ぼそぼそではあるんですが、レビューの悪評ほどではないと思いました。

そして、ネタ(まぐろ、海老、サーモン、サワラ、帆立貝柱、ぶり、真鯛)はどれもしっかりとしていてなかなか美味しい。ただしイカはゴム。

ただ、問題は値段ですよ。これが税込2,139円!

1ヶ月ほど保存できるそうですが、長期的に食料が調達できない環境で、どうしてもお寿司が食べたい!という状況ならその価値があるかもしれませんが、「まぁまぁなお寿司を2千円で買う意味」がなかなか見いだせないんですよね。

……と言うわけで、この冷凍お寿司、普通に注文したりはしないと思いますが、面白い体験をさせて頂きました。

キャンペーンでたった税込401円でこのお寿司、冷凍鮭、ハーゲンダッツも付いているので、注文できる方は試してみないと損でしょう。先着6,000名ですってよ。

C# Slack APIでファイルアップロード

これまでChatworkというチャットアプリのAPIを使っていろいろな自動化をしていたのですが、旧に会社の方針でChatwork廃止、Slackにするぞとなりました。

個人的には最初からSlackの方が高機能で良さそうだと思っていた~というのは愚痴ですが詰まるところ、Slackにこれまで作ってきたものを移植しないといけなくなったわけです。

寝耳に水。

Slackメッセージ送信は超簡単、ファイルのアップロードは?

メッセージの取得だけに限れば、curlでWebhook URLを叩けば一発でメッセージを送信できます。あら簡単。

curl -X POST -H "Content-type: application/json" --data "{\"text\":\"test Message"\"}" https://hooks.slack.com/services/T0XXXXXX/B0XXXXX/XXXXXX

ところが、Webhook URLで利用出来るのはメッセージの送信のみで、メッセージの受信はもちろん、ファイルのアップロードなどもできないのです。

ファイルのアップロードについて

これまであったSlackファイルアップロードAPI(files.upload)が2025年3月に廃止されるそうで、現在、既に新規作成したアプリは使用不可になっています。

これが世の中のサンプルがなぜか動かない原因と分かり、それまで難儀してました。

結論から言いうと、代わりに下記を使いましょう**となってます。SharePointとかも似た方式に移行してますね。

  • sequenced Web API
    • files.getUploadURLExternal
    • files.completeUploadExternal

ちなみに、公式?とおぼしきSlackAPI というライブラリがあり、それを使えば良いじゃんと思いましたが、1年前に更新が止まってしまっていて、上記のAPIには対応していませんでした。

さぁ四の五の言わず、アップロード関数サンプルを(C#

using System.Diagnostics;
using System.Net.Http.Headers;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

private String Token = "YoutBotToken";
private String SlackApiEntryPoint = "https://slack.com/api";

public async void UploadFileAsync(String channelId, String filePath)
{
    var fileData = await GetUploadURLExternal(filePath);
    await this.UploadFile(fileData.url, filePath);
    await this.CompleteUploadExternal(fileData.fileId, filePath, channelId);
}

private async Task<(string url, string fileId)> GetUploadURLExternal(string filePath)
{
    using (var client = new HttpClient()) {
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Token);
        var baseUrl = $"{SlackApiEntryPoint}/files.getUploadURLExternal";
        var response = await client.GetAsync($"{baseUrl}?filename={Path.GetFileName(filePath)}&length={new FileInfo(filePath).Length}");
        response.EnsureSuccessStatusCode();
        var jsonResponse = JObject.Parse(await response.Content.ReadAsStringAsync());
        if (!jsonResponse["ok"].Value<bool>()) {
            throw new Exception($"Error : files.getUploadURLExternal, {jsonResponse["error"]}.");
        }
        return (jsonResponse["upload_url"].ToString(), jsonResponse["file_id"].ToString());
    }
}

private async Task UploadFile(String url, String filePath)
{
    using (var client = new HttpClient())
    using (var form = new MultipartFormDataContent()) {
        using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read)) {
            var streamContent = new StreamContent(fileStream);
            streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
            form.Add(streamContent, "file", Path.GetFileName(filePath));
            var response = await client.PostAsync(url, form);
            if (response.IsSuccessStatusCode == false) {
                throw new Exception($"Error : UploadFile failed, status {response.StatusCode}");
            }
        }
    }
}
private async Task<JObject> CompleteUploadExternal(string fileId, string filePath, string channelId)
{
    using (var client = new HttpClient()) {
        var json = new JObject {
            { "channel_id", channelId },
            { "files", new JArray { new JObject{  
                { "id", fileId },                        
                { "title", Path.GetFileName(filePath) } 
            }
            } }
        };
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Token);
        var content = new StringContent(json.ToString());
        content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
        var response = await client.PostAsync($"{SlackApiEntryPoint}/files.completeUploadExternal", content);
        response.EnsureSuccessStatusCode();
        var jsonResponse = JObject.Parse(await response.Content.ReadAsStringAsync());
        if (!jsonResponse["ok"].Value<bool>()) {
            throw new Exception($"Error : files.completeUploadExternal, {jsonResponse["error"]}.");
        }
        return jsonResponse;
    }
}

必要な情報

必要なものは、「アクセス許可諸々有効にしたトークToken(この場合はBotToken)」「投稿先のチャンネルID channelId」「アップロードしたいファイル filePath」を用意して非同期関数のUploadFileAsyncを叩けばファイルがアップロードできます。

ここでは説明省略しますが、https://app.slack.comであらかじめアプリケーションを作成しトークンを発行、Permissionの許可設定とInstallという作業をしてアクセス許可を行っておく必要がある点に注意です。投稿先のチャンネルに作成したアプリケーションを追加するのも忘れずに(これに相当ハマった)。

処理構成

処理構成を簡単に説明するとUploadFileAsync関数は下記3つで構成されており、 順に呼び出す必要があります。

  1. GetUploadURLExternal() ... GETでファイル情報を渡してURLとIDを取得
  2. UploadFile() ... POSTで取得URIへファイルアップロード
  3. CompleteUploadExternal() ... POST/JSONでアップロード完了を通知

整理したら簡単ですね。

もしうまくいかない場合は、レスポンスのerror項目を見るとヒントになります。


さて、他の細々としたAPIも理解して対応していかねばといった次第……。未読のメッセージが(情報なしに)簡単に取得できなさそうで、ぐぬぬとなっております。

雑Slackテストアプリ
api.slack.com

体験してみた食品系サブスクいろいろ

コロナの影響が多いとは思いますが、近年在宅者に便利なサブスクサービスがどっと増


えましたよね。

SATOXさん、普通に無精なのと、なんだか宣伝している面白そうなサービス、特に食いしん坊なもんで「食」に関するサービスは良さそうなら試してみたくなります。

というわけで、これまで実際に試してみた5種類のサービスについてご紹介します。

(本記事投稿時の値段やサービスなのでご注意)

1. snaq.me

snaq.me(スナックミー)は人工甘味料や着色料、保存料を使わない様々なお菓子をランダムで届けてくれるサブスクリプションのサービスです。

1,880円+送料330円で6種類ほどのお菓子が入っていて、ポスト投函されます。

食べたことのない面白いお菓子が届いて楽しいのですが、ちょっとコスト高。お菓子もしばらくすると一周してしまうのでちょっと飽きてしまって現在はサービス停止中。

snaq.me(スナックミー)のおつまみBOX

 

2. nosh

nosh(ナッシュ)は冷凍のおかずを送ってくれるサービスです。

1食623円+冷凍送料1,056円(地域による)で、1食のお値段は購入数で520円まで安くなります。

おかずなのでご飯はないのがほとんどですが、中にはカレーライスやチャーハン、パン、シリアルチョコバーみたいなものもあったりしてメニューは自由に選べます。

基本的に味付けが良くおいしいのですが、1つのおかずに付く3種類の付け合わせに当たり外れがあり、バリエーションも少ないため、「ああ、またこれか」と飽きがきます。

また、メインのおかずを含め量がちょっと少ない。

なのでおいしくても物足りなさを感じてしまううえ、「おかず」だけなのでnoshだけでは基本完結せず「めんどうでコスパ悪いなぁ」という感想に行きつきます。(「少なさ」については改善されているようですが、具体的な増量具合は不明)

最近は+250円でプレミアム現在サービスは停止中。

3. PostCoffee

世界各国のコーヒーがポスト投函で定期購入できるというコーヒー好きにはたまらないサービスです。

購入パターンはいろいろありますが、送料込1,980円で75g×3種類のコーヒーが届きます。豆での購入はもちろん、粉やドリップパックでの購入が可能。コーヒーの炒具合などの好みもカスタマイズできます。(ちなみに自分は豆で購入してハンドミルしてます。)

以前は紙フィルターとおまけのお菓子が入っていたのに、そのサービスがなくなってしまったのは残念。

普通にコーヒー豆を買ってくるのと比べればまぁまぁコスト高ですが、いろいろなコーヒー豆がポストに届いて味わえるのでお気に入りのサブスクです。

現在も1年以上サービス継続中。

4. BASE FOOD (BASE BREAD)

栄養素がバランス良く含まれている完全食とうたっている食べ物の定期購入サービスです。

例えばBASE BREADチョコレート1袋240円ほどで常温送料500円が掛かります。

朝昼晩1食につき2個、1日6個食べるだけで生きていける栄養素が詰まっているようです。

味が気になってサービスを試してみたのですが、マズいとまでは言わないけども正直「おしくない」。結構多めに買ってしまってどうやって消費していくかが悩みどころで、消費期限が迫っている……。

現在サービスは停止中。

 

5. 完全メシDELI

日清食品の様々な栄養素に特化した冷凍食品が購入できるサービス。

栄養素がプラスされたカツ丼やお好み焼き、ピザ、カレーライスなど、メインとなる1食が冷凍食品が届きます。メニューは自由に選べます。

1食600円くらい+冷凍送料750円。

普通の冷食と比べてしまうとコスト高感があるものの、栄養バランスが考慮されているのがポイントで、まぁまぁおいしいです。

完全メシDELI的に「カツ丼」がイチオシのようで実際なかなかおいしいのですが、どうしてもべちょっとしてしまうご飯が苦手。個人的には「デミソースのふんわりオムライス」「旨辛ビビンバ」「炭火焼風味テリヤキチキンピザ」がおいしいと思う。

現在サービス継続中。

というわけで、実際に試してみた食品系サブスクの紹介でした。

他にもいろんなメーカーのいろんなサービスがありますよね。試してみたらご紹介したいと思います。

C#(libclang)でCソース解析

人類は誰しも2つの種類に分類される。

C言語ソースファイルを解析したいか解析したくないかである。

自分は前者。

……というわけで、C#C言語のソースを解析しようと、数ヶ月じゃ済まないくらい試行錯誤した末、自力ではかなり難しいという結論に達しまして、素直にlibclangを利用することにしました。 正直、C言語解析なんて余裕だぜと舐めていたのですが、一朝一夕でできるもんじゃないんですよね。libclang.dllのバイナリサイズ144MBが物語っています。

というお話。

ごちゃごちゃ説明はせず、シンプルに情報だけまとめておきます。

続きを読む

X(Twitter)ロゴを肉球アイコンに変えるブラウザ拡張機能つくった

自作のPochitter!がこれまで利用できたAPI廃止により使えなくなり、あまつさえ愛すべき「Twitter」という名称の変更。そして愛すべき鳥のアイコンが「𝕏」アイコンに……そんななんとも言えないできごとがあった2023年7月24日(頃)。

「𝕏」なんてちっとも愛嬌ないですよね。

そして、𝕏なんて付けるのはちょっと安易で中二病臭がしますよねぇ、SATO𝕏さん。

……。

XPawPadつくったよ

それはさておき、なにか面白いことをやりたい衝動に駆られ、「https://twitter.comサイトの𝕏ロゴを肉球アイコンに変更するだけ」Chrome/Firefox拡張機能を作ったのでした(多分1時間くらい)。

Chrome(Edge)版、Firefox版がそれぞれオフィシャルサイトで公開されてます。

Chrome 拡張機能(Edgeでも利用化)

Firefox Add-Ons

青い鳥アイコンに戻す拡張機能

ところで、元の青い鳥アイコンに戻す拡張機能が人気ですよね……。

実は、開発中は青い鳥に戻すようにしてたのですが、恐らく「みんな作るだろう」と思ってそうしませんでした。

案の定、調べてみるだけで「X Be Gone」「GoodbyeX」「restore-twitter-icon」「twitter_icon_x_to_bird」などがあるようです。

 

ちなみに、ブラウザ拡張機能の開発はメモ帳1つあれば作れるので、お勧めです。

Welcome to the Chrome Extension Manifest V3 - Chrome Developers

作るのがめんどくさいよう、という方はブラウザ拡張機能のネタをSATOXに教えてくださいw 気が向いたら作ってみますよ~。

C#でXMLにXSLTをかます

以前は結構使う機会があったと思うのですが、最近はどうだろう。XSL Transformations(XSLT)をC#で行いたい機会があって調べたのでメモ。

ちなみに、XSLTとはXMLに記述されたデータをスクリプトなどなしに思い通りの出力形式に変換する仕組み。 必要な情報だけ取り出したり、ページングするためにデータ範囲を決めたりして、主にHTMLに変換したりします。

以下、XMLXSLTを入力としてXMLを出力する関数のサンプル。

static void TransformXslt(String inputXmlFilePath, String inputXsltFilePath, String outputXmlFilePath)
{
     // xmlの読み込み
     var xmlDoc = new XmlDocument();
     xmlDoc.Load(inputXmlFilePath);

     // xsltの読み込み
     var xslt = new XslCompiledTransform();
     var xsltSettings = new XsltSettings() {
        EnableDocumentFunction = true,  // XSLT有効
        EnableScript = true,             // スクリプトブロック有効
     };
     xslt.Load(inputXsltFilePath, xsltSettings, null);

     // 出力XML Writer生成
     var writerSetting = new XmlWriterSettings() {
        Indent = true,            // インデント有効
        IndentChars = "     ", // インデントタブ文字列
     };
     using (var writer = XmlTextWriter.Create(outputXmlFilePath, writerSetting)) {
        // xmlをxsltで変換
        xslt.Transform(xmlDoc, null, writer);
     }
}

www.w3schools.com