- 投稿日:2020-07-21T22:43:25+09:00
[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クラスを使用したが、正直+連結で十分だった。
- 投稿日:2020-07-21T19:49:46+09:00
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
でインストールできるツールのようなのと同じ感覚でインストール可能なツールを作ることが出来ます。GUI でも
Visual Studio 2019 Preview 版を入れると CLI でやってたことが GUI でもぽちぽちできます。
Visual Studio Code でもコードを書けますが追加するパッケージを GUI で確認したり、コード補完が Code よりも強力な部分があったりと、まだ VS Code より多少重くても(個人の感覚)C# を書く場合には VS 2019 のほうを使ってしまいます。
まとめ
C# でもスクレイピングをさくっと出来たのと C# 9.0 のトップ レベル ステートメントは、単純にコードが短くなって素敵なので正式リリースが待ち遠しいです。
- 投稿日:2020-07-21T16:54:16+09:00
[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=USD10日~13日にやらかしてますね…
この時、DocumentDBの検証をやっていてインスタンス落とすの忘れてて
土日放置してたようです。あとは、このレポートを定期的/閾値を超えたらメールで送ったり
メッセンジャーに送ったりすれば被害を低減できるでしょうか。そもそも自分でちゃんと管理しろと言われそうですがね。
自分用の雑なコンソールアプリです。
閾値を超えたらメール送信するアプリ
- 投稿日:2020-07-21T15:20:23+09:00
【de:code2020学習記録】2020 年の最新 Xamarin 概要
はじめに
本稿は、de:code2020の以下のセッションで学んだ内容のメモです。
動画がこちらで公開されました。
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 初心者向け - connpassTwitterでも開発に役立つ情報を発信しています → @kojimadev
- 投稿日:2020-07-21T14:41:47+09:00
【C#】カメラでQRコード読み取り
前回のQRコード読み取りを利用して、カメラでQRコード解析してみました。
■参考記事
【C#】PCカメラから映像を取得するアプリ事前準備
Nugetパッケージは、前回の記事同様に以下をインストール。
・OpenCvSharp3
・ZXing.NetAForge.NET
※「[ Download Libraries Only ]」のリンクからzipをダウンロードします。
「AForge.Video.DirectShow.dll」「AForge.Video.dll」のDLLを使用しました。コード
カメラの種類のコンボボックス
画面表示時の処理で呼ぶとよい。
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コードが読み取られると、メッセージボックスで内容が表示されます。(読み取り値のやつ)
カメラに写っているのは、下のピクチャーボックスに表示
何かの機能追加の際とかに、コピペで使える・・かも?
- 投稿日:2020-07-21T14:39:42+09:00
【C#】荒い画像からQRコード読み取り
初投稿です。
以下の記事を参考にQRコード読み取りの案件があったので、実装しました。
【ZXing.Net】C#でQRコードの読取ぼやけたQRコードや、少し斜めったQRコードも読み取れるように改造した内容です。
実装途中、以下のようなメモリリークしたり、画像ファイルのアクセス不正とかで嵌ったので、メモとして残しました。
OpenCvSharp で Mat を返すメソッドを使うとメモリリークで死ぬ件Nugetパッケージのインストール
Nugetで「OpenCvSharp3」と「Zxing.Net」をインストールします。
コード
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 」の値を増やすといい感じだけども、処理時間も増える。。