20201125のC#に関する記事は12件です。

リングフィットアドベンチャーのようにJoyConを用いてGoogleStreetViewの世界を歩き回る【Unity+GoogleMapsJavaScriptAPI】

リングコンは使いません(レッグバンドとジョイコンだけ)

経緯

  • スタンディングデスク+ステッパーという意識高そうな在宅ワーク環境を手に入れた...のはいいものの、すぐ飽きた。座りたい。
  • 運動の動機付けとしてサイクリングマシンでストリートビューを操作するやつ(これとか)をステッパーで作ってみようと思った。
  • 運動を検知するためにセンサーが必要。何かしらの動きをマイコンで検知するとして、さらにそれを何かしらの手段でPCに送信して、...結構大変だなあ。お金もかかりそうだ。
  • どっかに運動(特に傾き)を検知できてBluetoothでPCに接続できる機械ないかなあ~~~~~。

36661.png

  • あった~~~~~~!!!!。ジョイコンで傾き検知が可能。ついでにレッグバンド(リングフィットアドベンチャーの付属品)でちょうど足につけられる。(レッグバンドはなくてもどうにでもなります)

というわけでジョイコンを用いてストリートビューの世界を歩き回ろうと思います。

方針

joyコンとの連携のためにライブラリが充実しているUnityを使用することは決定。ただしストリートビューを利用するためにはブラウザからHTMLファイルとして開く必要があるらしい。よって、Unityからブラウザを開く+開いたページをSeleniumで操作するという方針に決定。

以下の二つから構成される。

  • 表示するページ(HTML+javascript)
    • GoogleMapsJavaScriptAPIを用いてグーグルストリートビューの表示を行う。
    • 「ボタンを押すと前進する」という機能だけ持つ。
  • Unity側
    • selenium+chromeDriverを用いてページを開く。
    • ジョイコンの傾きを検知し、歩数をカウントする。歩くたびにwebページのボタンを押す。

表示するページ

参考サイト:

GoogleStreetViewが勝手に動く!?GoogleMapAPIを使った自動再生プログラムを作ってみる。|株式会社アーティス
https://www.asobou.co.jp/blog/web/streetview

まずAPIキーを取得します。このページ等を参考にしてGoogleMapsJavaScriptAPIの有効化とキーの取得を行ってください。googleのAPIには無料枠があり、月額200ドル分まで(およそ14000回ほど)無料で使えるようです。

表示するページは以下のように作成します。これはほとんど参考サイトで書かれたコードのままですが、ボタンを追加してそれを押すたびに(5回に1回)移動する、というように書き換えました。また、この記事を参考に多少座標がずれても修正できるようにしています。

<html>
<head>
    <title>StreetView</title>
    <meta name="viewport" content="initial-scale=1.0">
    <meta charset="utf-8">
    <input type="button" onclick="proceed()" id="count" value="0歩">
    <style>
        html,
        body {
            height: 100%;
            margin: 0;
            padding: 0;
        }

        #street-view {
            height: 100%;
        }
    </style>
</head>
<body>

<div id="street-view"></div>


<script>
    const LIMIT = 300; // 移動回数の上限値
    const START_LAT_LNG = {lat: 任意の緯度, lng: 任意の経度}; // 開始地点の緯度、経度
    //const START_LAT_LNG = {lat: 35.1010033, lng: 139.0779688}; // 例
    const START_HEADING = 180; // 開始時の方角
    let panorama;
    isProceed = false;
    let count = 0;
    let m = 0;
    let n = 5; // 何歩ごとに1進むか。Unityでも設定する。少ない方が嬉しいが無料枠に注意
    let buttonText = document.getElementById('count');
    function initMap() {
        let Links, count = 0, timer_id;
         panorama = new google.maps.StreetViewPanorama(
            document.getElementById('street-view'), {
                position: START_LAT_LNG,
                pov: {
                    heading: START_HEADING,
                    pitch: 0
                },
                zoom: 1
            }); // 
        // 座標の微妙なズレに補正を加える
        svs = new google.maps.StreetViewService();
        svs.getPanoramaByLocation(START_LAT_LNG, 50, function(result, status) {
            if (status == google.maps.StreetViewStatus.OK){
                    panorama.setPosition(result.location.latLng);
            }
        });
        Links = panorama.getLinks();



    }

    function proceed()
    {
        m++;
        if(m >= n)
        {
            m = 0;
            if (panorama.getStatus() == "OK") {
                Links = panorama.getLinks();
                if (count > LIMIT) {
                    alert('stop');
                    return false;
                }
                let target = 0;
                if (Links.length >= 4) {
                    target = Math.floor(Math.random() * Links.length);
                } else {
                    let val = 360;
                    let currentPov = panorama.getPov();
                    Links.forEach(function (element, index) {
                        let ans = Math.abs(currentPov.heading - element.heading);
                        if (val > ans) {
                            val = ans;
                            target = index;
                        }
                    });
                }
                panorama.setPov({
                    heading: Links[target].heading,
                    pitch: 0
                });
                panorama.setPano(Links[target]['pano']);

            };            
        }

        count++;
        buttonText.value = String(count) + "";
    }
</script>

<script src="https://maps.googleapis.com/maps/api/js?key=取得したキー&callback=initMap"
        async defer></script>
</body>
</html>

Unity側の設定

ジョイコン検知のための設定

以下のサイトを参考にしました。

【Unity】Nintendo Switch の Joy-Con を使用する方法

【Unity】Nintendo Switch の Joy-Con のジャイロ・加速度・傾きの値を取得したり、振動させたりすることができる「JoyconLib」紹介

上の内容に従いProject Settingsから入力の変更、二つのライブラリの導入を行いました。

selenium+chromedriverの導入

参考サイト: UnityのC#からSelenium経由でChromeをゴニョゴニョする

上の内容に従い、SeleniumとChromeDriverの導入を行いました。ChromeDriverはバージョンに注意してください。

コード

Start()時にChromeDriverを用いてブラウザを起動。歩行を感知するたびにボタンを押す。

歩数の取得をどうやるかは一考の余地がありますが、とりあえず以下のように設定しました。

using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;

using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;


public class JoyConWalker : MonoBehaviour
{
    private ChromeDriver _driver;
    private List<Joycon>    m_joycons;
    private Joycon          m_joyconL;
    private Joycon          m_joyconR;
    private Joycon.Button?  m_pressedButtonL;
    private Joycon.Button?  m_pressedButtonR;
    int count = 0;
    float LlastUpper = 0;
    float LlastLower = 0;
    float Llast = 0;
    bool LisUp = true;
    float theta = 0.08f; //閾値。設定は微妙なので要調整。
    void Start()
    {
        var path = Application.streamingAssetsPath;
        _driver = new ChromeDriver(path);

        _driver.Navigate().GoToUrl(Application.dataPath + "/StreetView.html");

        m_joycons = JoyconManager.Instance.j;
        if ( m_joycons == null || m_joycons.Count <= 0 ) return;
        m_joyconL = m_joycons.Find( c =>  c.isLeft );
        m_joyconR = m_joycons.Find( c => !c.isLeft );
    }

    void Update()
    {
        var orientationL = m_joyconL.GetVector();
        var current = orientationL.z;
        if(LisUp)
        {
            if(current >= Llast)
            {
                LlastUpper = current;
            }
            else
            {
                if(LlastUpper - LlastLower >= theta)
                {
                    Proceed();
                }
                LisUp = false;
                LlastLower = current;
            }
        }
        else
        {
            if(current <= Llast)
            {
                LlastLower = current;
            }
            else
            {
                if(LlastUpper - LlastLower >= theta)
                {
                    Proceed();
                }
                LisUp = true;
                LlastUpper = current;
            }            
        }
        Llast = current;


    }

    void Proceed()
    {
        count++;
        Debug.Log(count);
        _driver.FindElement(By.Id("count")).Click();

    }

    void OnDestroy()
    {
        _driver.Dispose();
    }
}

Unityで組み立てる

シーンに空のゲームオブジェクトを配置して↑のcsファイルと、JoyConManager.csをつける。htmlファイルをAssets下に配置。

これで完成。起動し、ページが開いたら左のジョイコンをレッグバンドに入れて足踏みをします。

座標は自分の好きな観光地である熱海に設定。歩数表示が左上に出ています。ステッパーで足踏みしてる間、延々と熱海をさまようことができます。

rapture_20201125221939.jpg
Asset下の構成はgitにアップロードしておきました。

https://github.com/NNNiNiNNN/JoyCon_StreetView

課題

初期座標の決定が難しいこと

現在は完全に固定した座標からスタートして、(アルゴリズム的に)完全に固定されたルートを通るようになっています。座標の決定を完全にランダムにしてから最寄りのデータを持つ座標に補正する、というやり方も試してみましたが、この手段で得られるパノラマ座標は「ストリートビューとして移動できるパノラマ座標」に限らないため、移動先を失うパターンが多くありました。apiも他にどのようなものが使えるのか把握していないため、自分がすぐ直すのは難しそうです。理想を言えば初期地点と目的地点を手動で決定して自動でルート案内するのがいいと思います。実装も多分可能ですが、今回はなしで。

歩行の検知

単純にジョイコンの傾きの差を取ればいいだけ、だとは思うのですがいまいち最適化できていません。そもそもxyzのどれを取ればいいのかもはっきり決め切れていません。現在は動きの閾値を小さめに設定することでだいたいの動きを無理やり取る形にしています。

ジョイコンの接続エラー?

一度接続したジョイコンは、その接続が途切れた後はPCを再起動しないと再接続できなくなることがあります。理由も再現性もよくわかっていません。

あとがき

課題は多くありますが、よく見るフィットネスバイク+ストリートビュー....の亜種を自力で作れたことが嬉しいです。ジョイコンとかgoogleのAPIとか、見渡せば色々便利なものがあるんですねえ。苦戦した部分もありましたが、最終的には簡単な内容で仕上がったのもよかったです。

作ったもので運動するかはわかりません。

参考

GoogleStreetViewが勝手に動く!?GoogleMapAPIを使った自動再生プログラムを作ってみる。|株式会社アーティス

Googleマップストリートビューで目的地に最寄りのストリート位置(座標情報)を取得する方法

【Unity】Nintendo Switch の Joy-Con を使用する方法

【Unity】Nintendo Switch の Joy-Con のジャイロ・加速度・傾きの値を取得したり、振動させたりすることができる「JoyconLib」紹介

UnityのC#からSelenium経由でChromeをゴニョゴニョする

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

リングフィットアドベンチャーの道具でグーグルストリートビューを歩き回る【Unity+GoogleMapsJavaScriptAPI】

リングコンは使いません(レッグバンドとジョイコンだけ)

経緯

  • スタンディングデスク+ステッパーという意識高そうな在宅ワーク環境を手に入れた...のはいいものの、すぐ飽きた。座りたい。
  • 運動の動機付けとしてサイクリングマシンでストリートビューを操作するやつ(これとか)をステッパーで作ってみようと思った。
  • 運動を検知するためにセンサーが必要。何かしらの動きをマイコンで検知するとして、さらにそれを何かしらの手段でPCに送信して、...結構大変だなあ。お金もかかりそうだ。
  • どっかに運動(特に傾き)を検知できてBluetoothでPCに接続できる機械ないかなあ~~~~~。

36661.png

  • あった~~~~~~!!!!。ジョイコンで傾き検知が可能。ついでにレッグバンド(リングフィットアドベンチャーの付属品)でちょうど足につけられる。(レッグバンドはなくてもどうにでもなります)

というわけでジョイコンを用いてストリートビューの世界を歩き回ろうと思います。

方針

joyコンとの連携のためにライブラリが充実しているUnityを使用することは決定。ただしストリートビューを利用するためにはブラウザからHTMLファイルとして開く必要があるらしい。よって、Unityからブラウザを開く+開いたページをSeleniumで操作するという方針に決定。

以下の二つから構成される。

  • 表示するページ(HTML+javascript)
    • GoogleMapsJavaScriptAPIを用いてグーグルストリートビューの表示を行う。
    • 「ボタンを押すと前進する」という機能だけ持つ。
  • Unity側
    • selenium+chromeDriverを用いてページを開く。
    • ジョイコンの傾きを検知し、歩数をカウントする。歩くたびにwebページのボタンを押す。

表示するページ

参考サイト:

GoogleStreetViewが勝手に動く!?GoogleMapAPIを使った自動再生プログラムを作ってみる。|株式会社アーティス
https://www.asobou.co.jp/blog/web/streetview

まずAPIキーを取得します。このページ等を参考にしてGoogleMapsJavaScriptAPIの有効化とキーの取得を行ってください。googleのAPIには無料枠があり、月額200ドル分まで(およそ14000回ほど)無料で使えるようです。

表示するページは以下のように作成します。これはほとんど参考サイトで書かれたコードのままですが、ボタンを追加してそれを押すたびに(5回に1回)移動する、というように書き換えました。また、この記事を参考に多少座標がずれても修正できるようにしています。

<html>
<head>
    <title>StreetView</title>
    <meta name="viewport" content="initial-scale=1.0">
    <meta charset="utf-8">
    <input type="button" onclick="proceed()" id="count" value="0歩">
    <style>
        html,
        body {
            height: 100%;
            margin: 0;
            padding: 0;
        }

        #street-view {
            height: 100%;
        }
    </style>
</head>
<body>

<div id="street-view"></div>


<script>
    const LIMIT = 300; // 移動回数の上限値
    const START_LAT_LNG = {lat: 任意の緯度, lng: 任意の経度}; // 開始地点の緯度、経度
    //const START_LAT_LNG = {lat: 35.1010033, lng: 139.0779688}; // 例
    const START_HEADING = 180; // 開始時の方角
    let panorama;
    isProceed = false;
    let count = 0;
    let m = 0;
    let n = 5; // 何歩ごとに1進むか。要検討。少ない方が嬉しいが無料枠に注意
    let buttonText = document.getElementById('count');
    function initMap() {
        let Links, count = 0, timer_id;
         panorama = new google.maps.StreetViewPanorama(
            document.getElementById('street-view'), {
                position: START_LAT_LNG,
                pov: {
                    heading: START_HEADING,
                    pitch: 0
                },
                zoom: 1
            }); // 
        // 座標の微妙なズレに補正を加える
        svs = new google.maps.StreetViewService();
        svs.getPanoramaByLocation(START_LAT_LNG, 50, function(result, status) {
            if (status == google.maps.StreetViewStatus.OK){
                    panorama.setPosition(result.location.latLng);
            }
        });
        Links = panorama.getLinks();



    }

    function proceed()
    {
        m++;
        if(m >= n)
        {
            m = 0;
            if (panorama.getStatus() == "OK") {
                Links = panorama.getLinks();
                if (count > LIMIT) {
                    alert('stop');
                    return false;
                }
                let target = 0;
                if (Links.length >= 4) {
                    target = Math.floor(Math.random() * Links.length);
                } else {
                    let val = 360;
                    let currentPov = panorama.getPov();
                    Links.forEach(function (element, index) {
                        let ans = Math.abs(currentPov.heading - element.heading);
                        if (val > ans) {
                            val = ans;
                            target = index;
                        }
                    });
                }
                panorama.setPov({
                    heading: Links[target].heading,
                    pitch: 0
                });
                panorama.setPano(Links[target]['pano']);

            };            
        }

        count++;
        buttonText.value = String(count) + "";
    }
</script>

<script src="https://maps.googleapis.com/maps/api/js?key=取得したキー&callback=initMap"
        async defer></script>
</body>
</html>

Unity側の設定

ジョイコン検知のための設定

以下のサイトを参考にしました。

【Unity】Nintendo Switch の Joy-Con を使用する方法

【Unity】Nintendo Switch の Joy-Con のジャイロ・加速度・傾きの値を取得したり、振動させたりすることができる「JoyconLib」紹介

上の内容に従いProject Settingsから入力の変更、二つのライブラリの導入を行いました。

selenium+chromedriverの導入

参考サイト: UnityのC#からSelenium経由でChromeをゴニョゴニョする

上の内容に従い、SeleniumとChromeDriverの導入を行いました。ChromeDriverはバージョンに注意してください。

コード

Start()時にChromeDriverを用いてブラウザを起動。歩行を感知するたびにボタンを押す。

歩数の取得をどうやるかは一考の余地がありますが、とりあえず以下のように設定しました。

using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;

using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;


public class JoyConWalker : MonoBehaviour
{
    private ChromeDriver _driver;
    private List<Joycon>    m_joycons;
    private Joycon          m_joyconL;
    private Joycon          m_joyconR;
    private Joycon.Button?  m_pressedButtonL;
    private Joycon.Button?  m_pressedButtonR;
    int count = 0;
    float LlastUpper = 0;
    float LlastLower = 0;
    float Llast = 0;
    bool LisUp = true;
    float theta = 0.08f; //閾値。設定は微妙なので要調整。
    void Start()
    {
        var path = Application.streamingAssetsPath;
        _driver = new ChromeDriver(path);

        _driver.Navigate().GoToUrl(Application.dataPath + "/StreetView.html");

        m_joycons = JoyconManager.Instance.j;
        if ( m_joycons == null || m_joycons.Count <= 0 ) return;
        m_joyconL = m_joycons.Find( c =>  c.isLeft );
        m_joyconR = m_joycons.Find( c => !c.isLeft );
    }

    void Update()
    {
        var orientationL = m_joyconL.GetVector();
        var current = orientationL.z;
        if(LisUp)
        {
            if(current >= Llast)
            {
                LlastUpper = current;
            }
            else
            {
                if(LlastUpper - LlastLower >= theta)
                {
                    Proceed();
                }
                LisUp = false;
                LlastLower = current;
            }
        }
        else
        {
            if(current <= Llast)
            {
                LlastLower = current;
            }
            else
            {
                if(LlastUpper - LlastLower >= theta)
                {
                    Proceed();
                }
                LisUp = true;
                LlastUpper = current;
            }            
        }
        Llast = current;


    }

    void Proceed()
    {
        count++;
        Debug.Log(count);
        _driver.FindElement(By.Id("count")).Click();

    }

    void OnDestroy()
    {
        _driver.Dispose();
    }
}

Unityで組み立てる

シーンに空のゲームオブジェクトを配置して↑のcsファイルと、JoyConManager.csをつける。htmlファイルをAssets下に配置。

これで完成。起動し、ページが開いたら左のジョイコンをレッグバンドに入れて足踏みをします。

座標は自分の好きな観光地である熱海に設定。歩数表示が左上に出ています。ステッパーで足踏みしてる間、延々と熱海をさまようことができます。

rapture_20201125221939.jpg
Asset下の構成はgitにアップロードしておきました。

https://github.com/NNNiNiNNN/JoyCon_StreetView

課題

初期座標の決定が難しいこと

現在は完全に固定した座標からスタートして、(アルゴリズム的に)完全に固定されたルートを通るようになっています。座標の決定を完全にランダムにしてから最寄りのデータを持つ座標に補正する、というやり方も試してみましたが、この手段で得られるパノラマ座標は「ストリートビューとして移動できるパノラマ座標」に限らないため、移動先を失うパターンが多くありました。apiも他にどのようなものが使えるのか把握していないため、自分がすぐ直すのは難しそうです。理想を言えば初期地点と目的地点を手動で決定して自動でルート案内するのがいいと思います。実装も多分可能ですが、今回はなしで。

歩行の検知

単純にジョイコンの傾きの差を取ればいいだけ、だとは思うのですがいまいち最適化できていません。そもそもxyzのどれを取ればいいのかもはっきり決め切れていません。現在は動きの閾値を小さめに設定することでだいたいの動きを無理やり取る形にしています。

ジョイコンの接続エラー?

一度接続したジョイコンは、その接続が途切れた後はPCを再起動しないと再接続できなくなることがあります。理由も再現性もよくわかっていません。

あとがき

課題は多くありますが、よく見るフィットネスバイク+ストリートビュー....の亜種を自力で作れたことが嬉しいです。ジョイコンとかgoogleのAPIとか、見渡せば色々便利なものがあるんですねえ。苦戦した部分もありましたが、最終的には簡単な内容で仕上がったのもよかったです。

作ったもので運動するかはわかりません。

参考

GoogleStreetViewが勝手に動く!?GoogleMapAPIを使った自動再生プログラムを作ってみる。|株式会社アーティス

Googleマップストリートビューで目的地に最寄りのストリート位置(座標情報)を取得する方法

【Unity】Nintendo Switch の Joy-Con を使用する方法

【Unity】Nintendo Switch の Joy-Con のジャイロ・加速度・傾きの値を取得したり、振動させたりすることができる「JoyconLib」紹介

UnityのC#からSelenium経由でChromeをゴニョゴニョする

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

ファイル・ディレクトリ検索インデックス作成・検索アプリケーション

1. はじめに

ファイルまたはディレクトリのインデックスを作成し、インデックスから検索してファイルの実行またはディレクトリの表示を行うアプリケーションです。
他のPCで作成したインデックスファイルをローカルPCにコピーすることにより、オフラインで検索したり、共有フォルダ越しにファイル・ディレクトリにアクセスしたりすることができます。

20201126.png

2. バイナリ

インデックス作成
MakeMyIndex_1.00_20201125.zip 最新版

インデックス検索
SearchMyIndex_1.01_20201126.zip 最新版
SearchMyIndex_1.00_20201125.zip

フリーウェアとします。無保証です。

3. ソース

MakeMyIndex GitHubリポジトリ
SearchMyIndex GitHubリポジトリ

4. インストール

1) バイナリファイルとexe.configを任意のフォルダに置いて下さい。
2) C:\MyIndex ディレクトリを作成して下さい。この直下のファイル(複数)をインデックスファイルとして読み込みます。
※このディレクトリを作成しないと、SearchMyIndexが何も表示せずに終了します。

5.使用例(PC 1台)

下記のコマンドを実行します。インデックスファイル名は任意です。

C:\dir1\ 配下のファイルのインデックスを作成
(インストールパス)\MakeMyIndex /f C:\dir1 /o C:\MyIndex\C.dir1.txt

C:\dir2\ 配下のディレクトリのインデックスを作成
(インストールパス)\MakeMyIndex /d C:\dir2 /o C:\MyIndex\C.dir2.txt

SearchMyIndex を実行します。

起動時に全てのインデックスファイルを読み込みます。
検索文字列を入力して検索ボタンもしくはEnterキーを押下すると、全てのインデックスのうち一致するファイル・ディレクトリを表示します。大文字小文字、全角半角、ひらがなカタカナを区別しません。検索文字列を入力せずに検索すると、インデックス内の全てのファイル・ディレクトリを表示します。
検索結果から1行選択してEnter押下またはダブルクリックを行うと、そのファイルを実行またはディレクトリを表示します。

6. 使用例(PC 2台)

PC1のC:\dir1\ 配下のファイルのインデックスを作成(C:\ の共有名はpc1_cとする)
PC1で下記のコマンドを実行します。
MakeMyIndex /f C:\dir1 /s \\PC1\pc1_c\dir1 /o C:\MyIndex\PC1.C.dir1.txt

PC2のC:\dir2\ 配下のディレクトリのインデックスを作成(C:\ の共有名はpc2_cとする)
PC2で下記のコマンドを実行します。
MakeMyIndex /d C:\dir2 /s \\PC2\pc2_c\dir2 /o C:\MyIndex\PC2.C.dir2.txt

PC2 の C:\MyIndex\PC2.C.dir2.txt を PC1 の C:\MyIndex\ にコピーします。

PC1 で SearchMyIndex を実行します。

PC1のファイルは、
C:\dir1\ファイル1-1.txt
などと表示され、
PC2のディレクトリは、
\\PC2\pc2_c\dir2\subdir1
などと表示されます。

7. 使用例(バックアップフォルダを同時に開く)

C:\の共有名はpc1_c、D:\の共有名はpc1_dとする。
(インストールパス)\MakeMyIndex /d C:\dir3 /s \\PC1\pc1_c\dir3 /o C:\MyIndex\PC1.C.dir3.txt /b D:\dir3_backup /bs \\PC1\pc1_d\dir3_backup

/bや/bsを指定してバックアップディレクトリやバックアップ共有フォルダを指定すると、バックアップディレクトリの同じディレクトリを開いてから対象のディレクトリを開きます。/dと使用して下さい。/fでは使用しないで下さい。

8. 使用例(PC 1台)詳細

下記のようにファイル・ディレクトリが存在するものとします。

C:\
├─dir1
│  │  ファイル1-1.txt
│  │  ファイル1-2.txt
│  │  
│  ├─subdir1
│  │      ファイル1-1-1.txt
│  │      ファイル1-1-2.txt
│  │      
│  └─subdir2
│          ファイル1-2-1.txt
│          ファイル1-2-2.txt
│          
├─dir2
│  │  ファイル2-1.txt
│  │  ファイル2-2.txt
│  │  
│  ├─subdir1
│  │      ファイル2-1-1.txt
│  │      ファイル2-1-2.txt
│  │      
│  └─subdir2
│          ファイル2-2-1.txt
│          ファイル2-2-2.txt

下記のコマンドを実行します。インデックスファイル名は任意です。

C:\dir1\ 配下のファイルのインデックスを作成
(インストールパス)\MakeMyIndex /f C:\dir1 /o C:\MyIndex\C.dir1.txt

C:\dir2\ 配下のディレクトリのインデックスを作成
(インストールパス)\MakeMyIndex /d C:\dir2 /o C:\MyIndex\C.dir2.txt

下記のファイルが出力されます。

C.dir1.txt
computername=PC1
dir=C:\dir1

subdir1\ファイル1-1-1.txt
subdir1\ファイル1-1-2.txt
subdir2\ファイル1-2-1.txt
subdir2\ファイル1-2-2.txt
ファイル1-1.txt
ファイル1-2.txt
C.dir2.txt
computername=PC1
dir=C:\dir2

subdir1
subdir2

SearchMyIndex を実行します。
検索文字列を入力せずにEnterを押下して検索を行うと、インデックス内が全て表示され、下記の表示となります。

20201125.png

9. インデックスディレクトリの変更

インデックスディレクトリは C:\MyIndex 固定です。
変更する場合には、C:\MyIndex を作成してから SearchMyIndex を実行し、終了させると、C:\Users\(ユーザー名)\AppData\Local\SearchMyIndex\SearchMyIndex.exe_Url_(英数字)\1.0.0.0\user.config に設定ファイルが保存されます(.NET標準)ので、この中の既存の<setting>の並びに下記の記述を追加し、値を任意のディレクトリに変更して下さい。

            <setting name="MyIndexDir" serializeAs="String">
                <value>C:\MyIndex</value>
            </setting>

10. 既知の問題

1) SearhMyIndex: C:\MyIndex (変更した場合には変更先のインデックスディレクトリ)が存在しないと、何も表示せずに終了します。
2) SearhMyIndex: 同じフルパスファイルを2回以上読み込むと例外が発生します。重複しないようにインデックスを作成して下さい。
3) SearhMyIndex: Enterキーで検索を行うと ぽーん と音が鳴る。(検索ボタンで検索を行うと音が鳴らない。鳴らないほうが正しい)

11. 更新履歴

MakeMyIndex

2020/11/25 1.00
1) 新規作成。

SearchMyIndex

2020/11/26 1.01
1) ソート順をエクスプローラ準拠に変更。
2) 検索テキストボックスと検索結果リストボックスの間をPageDown、PageUpで移動(↓、↑と同じ)
3) AND検索、OR検索対応。ANDは検索文字列を半角スペース区切り、ORは a | b 、a | b | c 等の形式。

2020/11/25 1.00
1) 新規作成。

12. 参考

エクスプローラのファイル順のように、自然順ソートで並び替える - DoboWiki

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

ASP.NET core + EF core コマンド「add-migration」にてエラー:Cannot resolve scoped service~ from root provider.

環境:ASP.NET core 3.1, Visual Studio 2019

以下の通り、同じDbContextを2つ登録していたのが原因。
InMemoryの方を消したら通った。

    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // InMemory
            services.AddDbContext<AppDbContext>(options =>
                options.UseInMemoryDatabase(nameof(AppDbContext))
                .ConfigureWarnings(m => m.Ignore(InMemoryEventId.TransactionIgnoredWarning))
                );

            // Postgres
            services.AddDbContextPool<AppDbContext>(options =>
            {
                options.UseNpgsql(Configuration.GetConnectionString(typeof(AppDbContext).Name));
            });
省略
        }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LINQによるデータ操作(2)

こんにちは、Mottyです。
前回に引き続き、LINQによる操作をつらつらとまとめていきます。

抽出(複数)

指定した条件の項目を"複数"返してくれるものとなります。
前回まとめたものよりもこちらのほうがよく使うのではないでしょうか。

Where

Whereは合致する条件のものを抽出するメソッドです。一番見るんじゃないかっていうぐらい頻出です。

List<int> intlist = new List<int> { 1, 1, 2, 3, 4, 5, 5, 6, 7, 7, 8, 9, 10 };
var tmp1 = intlist.Where(x => x >= 5);
foreach (var intNum in tmp1)
{
    Console.WriteLine(intNum);
}
//output: 6,7,7,8,9,10

//該当する条件がない場合
 var tmp2 = intlist.Where(x => x == 100);
  Console.WriteLine(tmp2.Count());
//output: 0

Distinct

Distinctは一意のデータを抽出するメソッド。重複データを削除した結果を返してくれます。

var tmp3 = intlist.Distinct().ToList();
foreach (var intNum in tmp3)
{
    Console.WriteLine(intNum);
}
Console.ReadKey();
//output: 1,2,3,4,5,6,7,8,9,10

Skip

Skipはリストの先頭から指定した数だけスキップした要素の配列を返してくれるメソッドです。例えば先頭3つをスキップしたら以下のようになります。

var tmp4 = intlist.Skip(3);
foreach (var intNum in tmp4)
{
Console.WriteLine(intNum);
}
Console.ReadKey();
//output: 3,4,5,5,6,7,7,8,9,10

take

Takeはリストの先頭から指定した数だけの要素を取り出すメソッドです。

var tmp5 = intlist.Take(5);
foreach (var intNum in tmp5)
{
    Console.WriteLine(intNum);
}
Console.ReadKey();
//output: 1,1,2,3,4

判定

LINQにはデータの性質調べてをbool値で返してくれるメソッドがあります。こちらも非常に便利なもので、仕事でもよく使っております。

All

要素のすべてが指定した条件を満たしているかを確認します。

List<int> SameList = new List<int> {1,1,1,1,1};
List<int> NotSameList = new List<int> { 1, 1, 2, 3, 4 };

Console.WriteLine(SameList.All(x => x == 1));
//true

Console.WriteLine(NotSameList.All(x => x == 1));
//false

Any

要素のいずれかが指定した条件を満たしているかを確認します。

Console.WriteLine(SameList.Any(x => x >= 4));
//false
Console.WriteLine(NotSameList.Any(x => x >= 4));
//true

Contains

要素のいずれかが指定した要素を含んでいるかどうかを確認します。こちらはAnyと違い、引数がラムダ式条件ではなく文字や数字となります。

Console.WriteLine(SameList.Contains(2)); 
//false
Console.WriteLine(NotSameList.Contains(2)); 
//true

まとめ

LINQはまだまだ他にもメソッドが用意されていますので第三弾もやりたいと思います。全部網羅するときりがないので、次はJoinと和集合・差集合あたりの部分をまとめていく予定です。
・Microsoft.Docs LINQ
https://docs.microsoft.com/ja-jp/dotnet/standard/linq/
・地平線に行く(LINQ構文を網羅)
https://yujisoftware.hatenablog.com/entry/20111031/1320068429

追記

@albireo さんよりご指摘いただきました、要素数0のリストに対してAll()を使うと
返り値がTrueになるという事実を確認しました。
参考までに記載しておきます。

List<int> SameList = new List<int> {1,1,1,1,1};
SameList.Clear(); //要素を全消去
Console.WriteLine(SameList.Count()); //0
Console.WriteLine(SameList.All(x => x == 1)); //True
Console.WriteLine(SameList.Any(x => x == 1)); //False
Console.ReadKey();
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C# PowerPointファイルのプロパティを設定・読み込む

PowerPointファイルのプロパティは ファイルの内容を示し、そのファイルを特定するためのものです。 ドキュメントのプロパティには、タイトル、作成者名、件名などのほかに、ドキュメントのトピックや内容を特定するキーワードがあります。
この記事では Spire. Presentationを通じてPowerPointファイルのプロパティを設定・読み込む方法をご紹介していきます。

下準備

1.E-iceblueの公式サイトからFree Spire. Presentation無料版をダウンロードしてください。


2.Visual Studioを起動して新規プロジェクトを作成してから、インストールされたファイルにあった相応しいSpire. Presentation.dllを参照に追加してください。
(Net 4.0を例としたら、デフォルトパスは“Bin→NET4.0→Presentation.dll”というようになります。)


プロパティの設定


using Spire.Presentation;
namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{

//PPTを作成します。
Presentation ppt = new Presentation();

//プロパティを設定します。
ppt.DocumentProperty.Title = "パンダについて";
ppt.DocumentProperty.Subject = "パンダの竹";
ppt.DocumentProperty.Author = "パンダンさん";
ppt.DocumentProperty.Manager = "パンダのマネジャー";
ppt.DocumentProperty.Company = "パンダ会社";
ppt.DocumentProperty.Category = "動物";
ppt.DocumentProperty.Keywords = "パンダ";
ppt.DocumentProperty.Comments = "パンダさんのお嫁さんってだれ?";

//保存します。
ppt.SaveToFile("プロパティ.pptx", FileFormat.Pptx2013);
}
 } 
  }

プロパティを読みこむ


using Spire.Presentation;
using System;
namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{

//PPTをロードします。
Presentation ppt = new Presentation();
ppt.LoadFromFile("プロパティ.pptx");

//プロパティを読み込みます。
Console.WriteLine("タイトル: " + ppt.DocumentProperty.Title);  
Console.WriteLine("サブタイトル: " + ppt.DocumentProperty.Subject);
Console.WriteLine("作成者: " + ppt.DocumentProperty.Author);
Console.WriteLine("管理者: " + ppt.DocumentProperty.Manager);
Console.WriteLine("会社名: " + ppt.DocumentProperty.Company);
Console.WriteLine("分類: " + ppt.DocumentProperty.Category);
Console.WriteLine("キーワード: " + ppt.DocumentProperty.Keywords);
Console.WriteLine("コメント: " + ppt.DocumentProperty.Comments);

Console.ReadKey();
}
 }
  }














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

C#でテキストファイル読み込み

テキストファイルの読み込み

using(var st = new System.IO.StreamReader("test.txt"))
{
    string line;
    while(null != (line = st.ReadLine())
    {
        //line に一行ずつ入る (改行コードは入っていない)
    }
}

.Net2.0 くらいからこの方法で読んでいる。
その頃、var はなかったけどね。
while の中がごちゃっとしてて入門者には説明が面倒なのと、
line のスコープが微妙に広いのが難点だけど、これが一番シンプル。

昔からあるファイル関係のクラスは、テキスト、ストリーム、staticメソッド、抽象クラス、継承とか
ごちゃごちゃしてるから「テキスト読み込みならこう書く」と決めておくのがいいと思う。

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

【C#】csc.exeを使用したコンパイラ

目次

コンソールアプリケーションのコンパイル

%csc.exeが配置されているファイルパス% /target:exe %finename%

フォームアプリケーションのコンパイル

%csc.exeが配置されているファイルパス% /target:winexe %finename%

参考記事

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

.NET Core 3.1 の Windows Forms で PerMonitorV2 動かしてみた

先日やった .NET Framework 4.7.2 + Windows Forms の PerMonitorV2 での動作確認 ですが LTS 版の .NET Core 3.1 でも試してみました。

ちなみに元記事に追記していますが、ListBox の変な動きは .NET Framework 4.8 にしたらなおってました。めでたしめでたし。

やってみよう

.NET Core 3.1 では、.NET Framework と違って Main メソッドで Application.Run(new Form1()); より前に Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); を呼ぶことで PerMonitorV2 になるらしいです。

WinForms の高 DPI

ということでフォームの AutoScaleMode を DPI にして ListBox と DataGridView を置いて試してみました。

100%
image.png

150%
image.png

100%
image.png

ListBox の愉快な症状はおきなかったですが、DataGridView の CheckBox は 150% に持って行っても小さいままでした。
ここら辺は自前でどうにか頑張る(頑張り方は自分は知らない…)しかないのかなぁ?

ここまでのまとめ

.NET Framework 4.8 と .NET Core 3.1 では ListBox と DataGridView を見る限りは動きはそんなに変わってないように見えるので、あとは、DataGridView のようにそのまま使っただけではちゃんと動かないコントロールの対応方法などを確認しないといけないかも。

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

Serilogでクレームの情報をログに出力しよう

今回の内容

 認証サーバーから受け取ったクレームに含まれる情報をログの情報として合わせて出力したいという要望があります。Serilogを利用してログにクレームの情報を出力する方法について見ていきます。

Serilogで常に特定の値をログに出力するには?

 .NETで今や標準と言ってもよいロガーのSerilogには、NLogのMappedDiagnosticsLogicalContextやlog4netのLogicalThreadContextと同じように、特定のコンテキストの中で常に特定の値をプロパティーとしてログに出力するためのLogContextクラスが存在します。

 例えばSerilogの出力フォーマットをJsonFormatterに設定したうえで次のようにログを出力すると、

using (LogContext.PushProperty("Prop1", "ほげ"))
using (LogContext.PushProperty("Prop2", "ほげほげ"))
{
    Log.Information("ログだよー");
}

 usingのコンテキスト内であれば、出力されたログのProperties要素の中にPushPropertyしたProp1やProp2が常に出力されるようになります。

{
  "Timestamp": "2020-11-25T08:16:49.1342176+09:00",
  "Level": "Information",
  "MessageTemplate": "ログだよー",
  "Properties": {
    "Prop2": "ほげほげ",
    "Prop1": "ほげ",
    "ActionId": "1e55d24a-bb44-479f-9a13-2233bae73eff",
    "ActionName": "TraceContextSample.WebApp2.Controllers.HomeController.Index (TraceContextSample.WebApp2)",
    "RequestId": "0HM4GPS20RBMN:00000001",
    "RequestPath": "/",
    "SpanId": "|353d86ac-4da39cde8fd02b90.",
    "TraceId": "353d86ac-4da39cde8fd02b90",
    "ParentId": ""
  }
}

クレーム情報をログに吐き出すには?

 では、クレームの情報を出力するとしたらどのような方法があるでしょうか。良い、悪いは別として方法としては次の5つがぱっと思い浮かびます。1,2はべったり実装する方法なので特に詳しい説明は必要ないでしょう。修正の影響範囲が大きいのであまりお勧めはしません。

  1. 各コントローラーやページでPushPropertyする
  2. コントローラーのベースクラスでPushPropertyする
  3. アクションフィルター(orページフィルター)でLogContextにPushPropertyする
  4. ミドルウェアでLogContextにPushPropertyする(おすすめ)
  5. Enricherを作ってAddPropertyIfAbsentする(おすすめ)

今回は2,3,4について説明して行きます。

subクレームのマッピング

 ASP.NETの認証ミドルウェアは、JWTに含まれるクレーム情報をHttpContextのクレーム情報に格納してくれます。ただし、subクレームなどの一部のクレーム情報はJWTハンドラーによって変換されるため、subクレームのまま利用したい場合はStartup時にClaimTypeのマッピングをクリアしておく必要があります。

Startup.cs
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // ... 略 ...
        JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
        // ... 略 ...
    }
}

アクションフィルター(orページフィルター)でLogContextにPushPropertyする

 コントローラーに対するアクションフィルターやRazorPageに対するページフィルターを作成して、各コントローラーやページにフィルターを適用する方法です。一見コントローラーに属性を設定するだけで、実装を後から追加できるので嬉しいように見えますが、コントローラーやページの外で発生したログに関してはフィルターが適用されないため値が出力されないというデメリットがあります。

 まずはアクションフィルターを作ります。現在のHTTPContextから認証済みユーザーの認証情報を抜き出し、subクレームの情報をLogContextに追加しています。

ClaimsLogFilter.cs
public class ClaimsLogFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        var user = context.HttpContext.User;
        if (user.Identity.IsAuthenticated)
        {
            var ciamls = user.Identities.First().Claims;
            var userId = ciamls.FirstOrDefault(c => c.Type.ToLower() == "sub");
            if (userId != null)
            {
                LogContext.PushProperty("UserId", userId);
            }
        }
    }
}

 あとは、コントローラーか、アクションメソッドに属性を付けるだけですね。

HomeController.cs
public class HomeController : Controller
{
    [Authorize]
    [ClaimsLogFilter]
    public async Task<IActionResult> Secret()
    {
    }
}

 場合によってStartup時にフィルターを追加して全コントローラーに適用しても良いですね。

Startup.cs
public class Startup
{
    /// ... 略 ...

    public void ConfigureServices(IServiceCollection services)
    {
        /// ... 略 ...
        services.AddControllersWithViews(options =>
        {
            options.Filters.Add(typeof(ClaimsLogFilter));
        });
        /// ... 略 ...
    }
}

参考

ミドルウェアでLogContextにPushPropertyする

 ASP.NET Coreのミドルウェアを利用してリクエストのパイプラインの途中でLogContextにコンテキストを追加する方法です。アクションフィルター同様HttpContextから現在のユーザーに紐づくクレーム情報を取り出し、PushPropertyしています。

ClaimsLoggingMiddleware.cs
public class ClaimsLoggingMiddleware
{
    private readonly RequestDelegate _next;

    public ClaimsLoggingMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext httpContext)
    {
        var user = httpContext.User;
        if (user.Identity.IsAuthenticated)
        {
            var ciamls = user.Identities.First().Claims;
            var userId = ciamls.FirstOrDefault(c => c.Type.ToLower() == "sub");
            if (userId != null)
            {
                using (LogContext.PushProperty("UserId", userId?.Value))
                    await _next(httpContext);
            }
        }
    }
}

 重要なのはミドルウェアを追加するタイミングです。認証情報はAuthenticationMiddlewareによって設定されるので、UseAuthenticationメソッドが呼ばれた後にClaimsLoggingMiddlewareを組み込まないと値を取得することができません(常にnullになる)。

Startup.cs
public class Startup
{
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        /// ... 略 ...
        app.UseAuthentication();
        app.UseAuthorization();

        app.UseMiddleware<ClaimsLoggingMiddleware>();
        /// ... 略 ...
    }
}

参考

Enricherを作ってAddPropertyIfAbsentする

 最後にSerilogの拡張機能であるEnricher(付与)を利用する方法です。少し複雑になりますが、ログを出力するという用途から考えるとこの方法が一番正しい気がします。ただ、現時点ではHttpContextを参照するようなEnricherを利用する場合は依存関係を解決するタイミングのせいで、Loggerの初期化を遅らせる必要があるというデメリットがあります。

 まず、ILogEventEnricherインターフェイスを実装して、ClaimsEnricherクラスを作成します。アクションフィルターやミドルウェアと違いHttpContextを引数にもらえないので、コンストラクタにIHttpContextAccessorインターフェイスを受け取りDIしてもらいましょう。subクレームの参照方法は他の方法と変わらないですね。

ClaimsEnricher.cs
public class ClaimsEnricher: ILogEventEnricher
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public ClaimsEnricher(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }
    public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
    {
        var user = _httpContextAccessor.HttpContext?.User;
        if (user != null && user.Identity.IsAuthenticated)
        {
            var ciamls = user.Identities.First().Claims;
            var userId = ciamls.FirstOrDefault(c => c.Type.ToLower() == "sub");
            if (userId != null)
            {
                var userIdProperty = propertyFactory.CreateProperty("UserId", userId);
                logEvent.AddPropertyIfAbsent(userIdProperty);
            }
        }
    }
}

 StartupではClaimsEnricherIHttpContextAccessorの依存を設定します。

Startup.cs
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // ... 略 ...
        services.AddTransient<ClaimsEnricher>();
        services.AddHttpContextAccessor();
        // ... 略 ...
    }
}

 プログラムのエントリーポイントでは、アプリケーションビルダーのUseSerilog初期化時に、Serilogの構成を行い、ConfigureServicesで設定したClaimsEnricherをDIコンテナから取り出し適用します。残念ながらDIコンテナの初期化のタイミングの影響で、Main直後にロガーの設定ができないので、アプリケーション起動時のログは平文で出力されてしまいます。

Program.cs
public class Program
{
    public static int Main(string[] args)
    {
        //本来はここでロガーを初期化したい。
        //Log.Logger = new LoggerConfiguration()
        //    .MinimumLevel.Information()
        //    .Enrich.FromLogContext()
        //    .WriteTo.Console(formatter: new JsonFormatter())
        //    .CreateLogger();
        try
        {
            Log.Information("Starting host...");

            CreateHostBuilder(args).Build().Run();
            return 0;
        }
        catch (Exception ex)
        {
            Log.Fatal(ex, "Host terminated unexpectedly.");
            return 1;
        }
        finally
        {
            Log.CloseAndFlush();
        }
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .UseSerilog(
                (context, services, configuration) =>
                configuration.
                    MinimumLevel.Information()
                    .Enrich.FromLogContext()
                    .Enrich.With(services.GetService<ClaimsEnricher>())
                    .WriteTo.Console(formatter: new JsonFormatter())

                )
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

 これに対応するために、Serilogの次?のバージョンでは、CreateBootstrapLoggerメソッドが追加され、Serilogの初期化を複数段階に分けて設定できるようになるらしいです。→ Bootstrap logging with Serilog + ASP.NET Core

参考

まとめ

 いろいろ方法はありますが、ミドルウェアを利用するか、Enricherを設定する方法が良さそうですね。

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

System.Diagnostics.Activityのオーバーヘッド

はじめに

ActivitySourceの記事で一通り機能は解説できていたと思うが、この記事では、実際にActivityを有効にしたときのオーバーヘッドを確認してみたいと思う。

テストプログラム

テストプログラムは gistにアップロードしている

ここでは以下のようなベンチマークを実施した。

テスト名 使用クラス 有効 備考
CreateActivityOnly - - new Activity()のオーバーヘッドの計測のため
DiagnosticSource_Disabled DiagnosticSource ×
DiagnosticSource_Enabled DiagnosticSource
ActivitySource_Disabled ActivitySource ×
ActivitySource_None ActivitySource ActivitySamplingResultにNoneを指定
ActivitySource_Propagation ActivitySource ActivitySamplingResultにPropagationDataを指定
ActivitySource_AllData ActivitySource ActivitySamplingResultにAllDataを指定
ActivitySource_AllDataAndRecorded ActivitySource ActivitySamplingResultにAllDataAndRecordedを指定

結果

結果は以下のようになった。

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.1139 (1909/November2018Update/19H2)
Intel Core i7-9700 CPU 3.00GHz, 1 CPU, 8 logical and 8 physical cores
.NET Core SDK=5.0.100
  [Host]   : .NET Core 5.0.0 (CoreCLR 5.0.20.51904, CoreFX 5.0.20.51904), X64 RyuJIT
  ShortRun : .NET Core 5.0.0 (CoreCLR 5.0.20.51904, CoreFX 5.0.20.51904), X64 RyuJIT

Job=ShortRun  IterationCount=3  LaunchCount=1  
WarmupCount=3  

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
CreateActivityOnly 10.6031 ns 1.6297 ns 0.0893 ns 0.0280 - - 176 B
DiagnosticSource_Disabled 0.5074 ns 0.1019 ns 0.0056 ns - - - -
DiagnosticSource_Enabled 533.8810 ns 118.0714 ns 6.4719 ns 0.1135 - - 712 B
ActivitySource_Disabled 14.6574 ns 0.3357 ns 0.0184 ns - - - -
ActivitySource_None 65.7799 ns 3.1931 ns 0.1750 ns - - - -
ActivitySource_Propagation 635.7652 ns 193.5533 ns 10.6093 ns 0.1001 - - 632 B
ActivitySource_AllData 616.2310 ns 10.3009 ns 0.5646 ns 0.1001 - - 632 B
ActivitySource_AllDataAndRecorded 633.3614 ns 208.1648 ns 11.4102 ns 0.1001 - - 632 B

考察

有効無効の差

まず、有効と無効の有無で約500~600nsの差が出ていることは大きな特徴として言えるだろう。
CreateActivityOnly(=newのみ)の結果は10ns程度で、ほとんどコストがかかっていないことから、Activity開始処理の中で様々な属性をセットしていることが起因していると思われる。

ActivitySourceとDiagnosticSourceの比較

また、DiagnosticSourceとActivitySourceで比較した場合、若干DiagnosticSourceの方がオーバーヘッドが少ないように見える。
これは、後述するActivitySamplingResultのチェック等も含まれているためと思われる。
ただし、DiagnosticSource経由で使用する場合は使い勝手が悪いため、この辺りはどちらを優先するかという問題になると思う。

ActivitySamplingResultによる違い

それぞれのフラグを比較すると、ActivitySamplingResult.Noneの時が特別に低い状態になっている。
これは同フラグを指定するとActivityを生成しないという動作をするので、オーバーヘッドがフラグのチェックのみになっているためと思われる。
他のフラグについては、今回計測した範囲では有意な差は無かった。
これは、None以外はActivityを構築するアプリケーションが参考にするフラグなので、内部的な動作にはほぼ影響しないということだろう。
ただし、アプリケーション側ではフラグを見てActivityにデータやイベントを追加するということは十分に考えられるので、無意味なフラグというわけではない。

まとめ

今回Activity自体のオーバーヘッドを測ったが、大きくても一回当たり1usec以下なので、これを気にするかどうかはユーザー次第であることは忘れないでおきたい。
シビアな状況においては、有効と無効の差はかなり大きいので、留意しておきたい。
また、オーバーヘッドが気になる状況でも、ActivitySamplingResultによってある程度軽減はできるため、検討しておくといいと思う。

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

ASP.NET Core の Bootstrap を最新にする

はじめに

Visual Studio 2019 で、ASP.NET Core Web アプリケーションをテンプレートから作成すると、Bootstrap ライブラリがリンクされた状態で作成されます。
image.png

これはこれで全然ありがたいのですが、Bootstrap のバージョンが最新でなかったりします。
やりたいことが最新のバージョンでしかサポートされていないこともあります。

Visual Studio 2019 では、クライアント ライブラリ マネージャーが利用でき、管理下のクライアント ライブラリを容易に任意のバージョンに更新できます。

ここでは、ASP.NET Core Web アプリケーションのクライアント ライブラリをクライアント ライブラリ マネージャー LibMan で管理する方法を説明します。

非管理ライブラリの削除

[<対象プロジェクト>] - [wwwroot] - [lib] - [bootstrap] を選択し、右クリック メニューから、[削除] で、"bootstrap" フォルダー以下を削除します。
(*) 元に戻せるように必ずバックアップをとってから実行してください。

image.png
削除に失敗する場合は、エディタ部を閉じてから削除を行ってください。

LibMan によるクライアント ライブラリの追加

[<対象プロジェクト>] - [wwwroot] - [lib] を選択し、右クリック メニューから、[追加] - [クライアント側のライブラリ...] を選択します。
image.png

[クライアント側のライブラリを追加します] ダイアログから、[プロバイダー] から、"jsdelivr" を選択、[ライブラリ] に "bootstrap" と入力すると、候補が一覧表示されるので、"bootstrap" を選択すると、その時に最新のバージョンに変換されます。今回は、"bootstrap@4.5.3" と、変換されました。

image.png
ここで、プロバイダーは、CDN のことで、以下から選択ができます。対象のライブラリが含まれている CDN を選択できます。

[インストール] を選択します。プロジェクトに、libman.json が追加されます。

libman.json
{
  "version": "1.0",
  "defaultProvider": "jsdelivr",
  "libraries": [
    {
      "library": "bootstrap@4.5.3",
      "destination": "wwwroot/lib/bootstrap/"
    }
  ]
}

<対象プロジェクト>\wwwroot\lib に "bootstrap" フォルダーが追加され、各種フォルダー、ファイルが追加されます。
image.png

クライアント ライブラリ マネジャー(LibMan) で管理すると何がよいかというと、libman.json を開いて、対象のライブラリを選択、クイック操作のアイコンを選択すると、最新版があるときに、ここから確認して、更新することができます。
image.png

同様の手順で、jQuery ライブラリも LibMan の管理下にしていきます。
<対象プロジェクト>\wwwroot\lib\jquery フォルダーを削除します。
[<対象プロジェクト>] - [wwwroot] - [lib] を選択し、右クリック メニューから、[追加] - [クライアント側のライブラリ...] を選択します。
[クライアント側のライブラリを追加します] ダイアログから、[プロバイダー] から、"jsdelivr" を選択、[ライブラリ] に "jquery" と入力すると、候補が一覧表示されるので、"jquery" を選択すると、その時に最新のバージョンに変換されます。今回は、"jquery@3.5.1" と、変換されました。
[インストール] を選択します。
image.png

同様に、<対象プロジェクト>\wwwroot\lib\ 以下の jquery-validation、jquery-validation-unobtrusive を削除、"jsdelivr" から、jquery-validation, jquery-validation-unobtrusive をそれぞれインストールします。
ここまでで、libman.json 以下のようになります。

libman.json
{
  "version": "1.0",
  "defaultProvider": "jsdelivr",
  "libraries": [
    {
      "library": "bootstrap@4.5.3",
      "destination": "wwwroot/lib/bootstrap/"
    },
    {
      "library": "jquery@3.5.1",
      "destination": "wwwroot/lib/jquery/"
    },
    {
      "library": "jquery-validation@1.19.2",
      "destination": "wwwroot/lib/jquery-validation/"
    },
    {
      "library": "jquery-validation-unobtrusive@3.2.11",
      "destination": "wwwroot/lib/jquery-validation-unobtrusive/"
    }
  ]
}

LibMan でクライアント ライブラリを管理する場合、[クライアント側のライブラリを追加します] ダイアログを使わず、libman.json を直接書き換えても構いません。

クライアント ライブラリへの参照の変更

最後に、bootstrap, jquery, jquery-validation, jquery-validation-unobtrusive 各ライブラリへのパスが変更されている場合は、<link>、<script> の参照パスを変更します。

ASP.NET Core Web アプリケーション プロジェクト テンプレートでは、_Layout.cshtml に *.css、*.js のクライアント ライブラリの参照がありますが、最近のコードでは、ローカルのライブラリを参照するようになっているため、参照を変更する必要がありません。

_Layout.cshtml
...
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
....
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
...

<environment exclude="Development"> で、非開発環境時には、CDN を参照するようにしている場合は、LibMan で管理しているバージョンと同様のバージョンを参照するように変更する必要があります。

参照サイト

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