20200721のC#に関する記事は6件です。

[C#]System.UriのベースURIと相対URIが思っていたのと違っていた話

URIの作成について

WebApiでURIにidを埋め込みたい場合に、id部分を+で連結するのを避ける方法を検討していた。
System.UriのベースURIと相対URIを設定すれば簡単にできると勘違いしていた。
例えば、"http://localhost:9999/api/5"とかの場合、以下の実装で問題ないはずだった。

            var uri = new Uri(new Uri(@"http://localhost:9999/api"), "/5");
            Debug.Print(uri.AbsoluteUri);

結果:"http://localhost:9999/5"
"/api"の部分が抜けている...

仕様の確認

MSのドキュメント を読むと、ベースURIの末尾の"/"と相対URIの先頭に"/"があるかで動作が変わるとのこと。

このコンストラクターは、baseUri と relativeUriを組み合わせることによって、Uri インスタンスを作成します。 relativeUri が絶対 URI (スキーム、ホスト名、および必要に応じてポート番号を含む) の場合、Uri インスタンスは relativeUriのみを使用して作成されます。
baseUri に相対部分 (/apiなど) がある場合、baseUri の相対部分が構築された Uriに保持される場合は、相対部分をスラッシュ (/api/など) で終了する必要があります。
さらに、relativeUri がスラッシュで始まる場合は、baseUri の任意の相対部分に置き換えられます。

動作確認

いくつかのパターンを試してみた。

            // 1. http://localhost:9999/api/5
            Debug.Print(new Uri(new Uri(@"http://localhost:9999/api/"), "5").AbsoluteUri);

            // 2. http://localhost:9999/5
            Debug.Print(new Uri(new Uri(@"http://localhost:9999/api"), "5").AbsoluteUri);

            // 3. http://localhost:9999/5
            Debug.Print(new Uri(new Uri(@"http://localhost:9999/api/"), "/5").AbsoluteUri);

            // 4. http://localhost:9999/api/5
            Debug.Print(new Uri(new Uri(@"http://localhost:9999/"), "/api/5").AbsoluteUri);

ベースURIと相対URIの定義次第だけど、4のようにすると、ベースURIの末尾"/"、相対URIの先頭"/"の有無は気にしなくて良くなる。

            // 5. http://localhost:9999/api/5
            Debug.Print(new Uri(new Uri(@"http://localhost:9999/"), "api/5").AbsoluteUri);

            // 6. http://localhost:9999/api/5
            Debug.Print(new Uri(new Uri(@"http://localhost:9999"), "/api/5").AbsoluteUri);

まとめ

ベースURIの末尾に"/"がない場合、ベースURIに含まれる相対部分が相対URIに置き換わる。
ベースURIの先頭に"/"がある場合、ベースURIに含まれる相対部分が相対URIに置き換わる。

補足

私のプロジェクトの場合、ベースURIと相対URIは詳細な形式は不明であり、末尾"/"、先頭"/"の不明はわからない状態だった。
結局、ベースURIの末尾"/"がなければ"/"を追加し、相対URIの先頭に"/"があれば削除してSystem.Uriクラスを使用したが、正直+連結で十分だった。

目次

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

C# でお手軽スクレイピング 2020 年夏よりちょっと未来

@otchy さんの Node でお手軽スクレイピング 2020 年夏 を見て自分も C# でやってみようと思いました。

HTML パーサー

C#でモダンにスクレイピングするならAngleSharp で紹介されている AngleSharp を使ってみようと思います。

C# 9.0 でやろう

node.js とかのお手軽なところとして CLI でサクッと作れてファイルにおもむろに console.log('xxx'); のように書き始められるところがあると思います。
C# 9.0 (現時点ではまだプレビュー) でも トップ レベル ステートメント という機能が追加されて、同じ要領で書けるようになります。素敵。

CLI でさくっと

.NET 5 のプレビュー SDK を入れます。VSCode の C# の拡張機能も入れておきましょう。

私が試した .NET の SDK のバージョンは以下の通りです。

> dotnet --version
5.0.100-preview.6.20318.15

では、コンソール アプリのプロジェクトを作って HTML パーサーのパッケージを追加します。どうせ、ちょっと未来を体験するつもりなので最新のプレビュー版を入れることにしました。

コマンドとしては以下のようになります。

> dotnet new console
> dotnet add package AngleSharp --version 1.0.0-alpha-827

拡張子が csproj のファイルを開いて言語のバージョンをプレビューにしておきます。最新の VS2019 プレビューだとデフォルトが C# 9.0 になってるっぽいけど、この定義を追加しないで CLI からビルドするとエラーになった。何が違うんだろう…。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0</TargetFramework>
    <LangVersion>preview</LangVersion> <!-- この行追加 -->
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="AngleSharp" Version="1.0.0-alpha-827" />
  </ItemGroup>

</Project>

コードは、以下のような感じになります。Main メソッドと、それを囲むクラスや名前空間とかの定義がまるっといらなくなって凄くすっきりしますね。

Program.cs
// 必要な名前空間を using
using System;
using System.Linq;
using System.Net.Http;
using AngleSharp.Html.Parser;

// ページの HTML を取得
var client = new HttpClient();
var res = await client.GetStringAsync("https://www.jma.go.jp/jp/week/319.html");
// HTML をパース
var parser = new HtmlParser();
var doc = await parser.ParseDocumentAsync(res);
// セレクターで目的の要素をゲット
var nodes = doc.QuerySelectorAll("#infotablefont tr:nth-child(4) td");

// 整形して出力
var tokyoWeathers = string.Join(", ", nodes.Select(x => x.TextContent.Trim()));
Console.WriteLine(tokyoWeathers);

実行してみましょう。dotnet run で実行できます。

> dotnet run
曇のち時々雨, 曇一時雨, 曇, 曇, 曇時々晴, 曇時々晴, 曇

因みに exe にしたかったら dotnet publish コマンドで出来ます。本番用なら -c Release にすることでリリース モードでのビルドです。

> dotnet publish -c Release
.NET 向け Microsoft (R) Build Engine バージョン 16.7.0-preview-20310-07+ee1c9fd0c
Copyright (C) Microsoft Corporation.All rights reserved.

  復元対象のプロジェクトを決定しています...
  復元対象のすべてのプロジェクトは最新です。
  プレビュー版の .NET Core を使用しています。https://aka.ms/dotnet-core-preview をご覧ください
  SampleApp -> C:\Labs\SampleApp\bin\Release\net5.0\SampleApp.dll
  SampleApp -> C:\Labs\SampleApp\bin\Release\net5.0\publish\

プロジェクトの bin\Release\net5.0\publish フォルダーに、こんな感じの成果物が出来上がります。

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---          2020/07/14    23:29         832512 AngleSharp.dll
-a---          2020/07/21    19:35           2524 SampleApp.deps.json
-a---          2020/07/21    19:35           6656 SampleApp.dll
-a---          2020/07/21    19:35         144896 SampleApp.exe
-a---          2020/07/21    19:35           9676 SampleApp.pdb
-a---          2020/07/21    19:35            165 SampleApp.runtimeconfig.json

ここではやりませんが、やろうと思えばランタイム不要な単一 exe にもビルド出来ます。では、exe も実行してみましょう。

> .\SampleApp.exe
曇のち時々雨, 曇一時雨, 曇, 曇, 曇時々晴, 曇時々晴, 曇

当然ですがちゃんと動きました。.NET Core 2.1 以降は .NET Core ツールという仕組みもあって、これを使うと npm install でインストールできるツールのようなのと同じ感覚でインストール可能なツールを作ることが出来ます。

.NET Core ツールの管理方法

GUI でも

Visual Studio 2019 Preview 版を入れると CLI でやってたことが GUI でもぽちぽちできます。

image.png

Visual Studio Code でもコードを書けますが追加するパッケージを GUI で確認したり、コード補完が Code よりも強力な部分があったりと、まだ VS Code より多少重くても(個人の感覚)C# を書く場合には VS 2019 のほうを使ってしまいます。

まとめ

C# でもスクレイピングをさくっと出来たのと C# 9.0 のトップ レベル ステートメントは、単純にコードが短くなって素敵なので正式リリースが待ち遠しいです。

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

[C#]AWSの使用料金をAPIで取得する

AWSの使用料金がえらいことに・・・

思いのほかAWSの使用料金が上がっていて、青ざめた経験が誰しもあるはず。
定期的に請求ページを確認すればいいのですが、休日は見てなかったりして
気づいたときは多額の請求がきていた…なんてことも。

そこで、CostExplorer APIを使っていつどのくらい使用したのかわかるようにしてみました。
※CostExplorer APIはリクエストごとに費用が発生するのでやりすぎに注意しましょう!
(記載時点で1回のリクエストにつき0.01ドル)

必要なもの

AWSのAccessKeyとSecretKey
AWSSDK.CostExplorerをインストール

コード(Console)

            string awsAccessKey = "xxxxxxxxxxx";
            string awsSecretKey = "xxxxxxxxxxxxxxxxxxxxx";

            AmazonCostExplorerClient client = new AmazonCostExplorerClient(awsAccessKey, awsSecretKey, RegionEndpoint.USEast1);
            GetCostAndUsageRequest req = new GetCostAndUsageRequest();
            req.TimePeriod = new DateInterval();
            req.TimePeriod.Start = "2020-07-01";
            req.TimePeriod.End = "2020-07-20";
            req.Granularity = Granularity.DAILY;
            req.Metrics = new List<string>() { "AMORTIZED_COST" };

            GetCostAndUsageResponse cost = client.GetCostAndUsage(req);
            for (int i = 0; i < cost.ResultsByTime.Count; i++) {
                string start = cost.ResultsByTime[i].TimePeriod.Start;
                string end = cost.ResultsByTime[i].TimePeriod.End;
                foreach (string key in cost.ResultsByTime[i].Total.Keys) {
                    decimal amount = decimal.Parse(cost.ResultsByTime[i].Total[key].Amount);
                    string unit = cost.ResultsByTime[i].Total[key].Unit;
                    Console.WriteLine(start + "~" + end + " Amount=" + amount + " Unit=" + unit);
                }
            }


結果

2020-07-01~2020-07-02 Amount=47.6436917916 Unit=USD
2020-07-02~2020-07-03 Amount=47.2799385496 Unit=USD
2020-07-03~2020-07-04 Amount=0.0012009547 Unit=USD
2020-07-04~2020-07-05 Amount=0.001352335 Unit=USD
2020-07-05~2020-07-06 Amount=0.0013171085 Unit=USD
2020-07-06~2020-07-07 Amount=0.0013171085 Unit=USD
2020-07-07~2020-07-08 Amount=0.0013171085 Unit=USD
2020-07-08~2020-07-09 Amount=0.0013171085 Unit=USD
2020-07-09~2020-07-10 Amount=0.2201781931 Unit=USD
2020-07-10~2020-07-11 Amount=48.1395790234 Unit=USD
2020-07-11~2020-07-12 Amount=48.1393261349 Unit=USD
2020-07-12~2020-07-13 Amount=48.1394533349 Unit=USD
2020-07-13~2020-07-14 Amount=6.6676519934 Unit=USD
2020-07-14~2020-07-15 Amount=0.0009903077 Unit=USD
2020-07-15~2020-07-16 Amount=0.0009903077 Unit=USD
2020-07-16~2020-07-17 Amount=0.0009903077 Unit=USD
2020-07-17~2020-07-18 Amount=0.0009903077 Unit=USD
2020-07-18~2020-07-19 Amount=0.0009903077 Unit=USD
2020-07-19~2020-07-20 Amount=0.0009903077 Unit=USD

10日~13日にやらかしてますね…
この時、DocumentDBの検証をやっていてインスタンス落とすの忘れてて
土日放置してたようです。

あとは、このレポートを定期的/閾値を超えたらメールで送ったり
メッセンジャーに送ったりすれば被害を低減できるでしょうか。

そもそも自分でちゃんと管理しろと言われそうですがね。

自分用の雑なコンソールアプリです。
閾値を超えたらメール送信するアプリ

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

【de:code2020学習記録】2020 年の最新 Xamarin 概要

はじめに

本稿は、de:code2020の以下のセッションで学んだ内容のメモです。

【A07】2020 年の最新 Xamarin 概要

動画がこちらで公開されました。

Xamarin 概要

  • C# で iOS、macOS、Android向けのアプリを作るためのプラットフォーム
  • Uno Pratoform や Mobile Bindings など他のクロスプラットフォームの土台としても使われる
  • AndroidやiOSのネイティブのAPIをC#から呼べる
  • 画面のネイティブの部品をそのまま利用可能
  • iOSを開発するためにはMacのPCが必要
  • iOSとAndroidのOSバージョンアップの対応は早い(iOSはOSリリースとほぼ同時、Androidは1ヶ月以内)

Xamarin.Forms 概要

  • C# で iOS、macOS、Android、Windows向けのアプリのユーザーインターフェイスをC#/XAMLで単一のコードで構築可能にしたUIフレームワーク

Xamarin 新機能

  • Android
    • Kotlinで開発されたライブラリのバインディングの改善
    • ダークテーマ対応
  • iOS
    • iPad用の複数Windowサポート
    • ダークモード対応
  • Xamarin.Forms
    • Shellコントロール(メニュー・タブ・検索ボックスなどのよくある基本機能を提供するコントロール)
    • Visual(コントロール全体の見た目を一気に変更できる、組み込みでマテリアルデザインを提供)
    • CollectionView(ListViewを置き換えるコントロール)
    • Gifアニメーションのサポート
    • C# Markup Extensions(Xamalを使わずC#だけで画面を作る)
    • Hot Reload(Xamalの変更時に即座にデバッグ中の画面に反映)

所感

Xamarinの最新情報をたくさん知れたので、とても有意義なセッションでした。

ちなみに、私はLT初心者が集まるコミュニティとして「Serverless LT 初心者向け」というコミュニティを運営しています。
Serverless LT 初心者向け - connpass

Twitterでも開発に役立つ情報を発信しています → @kojimadev

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

【C#】カメラでQRコード読み取り

前回のQRコード読み取りを利用して、カメラでQRコード解析してみました。

■参考記事
【C#】PCカメラから映像を取得するアプリ

事前準備

Nugetパッケージは、前回の記事同様に以下をインストール。
・OpenCvSharp3
・ZXing.Net

AForge.NET
※「[ Download Libraries Only ]」のリンクからzipをダウンロードします。

dll.png
「AForge.Video.DirectShow.dll」「AForge.Video.dll」のDLLを使用しました。

dll2.png
「参照の追加」でこの二つを追加。

コード

カメラの種類のコンボボックス

画面表示時の処理で呼ぶとよい。

    public void getCameraInfo()
    {
        try
        {
            // 端末で認識しているカメラデバイスの一覧を取得
            videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
            cmbCamera.Items.Clear();

            if (videoDevices.Count == 0)
                throw new ApplicationException();

            foreach (FilterInfo device in videoDevices)
            {
                //カメラデバイスの一覧をコンボボックスに追加
                cmbCamera.Items.Add(device.Name);
                cmbCamera.SelectedIndex = 0;
                DeviceExist = true;
            }
        }
        catch (ApplicationException)
        {
            DeviceExist = false;
            cmbCamera.Items.Add("Deviceが存在していません。");
        }

    }

開始/終了ボタン

    private void btnExec_Click(object sender, EventArgs e)
    {
        if (btnExec.Text == "開始")
        {
            if (cmbCamera.Items.Count == 0)
            {
                System.Windows.Forms.MessageBox.Show("カメラがありません。", "実行エラー");
                return;
            }
            if (DeviceExist)
            {
                videoSource = new VideoCaptureDevice(videoDevices[cmbCamera.SelectedIndex].MonikerString);
                videoSource.NewFrame += new NewFrameEventHandler(videoRendering);
                this.CloseVideoSource();

                videoSource.Start();

                btnExec.Text = "停止";
            }
        }
        else
        {
            if (videoSource.IsRunning)
            {
                this.CloseVideoSource();
                btnExec.Text = "開始";

            }
        }
    }

カメラ読み取り中のイベント

    private void videoRendering(object sender, NewFrameEventArgs eventArgs)
    {
        Bitmap img = (Bitmap)eventArgs.Frame.Clone();

        try
        {
            qrRead(img);
            pictureBoxCamera.Image = img;
        }
        catch (Exception ex)
        {
            System.Windows.Forms.MessageBox.Show(ex.Message + Environment.NewLine + ex.StackTrace);
        }
    }

QR読み取り処理

   private void qrRead(System.Drawing.Image image)
    {


        try
        {
            Bitmap myBitmap = new Bitmap(image);

            string text = string.Empty;

            using (Mat imageMat = OpenCvSharp.Extensions.BitmapConverter.ToMat(myBitmap))
            {

                // QRコードの解析
                ZXing.BarcodeReader reader = new ZXing.BarcodeReader();

                //テンポラリパス
                for (int i = 0; i < maxFilterSize; i++)
                {

                    //奇数にする必要があるので、さらに加算
                    i++;

                    //偶数を指定するとExceptionが発生します。
                    int filterSize = i;

                    //別変数のMATにとる
                    using (Mat imageMatFilter = imageMat.GaussianBlur(new OpenCvSharp.Size(filterSize, filterSize), 0))
                    {
                        //ビットマップに戻します
                        using (Bitmap filterResult = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(imageMatFilter))
                        {
                            try
                            {
                                //QRコードの解析
                                ZXing.Result result = reader.Decode(filterResult);

                                //これでQRコードのテキストが読める
                                if (result != null)
                                {
                                    text = result.Text;
                                    System.Windows.Forms.MessageBox.Show(text, "読み取り値"); //メッセージポップアップ
                                    return;
                                }
                            }
                            catch
                            {
                            }

                        }


                    }

                }
            }

        }
        catch (Exception ex)
        {
            //システムエラー発生時
            System.Windows.Forms.MessageBox.Show(ex.Message + Environment.NewLine + ex.StackTrace);
            this.Close();
        }
        finally
        {

        }
    }

git

ソースは一式、ここにあります。
某クラウドソーシングで、提案の際にプロトとして作ってみたものです。不採用でしたが・・。
コードは、整理しきれていませんが、プロトタイプということで・・。
https://github.com/ogayasuhito/QrcodeAndCamera.git

実行

対象のカメラの種類を選んで、「開始」ボタンで読み取り開始されます。
QRコードが読み取られると、メッセージボックスで内容が表示されます。(読み取り値のやつ)
カメラに写っているのは、下のピクチャーボックスに表示
qr.png

何かの機能追加の際とかに、コピペで使える・・かも?

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

【C#】荒い画像からQRコード読み取り

初投稿です。

以下の記事を参考にQRコード読み取りの案件があったので、実装しました。
【ZXing.Net】C#でQRコードの読取

ぼやけたQRコードや、少し斜めったQRコードも読み取れるように改造した内容です。

実装途中、以下のようなメモリリークしたり、画像ファイルのアクセス不正とかで嵌ったので、メモとして残しました。
OpenCvSharp で Mat を返すメソッドを使うとメモリリークで死ぬ件

Nugetパッケージのインストール

Nugetで「OpenCvSharp3」と「Zxing.Net」をインストールします。
nuget.png

コード

    public void qrRead(string filename)
    {
        //filename:画像ファイルパス(例:c:\\gazo.jpg)

        /// 画像フィルタリングのフィルタ値
        /// この値を上げると精度が上がっていくが、処理時間も増加する。
        /// ※適宜変更してください。
        const int maxFilterSize = 10;

        //画像ファイルをBitmapに変換
        System.Drawing.Image image = System.Drawing.Image.FromFile(filename);
        Bitmap myBitmap = new Bitmap(image);
        string data = string.Empty;

        //Mat形式に変換
        using (Mat imageMat = OpenCvSharp.Extensions.BitmapConverter.ToMat(myBitmap))
        {

            // QRコードの解析クラス
            ZXing.BarcodeReader reader = new ZXing.BarcodeReader();

            //画像フィルタリングのフィルタ値まで、ループ
            for (int i = 0; i < maxFilterSize; i++)
            {
                //奇数にする必要があるので、さらに加算
                i++;

                //偶数を指定するとExceptionが発生するので注意。
                int filterSize = i;

                //別変数のMATにとる
                using (Mat imageMatFilter = imageMat.GaussianBlur(new OpenCvSharp.Size(filterSize, filterSize), 0))
                {
                    //ビットマップに戻す
                    using (Bitmap filterResult = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(imageMatFilter))
                    {
                        //QRコードの解析
                        ZXing.Result result = reader.Decode(filterResult);
                        if (result != null)
                        {
                            //変数に退避
                            data = result.Text;
                        }
                    }
                }

                if (!string.IsNullOrEmpty(data))
                {
                    Console.WriteLine(data); //読み取り内容を出力
                    return;
                }
            }
        }

        Console.WriteLine("読み取れない!");

    }
}

このメソッド単体で動くはず。
読み取り精度を上げたい場合は、「maxFilterSize 」の値を増やすといい感じだけども、処理時間も増える。。

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