20210606のC#に関する記事は5件です。

T4(Text Template Transformation Toolkit)のTips

T4はC#でC#を作れる便利な子(C#じゃなくてもいいけど) 実装していて迷ったことをメモします。 Visual studio 2019 .net 5 開発環境のとり方 何処かのフォルダーに置かれている、CSVだったりXMLだったりExcelだったりをもとに大量にソースコードを生成したい場合などあると思います。 そんな時に例えばVisual studioのソリューションフォルダを取得するには次のような感じにします。 <#@ template debug="true" hostspecific="true" language="C#" #> <#@ assembly name="System.Core" #> <#@ assembly name="EnvDTE" #> <#@ import namespace="EnvDTE" #> <#@ output extension=".txt" #> <# var serviceProvider = this.Host as IServiceProvider; var dte = serviceProvider.GetService(typeof(EnvDTE.DTE)) as EnvDTE.DTE; var solutionFolder = System.IO.Path.GetDirectoryName(dte.Solution.FullName); #> <#=solutionFolder#> ポイント hotspecific="true" ⇒Hostを使用するようにします。 EnvDTEを使えるようにする ⇒ここから開発環境情報を取得します。 参考にしたMSDNはココ https://docs.microsoft.com/ja-jp/visualstudio/modeling/writing-a-t4-text-template?view=vs-2019 インクルードガード C#をメインにしていると忘れてしまいがちな多重インクルードの防止です。↓ #ifndef HOGE #define HOGE #endif #pragma once T4でも対策しておかないと多重インクルードで怒られます。 ダメな例 Main.tt <#@ template debug="true" hostspecific="true" language="C#" #> <#@ assembly name="System.Core" #> <#@ include file="file1.ttinclude"#> <#@ include file="file2.ttinclude"#> <#@ output extension=".txt" #> <# var str1 = ReturnString1("test print1"); var str2 = ReturnString2("test print2"); #> <#=str1#> <#=str2#> file1.ttinclude <#@ template debug="true" hostspecific="true" language="C#" #> <#@ assembly name="System.Core" #> <#@ include file="file2.ttinclude"#> <#+ static string ReturnString1(string message) { return ReturnString2(message); } #> file2.ttinclude <#@ template debug="true" hostspecific="true" language="C#" #> <#@ assembly name="System.Core" #> <#+ static string ReturnString2(string message) { return message; } #> 怒られます。 対策 他のファイルをインクルードしているところに once="true"をつけます。 <#@ include file="ファイル名" once="true" #> MSDNはここ https://docs.microsoft.com/ja-jp/previous-versions/visualstudio/visual-studio-2015/modeling/t4-include-directive?view=vs-2015&redirectedfrom=MSDN ファイル出力したくない場合 カスタムツールTextTemplatingFileGeneratorでT4実行すると<# output extension="xxxx" #>で指定した拡張子でファイルが生成されます。 しかし一つのT4から複数のファイルを生成したかったり、生成するファイルの名前、場所をカスタマイズしたい場合に、T4のファイル生成機能が邪魔になる場合があります。 カスタムツールとして使いたいけどファイル生成は抑制したい。そんな時は拡張子として"/"を指定してやるとファイル生成されなくなります。 <#@ output extension="/" #> MSDNにはこのような情報はなく、StackOverFlowから見つけました。 https://stackoverflow.com/questions/12909097/t4-templates-generates-unwanted-output-file 複数ファイル出力 (追記予定)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UiPathでJWTを生成してみる

はじめに UiPath では、いわゆる Web API に対して操作を行うことが可能なアクティビティが存在します。 Web API の利用にあたってはサービスごとに様々な認証手段が存在しますが、最近、 JWT というものを利用する手段が提供されているサービスが多く、「これを UiPath でも利用できるのではないか」と考えまして色々調べてみました。 JWT (JSON Web Token) とは Wikipediaによると、次のように記載されていました。 JSONデータに署名や暗号化を施す方法を定めたオープン標準 (RFC 7519) である。 技術的な詳細は上記リンクから見ていただければと思いますが、誤解を恐れずにいえば「トークン(ここでは認証に使用されるデータ)の生成に関する仕組み」と捉えて良いと思います。 なおこの記事では、この仕組みを利用して出力されるトークンについても JWT と呼称することにします。 詳しくないながら色々と調べてみると、基本的に JWT は「クライアントサイドで生成する」よりも、「サーバーサイドで認証し生成されたものを受け取る」ことが想定されているようなのですが、いくつかのサービスではクライアントサイドで生成することとなっていて、結局のところはサービスごとに異なるようです。 JWT を生成してみる 何はともあれ、クライアントサイドで生成する必要がある、という想定のもとに JWT を生成してみましょう。 準備 ライブラリの選定 UiPath には標準で JWT の生成に関するライブラリが含まれていません。ですので、ライブラリを入れていきます。 JWT に関するライブラリはいくつか存在するようなのですが、調べたところでは JWT.NET が比較的簡単に利用できそうでしたので、これを使用したいと思います。 JWT.NET のインストール それでは、 JWT.NET のインストールを進めます。ライブラリは NuGet 上でも提供されているので、デフォルトで登録されているパッケージフィードから "nuget.org" を選択して、そこで「JWT.NET」と検索して当該ライブラリをインストールします。 名前空間のインポート インストールが終わって、これで一応実装出来る状態にはなりました。ただこのままだとコードとして書かないといけない字数が増えちゃうので、ちょっと簡単にするための作業として「名前空間のインポート」を行いましょう。 追加する名前空間は、下記の2つです。 JWT.Algorithms 生成処理のなかで「署名アルゴリズム」を指定するのですが、その選択肢を持つ空間です。 JWT.Builder 実際の生成処理に必要な手順がメソッドとして実装されている空間です。 ちなみにこれが何を指しているのかを知らないひとも多いと思うので、別記事で書きますね。 実装 名前空間のインポートが終わったら、いよいよ実装に入ります。 生成方法の概要 まず、生成のための大まかな流れをまとめます。 ビルダーを生成する アルゴリズムを指定する 署名を追加する ヘッダー・クレームを追加する エンコードする ビルダーを生成する // 「左辺」 = 「右辺」 builder = JWTBuilder.Create() JWTに必要となる様々な情報を格納できる JWTBuilder という型があるので、この型にあったオブジェクトを作ります。 あらかじめ、変数パネルで builder という変数を作成して、その型として JWT.Builder.JWTBuilder を指定します。その後、【代入】アクティビティを用いて、下記のように設定してあげましょう。 左辺 builder 右辺 JWTBuilder.Create() 以降、この builder に対して様々な情報を付加していく作業になります。 アルゴリズムを指定する builder = builder.WithAlgorithm(new HMACSHA256Algorithm()) 生成した builder を左辺において、 builder に対し「builder にアルゴリズムを追加したもの」を代入します。 new HMACSHA256Algorithm() の部分ですが、これが実際に指定するアルゴリズムを指しています。いくつかアルゴリズムがあるようですが、一般的には上記のアルゴリズムが使われているようです。 実際に利用する場合は、サービス側で指定がされているはずですので確認してみてください。 署名を追加する builder = builder.WithSecret(secret) 上記と同様に、 「builder に署名を追加したもの」を代入していきます。 secret の部分は、文字列もしくはバイト列のいずれかを指定してください。 SecureString とかで指定できるといいんですけど、Stringしか指定できません。アセットから引っ張ってくるなどした場合は、 NetworkCredential クラスを使って取得してください。 ヘッダー・クレームを追加する builder = builder.AddHeader(headerName, object) builder = builder.AddClaim(claimName, object) さらに、ヘッダーとクレームを追加していきます。 headerName は、 HeaderName クラス配下にある定数を指定してください。 claimName のところは、文字列か ClaimName クラス配下にある定数を指定します。 ここに指定するヘッダー・クレームは、サービスごとに大きく異なるようですのでサービスのドキュメントをよく確認して追加していきます。 エンコードする token = builder.Encode() まず、 token という変数を文字列型で準備しておきます。 用意できたら、 builder に含まれる情報を .Encode() によって変換された状態で出力します。 (補足)メソッドチェーンによる実装 ここまで、 それぞれの作業を1つずつアクティビティとして追加していくことを想定して書きました。 ところで、 JWTBuilder クラスのメソッドのうち、 .WithAlgorithm() , .WithSecret() , .AddHeader() , .AddClaim() はすべて返り値が JWTBuilder 型ですので、以下のようなメソッドチェーンで記述することも可能です。 token = JWTBuilder.Create() .WithAlgorithm(new HMACSHA256Algorithm()) .WithSecret(secret) .AddHeader(headerName, object1) .AddClaim(claimName, object2) .Encode() UiPathなら、アクティビティは1つで済ませられますが、可読性はだいぶ犠牲になると思いますので、利用シーンを鑑みて選択してください。 テスト 実装が完了したら、念のため正しく生成できたかどうかをチェックしましょう。 チェックの方法ですが、 Auth0 が提供しているチェックサイトがありますのでそちらを利用してみましょう。 左側のボックスに、生成したトークンを貼り付けます。そうすると右側のボックスの入力内容が、トークンに含まれる情報に変化します。 まずこの時点で、右側のボックスの "HEADER" と "PAYLOAD" の内容が期待通りになっていることを確認しましょう。 次に、 "VERYFY SIGNATURE" の中にある "your-256-bit-secret" に secret の値を入力します。 入力後、左側のボックスに表示されているトークンと、生成したトークンが一致していることを確認してください。 一致していれば、正しくトークンが生成されていると判断して良いです。 まとめ 「UiPath でここまでやる必要あるの?」という意見も少なからずあると思います。 しかし今後、RPAはデスクトップオートメーションにとどまらず EAI ツールとして、より高度な自動化へのニーズに応えることが求められると思います。そうなったとき Web API の利用は避けられないでしょうし、そのうえでの認証周りの対応は不可欠になると思います。 上述のようなシーンに遭遇し、かつ「JWT が使えそうだ」というときに、この記事を役に立てていただけましたら幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

XAMLを共通化

XAMLを共通化する 必要過多に凝った例が多いのでシンプルな例 前提 プロジェクト直下を下記にようにしておく //埋め込むフォント .\Font\NotoSansCJKjp-DemiLight.otf //流用するXAML .\Themes\base.xaml .\Themes\style.xaml 共通で使うフォントの定義 App.XAML <Application ~省略~ > <Application.Resources> <ResourceDictionary> <FontFamily x:Key="NotoSans-DemiLight">Font\NotoSansCJKjp-DemiLight.otf</FontFamily> </ResourceDictionary> </Application.Resources> </Application> base.xaml 例としてButtonを共通外観を記載 App.xaml の NotoSans-DemiLight を割り当てる base.xaml <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Style TargetType="Button" > <Setter Property="FontFamily" Value="{StaticResource NotoSans-DemiLight}" /> <Setter Property="Background" Value="Transparent"/> <Setter Property="BorderThickness" Value="1.0" /> <Setter Property="BorderBrush" Value="White"/> <Setter Property="FontSize" Value="12" /> <Setter Property="Width" Value="80" /> <Setter Property="Height" Value="80" /> </Style> </ResourceDictionary> style.xaml 例としてTextBlockの共通外観を記載 App.xaml の NotoSans-DemiLight を割り当てる style.xaml <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Style TargetType="TextBlock"> <Setter Property="Text" Value="これはテストですよ。ああああああ1234567890ABCDEFGabcdefg" /> <Setter Property="FontSize" Value="32" /> <Setter Property="TextWrapping" Value="Wrap" /> <Setter Property="FontFamily" Value="{StaticResource NotoSans-DemiLight}" /> <Setter Property="Background" Value="Aquamarine" /> </Style> </ResourceDictionary> MainWindow.xaml base.xaml と style.xaml を適用 複数のXAMLを呼び出すときは <ResourceDictionary.MergedDictionaries> が必要 今回の方法では、WindowにはXAML定義を適用できないため、フォントのみ App.xaml の定義を使用 MainWindow.xaml <Window ~省略~    Height="450" Width="800" Title="あああaaa" FontFamily="{StaticResource NotoSans-DemiLight}" > <Window.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="Themes/base.xaml"/> <ResourceDictionary Source="Themes/style.xaml"/> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Window.Resources> <Grid> <TextBlock HorizontalAlignment="Left" Height="134" Margin="139,161,0,0" VerticalAlignment="Top" Width="542" /> <Button Content="Button" HorizontalAlignment="Left" Margin="519,38,0,0" VerticalAlignment="Top" /> </Grid> </Window> ハマりどころ base.xml と style.xml で <Style TargetType="Button" > を定義していた場合、最後に呼び出したほうが採用される。 重複定義していることをエラー出してくれないので、あまり複雑な構成は作らない方が良い。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#でYamlファイルをシリアライズ/デシリアライズする

概要 早速ですがYamlファイルで初期設定したいですよね。少なくとも私はそうでした. C#で使う方法としてはNuGetで配布されているYamlDotNetというパッケージを使うのが一番手っ取り早そうだったので今回触ってみたところ、参考資料が少なくて少し不便だったので備忘録も兼ねて今回共有できたらなと思いました。 ドキュメントとか用意してくれてるんですけど少し古いっぽくてエラー出てきちゃったんですよね。。。 元も子もないんですけど、一応最新のはずのGithubのリンクを貼っておきます。これを読んだらこんな記事読む必要無いです?( https://github.com/aaubry/YamlDotNet ) それでは書いていきます! 環境 Windows10 Visual Studio 2019 .NetCore 3.1.0 YamlDotNet 11.1.1 準備 YamlDotNetのインストール 「プロジェクト」タブ(①)→「NuGetパッケージの管理」(②)と選択 「参照」タブを選択して、検索ボックスに「YamlDotNet」と入力 ①、②の順にクリックしてインストールすれば完了、インストール済みのところにYamlDotNetが追加されているはず YamlDotNetについて チートシートを書きたいわけではない(書けるはずもない)のでここでは今回実装するにあたって知っておけば良さそうな情報をここでは共有します。 YamlDotNet.Serialization Serializer.Serialize TextWriterとシリアライズしたい情報をもったオブジェクトを渡すと、TextWriterで設定されている出力先パスにファイルを作成してくれます。また、オブジェクトのみを渡すとStringが返ってきて、オブジェクトの中身を見れる便利機能もありました。 Deserializer.Deserialize ジェネリックに自分で定義したオブジェクト型を渡して、引数にStreamReaderやSystem.Stringを渡すことで、文字列情報をオブジェクトにデシリアライズしてくれます。 また、横道に逸れますがDeserializerBuilderを使ってデシリアライザをビルドすることで、Yamlファイルに出力される文字列の表記をキャメルケースやスネークケースに変えてくれたりするような細かい設定ができるようになります。 YamlDotNet.RepresentationModel YamlStream メンバ関数のLoadの引数にTextReaderを渡すと、辞書型で各ノードの情報が格納されたYamlStream型が取得できます。 メンバ変数のDocumentsには、Yamlの区切り文字---または終端文字...を認識して、複数のドキュメントとして配列にして返してくれます。Documents配列内の変数の型は、YamlNodeという基底クラスを継承したノード達が格納されています。厳密な部分は少し端折っていますが、このDocumentの中を型に注意しながら見ていけばいいんだなぁというのが伝われば幸いです。 YamlNode 以下で紹介する各ノード種類の継承元クラス。以下がモデルの構成図です。 (引用元:https://yaml.org/spec/1.2/spec.html ) YamlScalarNode 子要素を持たないノード(葉ノード)です。アクセサであるValueを持っています。 YamlSequenceNode ノードが格納されている配列型です。子を持つ配列形式のノードはこの型になります。 YamlMappingNode ノードが格納されている辞書型です。子を持つハッシュ形式のノードはこの型になります。 今回書きたいyamlファイルを定義 Version:1.0.0 Date:{yyyy-MM-dd} PersonInfo: HP:20 MP:10 Attack:4 Defence:5 Speed:2 Luck:3 Classes: - Name:せんし Strategy:ガンガンいこうぜ Feature:力持ち - Name:そうりょ Strategy:いのちだいじに Feature:癒し系 - Name:まほうつかい Strategy:じゅもんつかうな Feature:素手派 Items: - Heal:やくそう Doping:ちからのたね Weapon:ひのきのぼう TechniqueMachines: - Machine01:きあいパンチ Machine04:めいそう Machine08:ビルドアップ Machine13:れいとうビーム とりあえずこのくらいに対応すれば一通り流れがつかめると思うのでこれで行きます。 それではやっていきましょう! Yamlファイルを出力(シリアライズ) まずは出力先のフォルダです。 私は今回、C\DQ\フォルダを作成し、そこに出力していきます。 自分の中の勝手な風習として先に読み込みたいんですけど、先にファイル用意したほうが手打ちより楽そうなのでこっちから行きます(もうQiitaのマークダウンで書いているが)。 それではオブジェクトを使うパターンとそうでないパターンの2種類のコードを書いていきます。 オブジェクトを使うやり方 まずは地道にオブジェクトを作ります。 public class YamlObj { public string Version { get; set; } public DateTime Date { get; set; } public PersonInfo PersonInfo { get; set; } public Classes[] Classes { get; set; } public Items[] Items { get; set; } } public class PersonInfo { public int HP { get; set; } public int MP { get; set; } public int Attack { get; set; } public int Defence { get; set; } public int Speed { get; set; } public int Luck { get; set; } } public class Classes { public string Name { get; set; } public string Strategy { get; set; } public string Feature { get; set; } } public class Items { public string Heal { get; set; } public string Doping { get; set; } public string Weapon { get; set; } public TechniqueMachines[] TechniqueMachines { get; set; } } public class TechniqueMachines { public string Machine01 { get; set; } public string Machine04 { get; set; } public string Machine08 { get; set; } public string Machine13 { get; set; } } この時、変数名がYamlファイルのキーとして出力されてくるので、出力する分にはいいですが、読み込む際には十分に注意する必要があります。 それでは適当な関数を用意して、シリアライズしていきます。 var yamlObj = new YamlObj { Version = "1.0.0", Date = new DateTime(1988, 2, 10), PersonInfo = new PersonInfo { HP = 20, MP = 10, Attack = 4, Defence = 5, Speed = 2, Luck = 3 }, Classes = new Classes[] { new Classes { Name = "せんし", Strategy = "ガンガンいこうぜ", Feature = "力持ち" }, new Classes { Name = "そうりょ", Strategy = "いのちだいじに", Feature = "癒し系" }, new Classes { Name = "まほうつかい", Strategy = "じゅもんつかうな", Feature = "素手派" }, }, Items = new Items[] { new Items { Heal = "やくそう", Doping = "ちからのたね", Weapon = "ひのきのぼう", TechniqueMachines = new TechniqueMachines[] { new TechniqueMachines { Machine01 = "きあいパンチ", Machine04 = "めいそう", Machine08 = "ビルドアップ", Machine13 = "れいとうビーム" } } } } }; // シリアライズ string savePath = @"C:\DQ\param.yaml"; using TextWriter writer = File.CreateText(savePath); var serializer = new Serializer(); serializer.Serialize(writer, YamlObj); 出力結果はこんな感じ。 ストリームを使うやり方 var techniqueMachines = new YamlMappingNode( new YamlScalarNode("Machine01"), new YamlScalarNode("きあいパンチ"), new YamlScalarNode("Machine04"), new YamlScalarNode("めいそう"), new YamlScalarNode("Machine08"), new YamlScalarNode("ビルドアップ"), new YamlScalarNode("Machine13"), new YamlScalarNode("れいとうビーム") ); var stream = new YamlStream( new YamlDocument( new YamlMappingNode( new YamlScalarNode("Version"), new YamlScalarNode("1.0.0"), new YamlScalarNode("Date"), new YamlScalarNode($"{ new DateTime(1988, 2, 10) }"), new YamlScalarNode("PersonInfo"), new YamlMappingNode( new YamlScalarNode("HP"), new YamlScalarNode("20"), new YamlScalarNode("MP"), new YamlScalarNode("10"), new YamlScalarNode("Attack"), new YamlScalarNode("4"), new YamlScalarNode("Defence"), new YamlScalarNode("5"), new YamlScalarNode("Speed"), new YamlScalarNode("2"), new YamlScalarNode("Luck"), new YamlScalarNode("3") ), new YamlScalarNode("Classes"), new YamlSequenceNode( new YamlMappingNode( new YamlScalarNode("Name"), new YamlScalarNode("せんし"), new YamlScalarNode("Strategy"), new YamlScalarNode("ガンガンいこうぜ"), new YamlScalarNode("Feature"), new YamlScalarNode("力持ち") ), new YamlMappingNode( new YamlScalarNode("Name"), new YamlScalarNode("そうりょ"), new YamlScalarNode("Strategy"), new YamlScalarNode("いのちだいじに"), new YamlScalarNode("Feature"), new YamlScalarNode("癒し系") ), new YamlMappingNode( new YamlScalarNode("Name"), new YamlScalarNode("まほうつかい"), new YamlScalarNode("Strategy"), new YamlScalarNode("じゅもんつかうな"), new YamlScalarNode("Feature"), new YamlScalarNode("素手派") ) ), new YamlScalarNode("Items"), new YamlMappingNode( new YamlScalarNode("Heal"), new YamlScalarNode("やくそう"), new YamlScalarNode("Doping"), new YamlScalarNode("ちからのたね"), new YamlScalarNode("Weapon"), new YamlScalarNode("ひのきのぼう"), new YamlScalarNode("TechniqueMachines"), techniqueMachines ) ) ) ); // シリアライズ string savePath = @"C:\DQ\param.yaml"; using TextWriter writer = File.CreateText(savePath)); stream.Save(writer, false); どうせなのでもう1パターン。YamlMappingNodeのAdd関数を使っていく方法です。自分的にはこっちの方が好みです。 // 1行目を直接記述 var version = "---\nVersion: 1.0.0\n"; var sr = new StringReader(version); var stream = new YamlStream(); stream.Load(sr); // 先ほど書いた1行目を根ノードとして取得 var root_node = (YamlMappingNode)stream.Documents[0].RootNode; root_node.Add("Date", $"{ new DateTime(1988, 2, 10) }"); // PersonInfo var personInfo = new YamlMappingNode(); personInfo.Add("HP", "20"); personInfo.Add("MP", "10"); personInfo.Add("Attack", "4"); personInfo.Add("Defence", "5"); personInfo.Add("Speed", "2"); personInfo.Add("Luck", "3"); root_node.Add("PersonInfo", personInfo); // Classes var classes = new YamlSequenceNode(); var warrior = new YamlMappingNode(); warrior.Add("Name", "せんし"); warrior.Add("Strategy", "ガンガンいこうぜ"); warrior.Add("Feature", "力持ち"); var priest = new YamlMappingNode(); priest.Add("Name", "そうりょ"); priest.Add("Strategy", "いのちだいじに"); priest.Add("Feature", "癒し系"); var mage = new YamlMappingNode(); mage.Add("Name", "まほうつかい"); mage.Add("Strategy", "じゅもんつかうな"); mage.Add("Feature", "素手派"); classes.Add(warrior); classes.Add(priest); classes.Add(mage); root_node.Add("Classes", classes); // Items var items = new YamlSequenceNode(); var item = new YamlMappingNode(); item.Add("Heal", "やくそう"); item.Add("Doping", "ちからのたね"); item.Add("Weapon", "ひのきのぼう"); var techniqueMachines = new YamlSequenceNode(); var techniqueMachine = new YamlMappingNode(); techniqueMachine.Add("Machine01", "きあいパンチ"); techniqueMachine.Add("Machine04", "めいそう"); techniqueMachine.Add("Machine08", "ビルドアップ"); techniqueMachine.Add("Machine13", "れいとうビーム"); techniqueMachines.Add(techniqueMachine); item.Add("TechniqueMachines", techniqueMachines); items.Add(item); root_node.Add("Items", items); // シリアライズ string savePath = @"C:\DQ\param.yaml"; using TextWriter writer = File.CreateText(savePath); stream.Save(writer, false); 出力結果はこんな感じ YamlStream.SaveでシリアライズしたYamlファイルの末尾には自動で終端記号の...がつくみたいですね。 気になるようでしたらオブジェクトを使って出力すれば解決できそう← Yamlファイル読み込み(デシリアライズ) こちらもオブジェクトを使うパターンとそうでないパターンの2種類のコードを書いていきます。 オブジェクトを使うやり方 オブジェクトは先ほど定義したものを再利用して、デシリアライザを使ってオブジェクトを取得する関数を作成します。 public class YamlImporter { public static YamlData Deserialize(string yamlPath) { // テキスト抽出 var input = new StreamReader(yamlPath, Encoding.UTF8); // デシリアライザインスタンス作成 var deserializer = new Deserializer(); // yamlデータのオブジェクトを作成 var deserializeObject = deserializer.Deserialize<YamlData>(input); return deserializeObject; } } ではこれで返ってくるオブジェクトを早速確認してみます。 // オブジェクト作成 string yamlPath = @"C:\DQ\param.yaml"; YamlObj yamlObj = YamlImporter.Deserialize(yamlPath); Console.WriteLine(yamlObj.Version); Console.WriteLine(yamlObj.Date); Console.WriteLine($"{yamlObj.PersonInfo.HP}\t{yamlObj.PersonInfo.MP}\t{yamlObj.PersonInfo.Attack}\t" + $"{yamlObj.PersonInfo.Defence}\t{yamlObj.PersonInfo.Speed}\t{yamlObj.PersonInfo.Luck}"); foreach (var tClass in yamlObj.Classes) { Console.WriteLine($"{tClass.Name}\t{tClass.Strategy}\t{tClass.Feature}"); } foreach (var item in yamlObj.Items) { Console.WriteLine($"{item.Heal}\t{item.Doping}\t{item.Weapon}"); foreach (var TechniqueMachine in item.TechniqueMachines) { Console.WriteLine($"{TechniqueMachine.Machine01}\t{TechniqueMachine.Machine04}\t" + $"{TechniqueMachine.Machine08}\t{TechniqueMachine.Machine13}"); } } /* >> 1.0.0 >> 1988/02/10 0:00:00 >> 20 10 4 5 2 3 >> せんし ガンガンいこうぜ 力持ち >> そうりょ いのちだいじに 癒し系 >> まほうつかい じゅもんつかうな 素手派 >> やくそう ちからのたね ひのきのぼう */ ストリームを使うやり方 最後にわざわざオブジェクト作らないで省エネに行くコードを書きます。 string yamlPath = @"C:\DQ\param.yaml"; var input = new StreamReader(yamlPath, Encoding.UTF8); var yaml = new YamlStream(); yaml.Load(input); まずはストリームで読み込んだ後に、YamlStreamってところに渡したら勝手にドキュメントごとに分割して返してくれます。 ドキュメントごとというのは、Yamlファイルの区切り文字である---のことですね。今回は1つだけ(---は使っていない)なので、決め打ちで配列の0番を指定しちゃいます。また、仕様だと思いますが、終端文字の...を記述していても、Documentsには追加されてしまいます。 var rootNode = yaml.Documents[0].RootNode; 繰り返しとはなりますが、DocumentのgetterにはAllNodesとRootNodeの2種類あります。今回はRootNodeからたどっていく方法で実装していきます。 var version = rootNode["Version"]; Console.WriteLine(version); var date = rootNode["Date"]; Console.WriteLine(date); /* >> 1.0.0 >> 1988-02-10T00:00:00.0000000 */ var personInfo = (YamlMappingNode)rootNode["PersonInfo"]; foreach (var c in personInfo.Children) { Console.WriteLine($"{c.Key} : {c.Value}"); } /* >> HP : 20 >> MP : 10 >> Attack : 4 >> Defence : 5 >> Speed : 2 >> Luck : 3 */ var classes = (YamlSequenceNode)rootNode["Classes"]; foreach (var c in classes.Children) { Console.WriteLine($"{c["Name"]}, {c["Strategy"]}, {c["Feature"]}"); } /* >> せんし, ガンガンいこうぜ, 力持ち >> そうりょ, いのちだいじに, 癒し系 >> まほうつかい, じゅもんつかうな, 素手派 */ var items = (YamlSequenceNode)rootNode["Items"]; foreach (var c in items.Children) { Console.WriteLine($"{c["Heal"]}, {c["Doping"]}, {c["Weapon"]}"); foreach (var cc in (YamlSequenceNode)c["TechniqueMachines"]) { Console.WriteLine($"{cc["Machine01"]}, {cc["Machine04"]}, {cc["Machine08"]}, {cc["Machine13"]}"); } } /* >> やくそう, ちからのたね, ひのきのぼう >> きあいパンチ, めいそう, ビルドアップ, れいとうビーム */ RootNodeからたどっていくとこんな感じの書き方になるんですかね? 最後のforeach内でYamlSequenceNodeにキャストしているのは、YamlNodeにGetEnumeratorが実装されていない(foreachが使えない)からです。一回子どもに行くとYamlNodeになってしまうのでキャストします。 また、Classes以降はKeyは表示してないんですが、やるとしたらこんな感じになります。 var classes = (YamlSequenceNode)rootNode["Classes"]; foreach (YamlMappingNode c in classes.Children) { foreach (var cc in c.Children) { Console.WriteLine($"{cc.Key} : {cc.Value}"); } } /* Name : せんし Strategy : ガンガンいこうぜ Feature : 力持ち Name : そうりょ Strategy : いのちだいじに Feature : 癒し系 Name : まほうつかい Strategy : じゅもんつかうな Feature : 素手派 */ Classesは配列型で定義しているのでまずはYamlSequenceNodeにキャストしてから、その子要素が配列型じゃなくなるタイミングでKeyValuePairが継承されているYamlMappingNodeにキャストすることでKeyValueが取得できます。 ですのでこれらを踏まえると、YamlNodeのgetterに存在するNodeTypeをチェックして配列型の時のみYamlMappingNodeにキャストするみたいな方法をとればKeyの手打ち入力は完全に不要になるはずです。 これで得体のしれないYamlファイルを読み込めます!(そんな場面があるのかはさておき) さいごに どうですかYamlDotNet!すごい便利!ステマじゃないですよ? 日本語の資料が少ないのが自分としては本当にネックなので皆さんでこの閑散とした世界を活発にしていきましょう! また、配列の要素が1つしかないのに配列で定義していたりと意味の分からないコードになっていますが、サンプルになればいいなと思い書いたのでusing 温かい目で見守ってください。 Githubにも今回のコードを全て関数にして置いておいたので、ぜひ使ってください。 https://github.com/miwazawa/YamlDotNetSample 少しでも実装の手助けになったらハピネスです。 最後までありがとうございました! 参考 全体的にはほぼGithubとドキュメント Github : https://github.com/aaubry/YamlDotNet ドキュメント : https://aaubry.net/pages/yamldotnet.html シリアライズ https://csharp.hotexamples.com/jp/examples/YamlDotNet.RepresentationModel.Serialization/Serializer/Serialize/php-serializer-serialize-method-examples.html デシリアライズ https://csharp.hotexamples.com/jp/examples/YamlDotNet.RepresentationModel.Serialization/Deserializer/Deserialize/php-deserializer-deserialize-method-examples.html
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【WPF】Pythonの標準出力をリアルタイムに表示する

Pythonで機械学習を回すGUIアプリを作っているときに、進捗状況を確認できればいいなーと思い色々調べたのでメモ コード 結果から言うとこれで上手いこといきました。恐らくこれが一番シンプルだと思います。 sumple.pyの出力結果を非同期で取得して、textbox(名前はtb1)に表示しています。 sumple.pyは1秒ずつ文字列を出力するだけのプログラムです。 GUIにはボタンとtextboxだけ置いてます。 MainWindow.xaml.cs using System; using System.Windows; using System.Diagnostics; namespace ConsoleRead { public partial class MainWindow : Window { Process pr = null; public MainWindow() { InitializeComponent(); } private void Button_Start(object sender, RoutedEventArgs e) { HeavyMethod(); } /// <summary> /// pythonコードを非同期で実行 /// <summary> public void HeavyMethod() { pr = new Process(); // pythonファイル(sumple.py)の指定 pr.StartInfo.FileName = "python.exe"; pr.StartInfo.Arguments = "-u sumple.py"; // コンソール画面を表示させない pr.StartInfo.CreateNoWindow = true; // 非同期実行に必要 pr.StartInfo.UseShellExecute = false; pr.StartInfo.RedirectStandardOutput = true; // イベントハンドラ登録(標準出力時) pr.OutputDataReceived += process_DataReceived; // イベントハンドラ登録(プロセス終了時) pr.EnableRaisingEvents = true; pr.Exited += onExited; pr.Start(); pr.BeginOutputReadLine(); //非同期で標準出力読み取り } /// <summary> /// 標準出力があった時に実行 /// </summary> public void process_DataReceived(object sender, DataReceivedEventArgs e) { string output = e.Data + "\r\n"; Dispatcher.BeginInvoke(new Action(() => { tb1.Text += output; })); } /// <summary> /// プロセス終了時に実行 /// </summary> public void onExited(object sender, EventArgs e) { if (pr != null) { pr.Close(); pr.Dispose(); MessageBox.Show("end"); } } } } sumple.py import time for i in range(10): time.sleep(1) print("process" ,i*1) } ポイント 1. 非同期実行 pr.BeginOutputReadLine() を記述することで非同期で標準出力を読み取ることができます。 2. 標準出力の検知 pr.OutputDataReceived にメソッドを追加することで、標準出力が行われたときに処理をすることができます。 3. UIスレッドからのtextboxの操作 BeginInvoke でUIスレッドに戻してからtb1に結果を追加しています。 4. python実行時に-uオプションを付ける これを付けないと標準出力が最後にまとめて表示されます。 これで半日悩んで結局teratailに質問投げたんですが、小一時間で回答が返ってきて解決してしまいインターネットしゅごい…となりました。 余談 最初async/awaitを使っていたのですが、awaitを使うとpythonの実行が終わるまで待たなければならず上手くいきませんでした。 標準出力が一つだけのコードを繰り返し使うような場合には良いかもしれません。 注意 全体的にふんわりとした理解なので(特にイベントハンドラ周り)、間違ったことを言っている可能性は十二分にあります。 何かありましたらご指摘いただけると有難いです。 参考 C# GUIアプリケーションからPythonスクリプトを実行する https://qiita.com/kimisyo/items/0879fc9f1315e2abfd1f 非同期で標準出力(コンソールアプリケーション)の内容をプログラムで受け取る - C#プログラミング https://www.ipentec.com/document/csharp-get-standard-output-async Taskを極めろ!async/await完全攻略 https://qiita.com/acple@github/items/8f63aacb13de9954c5da
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む