20210607のC#に関する記事は8件です。

[Unity] 画面キャプチャをドライブにアップロードする

概要 Unityエディタ上で画面をキャプチャして、それをファイル出力せずドライブにアップロードする 経緯 UI実装してデザイナーに確認する度にキャプチャをとることが増え、多少なりとも自動化できないかと思い至った 実装 GASでウェブアプリ作ってそれを叩くだけ function doPost(e) { var json = JSON.parse(e.postData.contents); var folder = DriveApp.getFolderById(json.folderId); var blob = Utilities.newBlob(json.data, json.mime, json.fileName); folder.createFile(blob); var text = "upload succeeded"; var output = ContentService.createTextOutput(text); output.setMimeType(ContentService.MimeType.TEXT); return output; } IEnumerator Co(string gasId, string folderId, Action<string> callback) { yield return new WaitForEndOfFrame(); var texture = ScreenCapture.CaptureScreenshotAsTexture(); var data = new PostData { folderId = folderId, fileName = DateTime.Now.ToString("yyyy/MM/dd/HH:mm:ss") + ".png", mime = "image/png", data = texture.EncodeToPNG(), }; var request = new UnityWebRequest( $"https://script.google.com/macros/s/{gasId}/exec", UnityWebRequest.kHttpVerbPOST, new DownloadHandlerBuffer(), new UploadHandlerRaw(Encoding.UTF8.GetBytes(JsonUtility.ToJson(data))) ); yield return request.SendWebRequest(); try { callback(request.downloadHandler.text); } catch (Exception e) { Debug.LogError(e); } } 何番煎じか分からないがとりあえず備忘録的な感じで 加えて細かい設定ができるようにエディタウィンドウ実装したり、フック作って通知したり
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RiderとSelenium

Chromeで動作させる Chromeをインストールする Nugetからインストール Install-Package Selenium.WebDriver Install-Package Selenium.WebDriver.ChromeDriver using System; using OpenQA.Selenium; using OpenQA.Selenium.Chrome; namespace ConsoleApp1 { class Program { static void Main(string[] args) { var chrome = new ChromeDriver(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location)); chrome.Url = "http://google.com/"; //特定のURLを開いて待機する Console.ReadKey(); chrome.Quit(); } } } Chrominumで動作させる Chromeをアンインストールする Chrominumをインストールする ChromeDriverをインストールする ChromeをアンインストールしないとChrominumが立ち上がらないです。 Nugetからインストール Install-Package Selenium.WebDriver Install-Package Selenium.WebDriver.ChromeDriver Chrominumとドライバをインストールする $ sudo apt-get update $ sudo apt-get install chromium-browser $ sudo apt-get install chromium-chromedriver $ chromium-browser --version $ chromedriver --version # 日本語フォントをインストールする $ sudo apt-get install fonts-ipafont-gothic fonts-ipafont-mincho 参考 chromedriverのインストールされた場所を調べる $ which chromium-browser $ which chromedriver chromedriverは/usr/bin/chromedriverにインストールされます。 C# ChromeDriver関数にパスを指定する。ここでは「/usr/bin/」を指定する var driver = new ChromeDriver("/usr/bin/"); using System; using OpenQA.Selenium; using OpenQA.Selenium.Chrome; using OpenQA.Selenium.Firefox; namespace SeleniumConsoleApp { class Program { static void _mkFireFox() { IWebDriver driver =new FirefoxDriver(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location)); driver.Navigate().GoToUrl("https://www.google.com/?hl=ja"); //特定のURLを開いて待機する Console.ReadKey(); driver.Quit(); } static void _mkChromnimu() { var driver = new ChromeDriver("/usr/bin/"); driver.Navigate().GoToUrl("https://www.google.com/?hl=ja"); //特定のURLを開いて待機する Console.ReadKey(); driver.Quit(); } static void Main(string[] args) { _mkFireFox(); _mkChromnimu(); } } } FireFoxで動作させる FireFoxをインストールする GeckoDriverをNugetからインストールする Nugetからインストール Install-Package Selenium.WebDriver Install-Package Selenium.WebDriver.GeckoDriver 上記のプログラムで動作します。 ユーザープロファイルの保存指定する 引数 内容 –user-data-dir ユーザープロファイルの保存先を指定 –profile-directory 使用するユーザープロファイルを指定 引数を指定しない場合 –user-data-dirを指定しない場合 プロファイルは一時ファイルとして作成され、Chrome終了後に廃棄されます。 –profile-directoryを指定しない場合 ユーザープロファイルは–user-data-dirで指定したフォルダ内のデフォルトのプロファイル(通常はDefault)を使用します。 指定したフォルダが存在しない場合 –user-data-dir、–profile-directoryに指定したフォルダが存在しない場合は、自動でフォルダが作成されます(Windows環境でのみ確認)ので、タイプミスに注意しましょう。 using System; using OpenQA.Selenium; using OpenQA.Selenium.Chrome; namespace example { class Program { static void Main(string[] args) { ChromeOptions options = new ChromeOptions(); options.AddArgument("--user-data-dir=D:\\tmp\\User Data"); options.AddArgument("--profile-directory=Profile1"); options.AddArgument("--lang=en"); String path = @"D:\tmp\driver\chromedriver_win32"; //フォルダを指定 IWebDriver driver = new ChromeDriver(path, options); driver.Navigate().GoToUrl("chrome://version"); Console.ReadKey(); driver.Quit(); } } } 参考 プラウザを開いた後、非同期で待機状態にする プラウザを開いた後、GTKアプリ側で処理をしたい場合は非同期で待機状態にします。 Selenium.Support.UIのWebDriverWaitを使います。 using System; using OpenQA.Selenium; using OpenQA.Selenium.Chrome; using OpenQA.Selenium.Support.UI; namespace ConsoleApp1 { class Program { static void Main(string[] args) { var chrome = new ChromeDriver(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location)); chrome.Url = "http://google.com/"; WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(5)); } } } Gtk3アプリ RiderとWebViewに続く
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WPF/Prism のViewModel にInterceptor を組み込む

WPFでPrism 使うと ViewModel は自動紐づけしてくれるけど、そのままだとメソッド単位のトレースとか自前でやる必要がある。そうするとメソッド本体のロジックに無関係の物が紛れてしまうし、早期リターンとかするたびに logger.Trace("exit") と書き続けなければならないので少々面倒。 そこで ViewModel も Interceptor注入対象にしてトレースやらトランザクションやらを勝手に組み込めるようにしたい。 ここでは .NET Framework 4.6.1 + Prism.WPF 6.3 が対象。 パッケージ Prism.Autofac.Wpf 6.3.0 Autofac 4.8.1 Autofac.Extras.DynamicProxy 4.5.0 NLog 4.5.11 ReactiveProperty 5.2.0 Interceptor 単純にメソッド実行前後でトレースをしゅうWPFでPrism 使うと ViewModel は自動紐づけしてくれるけど、そのままだとメソッド単位のトレースとか自前でやる必要がある。そうするとメソッド本体のロジックに無関係の物が紛れてしまうし、早期リターンとかするたびに logger.Trace("exit") と書き続けなければならないので少々面倒。 そこで ViewModel も Interceptor注入対象にしてトレースやらトランザクションやらを勝手に組み込めるようにしたい。 ここでは .NET Framework 4.6.1 + Prism.WPF 6.3 が対象。 パッケージ Prism.Autofac.Wpf 6.3.0 Autofac 4.8.1 Autofac.Extras.DynamicProxy 4.5.0 NLog 4.5.11 ReactiveProperty 5.2.0 Interceptor 単純にメソッド実行前後でトレースを収集するような Interceptor using Castle.DynamicProxy; using System; using System.Collections.Generic; using System.Text; using NLog; using System.Diagnostics; using System.Reflection; namespace VmInjectionSample1.ComponentManagement { public class TraceInterceptor : IInterceptor { public TraceInterceptor() { } public void Intercept(IInvocation invocation) { var logger = LogManager.GetLogger(invocation.TargetType.FullName); logger.Trace($"{invocation.TargetType.FullName}#{invocation.Method.Name} method start "); invocation.Proceed(); logger.Trace($"{invocation.TargetType.FullName}#{invocation.Method.Name} method end "); } } } Bootstrapper Bootstrapper では DIコンテナを設定する。 (Prism 7以降では DIコンテナの設定は App.xaml.cs に移動している) using Autofac; using Prism.Autofac; using Prism.Modularity; using System; using System.Windows; using VmInjectionSample1.Views; using VmInjectionSample1.ComponentManagement; using System.Reflection; using Autofac.Extras.DynamicProxy; namespace VmInjectionSample1 { public class Bootstrapper : AutofacBootstrapper { /// <summary> /// コンストラクタ /// </summary> public Bootstrapper() { } protected override void ConfigureContainerBuilder(ContainerBuilder builder) { base.ConfigureContainerBuilder(builder); // Shell の登録 builder.RegisterType<MainWindow>(); // 共有オブジェクトの依存関係(ログ、interceptorなど) builder.RegisterType<TraceInterceptor>(); builder.RegisterAssemblyTypes(this.GetType().Assembly) .Where(t => t.Name.EndsWith("ViewModel")) .AsSelf() .EnableClassInterceptors() .InterceptedBy(typeof(TraceInterceptor)) .InstancePerLifetimeScope() ; } protected override void InitializeShell() { var window = (Window)Shell; window.Show(); } protected override DependencyObject CreateShell() { return Container.Resolve<MainWindow>(); } protected override void ConfigureModuleCatalog() { // AutofacではPrism.Moduleを使えない } } } View ここでは単純に数値をインクリメント、デクリメントするだけのViewを使う。 <UserControl x:Class="VmInjectionSample1.Views.SampleView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:VmInjectionSample1.Views" xmlns:prism="http://prismlibrary.com/" prism:ViewModelLocator.AutoWireViewModel="True" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" FontSize="16"> <Grid Margin="8"> <StackPanel> <TextBox Text="{Binding NumberProperty.Value}" Margin="0 0 0 8"></TextBox> <DockPanel> <StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0 0 8 0"> <Button Content="Incremenet" Command="{Binding IncrementCommand}"></Button> </StackPanel> <StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0 0 8 0"> <Button Content="Decrement" Command="{Binding DecrementCommand}"></Button> </StackPanel> </DockPanel> </StackPanel> </Grid> </UserControl> ViewModel SampleView のViewModel using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Prism.Mvvm; using VmInjectionSample1.ComponentManagement; using Reactive.Bindings; namespace VmInjectionSample1.ViewModels { public class SampleViewModel : BindableBase { public virtual ReactiveProperty<int> NumberProperty { get; set; } = new ReactiveProperty<int>(); public ReactiveCommand IncrementCommand { get; set; } = new ReactiveCommand(); public ReactiveCommand DecrementCommand { get; set; } = new ReactiveCommand(); public SampleViewModel() { IncrementCommand.Subscribe(() => { Increment(); }); DecrementCommand.Subscribe(() => { Decrement(); }); } protected virtual void Increment() { NumberProperty.Value++; } protected virtual void Decrement() { NumberProperty.Value--; } } } ReactivePropertyとトレース対象メソッドは virtual にしないと Interceptor が認識できない。 MainWindow.xaml.cs RegisterForNavigation だけだと最初のViewのロードがうまくできないらしいので、最初のViewへの移動を MainWindow.xaml.cs で行う。 using Prism.Regions; using System.Windows; namespace VmInjectionSample1.Views { /// <summary> /// MainWindow.xaml の相互作用ロジック /// </summary> public partial class MainWindow : Window { public MainWindow(IRegionManager regionManager) { InitializeComponent(); regionManager.RegisterViewWithRegion("ContentRegion", typeof(SampleView)); } } } nlog 設定 まあ、いつも使う設定でよい。 <nlog> <targets> <target name="file" type="File" layout="${longdate} [${threadid:padding=8}] [${uppercase:${level:padding=-5}}] ${message} ${exception:format=ShortType,Message,StackTrace,Data:maxInnerExceptionLevel=10:separator=\r\n}" fileName="${basedir}/logs/${shortdate}.log" encoding="UTF-8" archiveFileName="${basedir}/logs/archives/archive.{#}.log" archiveEvery="Day" archiveNumbering="Rolling" maxArchiveFiles="7" /> <target name="null" type="Null" layout="${message}"/> <target name="console" type="Console" layout="${message}"/> </targets> <rules> <logger name="*" minlevel="Trace" writeTo="file,console" /> </rules> </nlog> 実行 単純に実行してボタンを押してみる。 その他 Prism 7 以降だと Autofacが使えない。Unityでは Interceptor の作り方が違うので、同じ Interceptor を使いたい場合は DryIoc に移行することになる。 ここのサンプルではすべての ViewModel が Interceptor 組み込み対象になっているが、クラスやメソッドに属性を追加してIntercept対象かどうか判定したほうが良い。 プロパティの更新が反映されないように見える場合 このサンプルだとうまくいっているはずだが、 ReactiveProperty を使わないでバッキングフィールド付きのプロパティにした場合などにプロパティの更新がうまくいかないように見える場合がある。 この場合は プロパティを virtual にする プロパティを更新した後で RaisePropertyChanged(nameof(Foo)); を実行する などしてみると良い。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

楽天API C# 商品検索の一覧取得する

はじめに スクレイピングする上で重要なAPIの利用の実装例です。 PHPのサンプルはそこそこあったがC#で実行するサンプルが無かったので自前で 作ったのでご紹介します。 事前準備 楽天WEBサービス(Rakuten Developer)から「アプリID発行」する。 https://webservice.rakuten.co.jp/ とりあえず発行するだけなので入力内容は適当でOKです。 (楽天のアカウントさえあれば後からいくつでも発行できます) 発行後に「アプリID/デベロッパーID」をメモしておいてください。 動作内容 「ドラゴンボール」と検索してヒットするページ情報をXML形式で取得しています。 今回はXML形式のまま取得して必要なデータの整形は次回にしたいと思います。 開発環境 Microsoft Visual Studio Community 2019 Version 16.6.0 コンソールアプリ(.NET Framework) コピペで使うときの注意 コード内のapplicationIdの右側の”0000000000000000000”を先ほど取得したIDに書き換える。 入力パラメーターは次のURLを参照してください。今回は「楽天市場商品検索API」です。 https://webservice.rakuten.co.jp/api/ichibaitemsearch/ APIの種類によって入出力パラメータが代わります。 メインスレッド 内容は100行ほどのコードですので全文載せています。 Sample10.cs using System; using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Text; using System.Threading.Tasks; namespace RakutenAPI { class Program { private static HttpClient client; static void Main(string[] args) { //楽天市場商品検索API version:2017-07-06 string RequestURL = "https://app.rakuten.co.jp/services/api/IchibaItem/Search/20170706?"; //検索ワード string seachword = "ドラゴンボール"; //以下は入力パラメータ version:2017-07-06**************************************************** //参照URL  https://webservice.rakuten.co.jp/api/ichibaitemsearch/ //検索には必須条件と任意条件があるため必要なパラメータは指定しない場合Default値が参照される。 //入力値(右側)はすべてダブルクォーテーション"で囲む。 //入力値(左側)は参照URLから「パラメーター」の項目をそのまま入力。 var dic = new Dictionary<string, string>(); dic.Add("applicationId", "0000000000000000000"); dic.Add("keyword", ToUTF8(seachword)); dic.Add("format", "xml"); //取得方法 json または xml //dic.Add("formatVersion", "2"); //jsonのみ2を使える Default 1 //dic.Add("hits", "5"); //1ページあたりの取得件数 Default 30 //dic.Add("page", "1"); //取得ページ数 Default 1 //入力パラメータここまで************************************************************************ var mylist = new List<string>(); foreach(var m in dic) mylist.Add(m.Key + "=" + m.Value); RequestURL += string.Join("&", mylist); //クライアント接続開始 try{ client = new HttpClient(); } catch (Exception ex){ Console.WriteLine("【接続エラー】:" + ex.Message); } string HTMLtext = ""; try { //URLでHTMLを取得する。 Task<string> task_get = GetRequest(RequestURL); HTMLtext = task_get.Result; } catch (Exception ex) { Console.WriteLine("【応答エラー】:" + ex.Message); } Console.WriteLine(RequestURL); Console.WriteLine(HTMLtext); Console.ReadKey(); } //keywordだけUTF8のバイト変換が必要 public static string ToUTF8(string Keyword) { //String型を16進数バイト型文字列に変換 ”楽天”→”%E6%A5%BD%E5%A4%A9” byte[] bytedata = Encoding.UTF8.GetBytes(Keyword); Keyword = "%" + BitConverter.ToString(bytedata).Replace("-", "%"); return Keyword; } //URLからHTMLをゲット async static Task<string> GetRequest(string url) { HttpResponseMessage response = await client.GetAsync(url); string contentstr = response.StatusCode.ToString(); Console.WriteLine("【GetRequest Response】" + contentstr); if (response.StatusCode == System.Net.HttpStatusCode.OK) { contentstr = await response.Content.ReadAsStringAsync(); } else if (response.StatusCode == HttpStatusCode.Redirect) { //StatusCode リダイレクトのときの処理 var r = client.GetAsync(url).Result; Uri uri = new Uri(new Uri(url), r.Headers.Location); contentstr = client.GetAsync(uri).Result.Content.ReadAsStringAsync().Result; } return contentstr; } } } コンソールアプりが起動して下のような画面が出たら成功です。 今回は「ドラゴンボール」で検索してxmlデータで取得しています。 jsonでも良かったのですがC#だとここから必要な個所を取り出すのに AngleSharpなりJson変換などのNuGetパッケージが追加で必要になるので 今回はここまでとします。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

楽天API C# 商品検索の一覧取得する(1)

はじめに スクレイピングする上で重要なAPIの利用の実装例です。 PHPのサンプルはそこそこあったがC#で実行するサンプルが無かったので自前で 作ったのでご紹介します。 事前準備 楽天WEBサービス(Rakuten Developer)から「アプリID発行」する。 https://webservice.rakuten.co.jp/ とりあえず発行するだけなので入力内容は適当でOKです。 (楽天のアカウントさえあれば後からいくつでも発行できます) 発行後に「アプリID/デベロッパーID」をメモしておいてください。 動作内容 「ドラゴンボール」と検索してヒットするページ情報をXML形式で取得しています。 今回はXML形式のまま取得して必要なデータの整形は次回にしたいと思います。 開発環境 Microsoft Visual Studio Community 2019 Version 16.6.0 コンソールアプリ(.NET Framework) コピペで使うときの注意 コード内のapplicationIdの右側の”0000000000000000000”を先ほど取得したIDに書き換える。 入力パラメーターは次のURLを参照してください。今回は「楽天市場商品検索API」です。 https://webservice.rakuten.co.jp/api/ichibaitemsearch/ APIの種類によって入出力パラメータが代わります。 メインスレッド 内容は100行ほどのコードですので全文載せています。 Sample10.cs using System; using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Text; using System.Threading.Tasks; namespace RakutenAPI { class Program { private static HttpClient client; static void Main(string[] args) { //楽天市場商品検索API version:2017-07-06 string RequestURL = "https://app.rakuten.co.jp/services/api/IchibaItem/Search/20170706?"; //検索ワード string seachword = "ドラゴンボール"; //以下は入力パラメータ version:2017-07-06**************************************************** //参照URL  https://webservice.rakuten.co.jp/api/ichibaitemsearch/ //検索には必須条件と任意条件があるため必要なパラメータは指定しない場合Default値が参照される。 //入力値(右側)はすべてダブルクォーテーション"で囲む。 //入力値(左側)は参照URLから「パラメーター」の項目をそのまま入力。 var dic = new Dictionary<string, string>(); dic.Add("applicationId", "0000000000000000000"); dic.Add("keyword", ToUTF8(seachword)); dic.Add("format", "xml"); //取得方法 json または xml //dic.Add("formatVersion", "2"); //jsonのみ2を使える Default 1 //dic.Add("hits", "5"); //1ページあたりの取得件数 Default 30 //dic.Add("page", "1"); //取得ページ数 Default 1 //入力パラメータここまで************************************************************************ var mylist = new List<string>(); foreach(var m in dic) mylist.Add(m.Key + "=" + m.Value); RequestURL += string.Join("&", mylist); //クライアント接続開始 try{ client = new HttpClient(); } catch (Exception ex){ Console.WriteLine("【接続エラー】:" + ex.Message); } string HTMLtext = ""; try { //URLでHTMLを取得する。 Task<string> task_get = GetRequest(RequestURL); HTMLtext = task_get.Result; } catch (Exception ex) { Console.WriteLine("【応答エラー】:" + ex.Message); } Console.WriteLine(RequestURL); Console.WriteLine(HTMLtext); Console.ReadKey(); } //keywordだけUTF8のバイト変換が必要 public static string ToUTF8(string Keyword) { //String型を16進数バイト型文字列に変換 ”楽天”→”%E6%A5%BD%E5%A4%A9” byte[] bytedata = Encoding.UTF8.GetBytes(Keyword); Keyword = "%" + BitConverter.ToString(bytedata).Replace("-", "%"); return Keyword; } //URLからHTMLをゲット async static Task<string> GetRequest(string url) { HttpResponseMessage response = await client.GetAsync(url); string contentstr = response.StatusCode.ToString(); Console.WriteLine("【GetRequest Response】" + contentstr); if (response.StatusCode == System.Net.HttpStatusCode.OK) { contentstr = await response.Content.ReadAsStringAsync(); } else if (response.StatusCode == HttpStatusCode.Redirect) { //StatusCode リダイレクトのときの処理 var r = client.GetAsync(url).Result; Uri uri = new Uri(new Uri(url), r.Headers.Location); contentstr = client.GetAsync(uri).Result.Content.ReadAsStringAsync().Result; } return contentstr; } } } コンソールアプりが起動して下のような画面が出たら成功です。 今回は「ドラゴンボール」で検索してxmlデータで取得しています。 jsonでも良かったのですがC#だとここから必要な個所を取り出すのに AngleSharpなりJson変換などのNuGetパッケージが追加で必要になるので 今回はここまでとします。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity(C#)】unityroomにカメラを使ったゲームを公開する

はじめに Unity1Weekという1週間ゲームジャムに参加し、 カメラを使ったゲームを開発しました。 一応、遊べるレベルまで完成しましたが、他の環境でもちゃんと動くのかが心配です?#unityroom #unity1week pic.twitter.com/1gwXC7Bf2M— KENTO⚽️XRエンジニア?Shader100記事マラソン挑戦中36/100 (@okprogramming) May 4, 2021 口を開けるとオジサンが笑いながらジャンプします。 前例が無い中、いろいろと問題を解決してようやく公開できたので 同じ苦労する人が減ることを願いメモします。 カメラへのアクセス unityroomでカメラを使ったゲームを公開できるかどうかの調査から行いました。 カメラのアクセスを試みるにあたって、 jsのプラグインを作ったりといろいろ検証してました。 結論、iframeで画面にWebGLを埋め込んでいる現時点(2021/06)の形式では こちらから何かしら手を加えてカメラにアクセスすることはできなかったです。 なので、運営の方に直接問い合わせて許可をいただき、 埋め込まれたリンクを直指定してWebGLを開く形式で対応しました。 概要欄に「ここから遊んでね!」みたいなのを書くというやり方です。 jsプラグイン カメラへのパーミッション要求はWebCamTextureを呼び出すなど、 カメラを必要とする関数に紐づいて勝手に要求処理が走るのですが、 せっかくなので勉強がてらjsのプラグインを作成してパーミッション要求を行ってみました。 以下C#側からjsを呼び出すサンプルです。 まず.jslibという拡張子のファイルを作成し、そこにjsの処理を記述します。 保存先ディレクトリはAssets/Plugins配下です。 カメラのパーミッション要求 mergeInto(LibraryManager.library, { CameraRequest: function () { var constraints = { audio: false, video: { width: 640, height: 480 } }; navigator.mediaDevices.getUserMedia(constraints) .then(function (mediaStream) { var video = document.querySelector('video'); video.srcObject = mediaStream; video.onloadedmetadata = function (e) { video.play(); }; }) .catch(function (err) { console.log(err.name + ": " + err.message); }); }, }); 次にC#側のコードです。クラス名がjsと一致していればOKです。 任意の箇所で呼び出し可能です。私はCameraRequest()をボタン押下時に呼び出すよう実装しました。 [DllImport("__Internal")] private static extern void CameraRequest(); 【参考リンク】:Unity2020でWebGLを試してみた! ~基本編~ OpenCV カメラの問題は片付きました。 次に、OpenCVで顔の認識を行うサンプルがWebGLで問題なく動作するか検証しました。 下記のサンプルをほぼそのまま流用しました。 【参考リンク】:FaceTracker Example ただ、結論から言うとFPSが安定しませんでした。 何か削れる処理や軽量化に繋がる画期的方法が無いか模索しましたが、 サンプルそのままでもさほど安定していなかったので限界と判断し早急に諦めました。 model及びcascadeの読み込み OpenCVではモデルデータと識別器(cascade)の読み込みが必要で、 WebGLの場合はStreamingAssetsとしての利用が可能な実装になっていました。 現時点でunityroomにおいてStreamingAssetsは使用できないため、 Gistにプライベート(リンクを知っている人のみアクセス可)で公開し、参照するようにしました。 【参考リンク】:UnityroomではStreaming Assetsは使えないのでしょうか? 工夫した点 顔の認識が確認できた以降の実装では キャラクターがジャンプする処理などを追加して挙動を確認していきました。 ステージがクリア不可能でないか等を動かしてデバッグする際に カメラを付けて口をパクパクして...というのが面倒だったので 入力機能をモジュール化してキー入力と顔入力を切り替えれるようにしました。 DIライブラリにはVContainerを使用しました。 おわりに unity1weekはエンジニアになって以来、 毎回新しいことに取り組む場として活用させていただいています。 1週間の中盤~終盤にかけて参加したことを後悔する(己の実装力に心が折れる)瞬間もありますが、 他の方の頑張りをSNS等で拝見して自分に鞭を打ちながら毎回走り切れています。(ほぼ遅刻) 運営の方はもちろんのこと、参加者の方、遊んでくれた方、みなさんありがとうございます。 次回も楽しみにしています。 参考リンク 「OpenCV for Unity」AssetのWebGL対応がやって来たヤァ!ヤァ!ヤァ!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【C#/ASP.NET】Web APIのベンチマークを計測する

概要 クライアントが「Web APIをコールし、サーバから応答を得るまでの時間」を計測した。 具体的には、HttpClientとBenchmarkDotNetを使って、処理時間の集計(平均値・最小値・最大値を算出)し、集計結果をHTMLでエクスポートとした。 計測の手順と実装を掲載する。 動作環境 クライアント .NET Framework4.6.1 Nugetパッケージ  ・BenchmarkDotNet 0.13.0  ・NewtonsoftJson 13.0.1 サーバー ASP.NET Web API .NET Framework4.6.1 計測予定のWebAPIの一覧 今回はGET/PUT/POST/DELETEを準備する。 下記表のWeb APIをサーバ側に実装する(自動生成コードをそのまま利用する)。 HTTPメソッド URL GET http://localhost:19691/api/values POST http://localhost:19691/api/values PUT http://localhost:19691/api/values/{id} DELETE http://localhost:19691/api/values/{id} アプリケーションを作成する。 クライアントを作成する コンソールアプリケーションでソリューションを作成して、下記手順を実行する。 1. BenchmarkDotNetを適用する。 「BenchmarkDotNet」をNugetパッケージでインストールする。 2. 計測対象のメソッドを作成する。 「BenchmarkDotNet」で計測する対象メソッド、その他オプションは「属性」で設定する。 各属性の意味はコメントで補足説明する。 WebApiClient using System.Net.Http; using System.Text; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using Newtonsoft.Json; namespace BenchMark { [HtmlExporter]//Htmlエクスポート public class WebApiClient { private static readonly HttpClient Client = new HttpClient(); private static string _jsonString; private static StringContent _data; [GlobalSetup]//初回セットアップ public void GlobalSetUp() { _jsonString = JsonConvert.SerializeObject("TestMessage"); _data = new StringContent(_jsonString, Encoding.UTF8, mediaType: "application/json"); } [Benchmark]//計測対象のメソッドに指定 public async Task GetAsync() { var response = await Client.GetAsync("http://localhost:19691/api/values"); response.EnsureSuccessStatusCode(); } [Benchmark] public async Task PutAsync() { var response = await Client.PutAsync("http://localhost:19691/api/values/5", _data); response.EnsureSuccessStatusCode(); } [Benchmark] public async Task PostAsync() { var response = await Client.PostAsync("http://localhost:19691/api/values", _data); response.EnsureSuccessStatusCode(); } [Benchmark] public async Task DeleteAsync() { var response = await Client.DeleteAsync("http://localhost:19691/api/values/5"); response.EnsureSuccessStatusCode(); } } } 3. メイン関数から計測対象をコールする。 Program.cs using System.Threading.Tasks; using BenchmarkDotNet.Running; namespace BenchMark { class Program { static void Main(string[] args) { //CheckOperation().GetAwaiter().GetResult(); //計測を開始する BenchmarkRunner.Run<WebApiClient>(); } /// <summary> /// ベンチマーク測定前の動作確認用 /// </summary> static async Task CheckOperation() { var client = new WebApiClient(); client.GlobalSetUp(); await client.GetAsync(); await client.PutAsync(); await client.PostAsync(); await client.DeleteAsync(); } } } サーバを作成する。 自動生成コード使って、Web APIを準備する。 1. Web アプリケーションでソリューションを作成 Web APIを選ぶ。 2. Web APIが自動生成されたことを確認する。 「ソリューションエクスプローラー」 →「Controllers」   ValuesControllers.csを開く。 ValuesControllers.cs using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Web.Http; namespace HttpServerBenchMark.Controllers { public class ValuesController : ApiController { // GET api/values public IEnumerable<string> Get() { return new string[] { "value1", "value2" }; } // GET api/values/5 public string Get( int id ) { return "value"; } // POST api/values public void Post( [FromBody]string value ) { } // PUT api/values/5 public void Put( int id, [FromBody]string value ) { } // DELETE api/values/5 public void Delete( int id ) { } } } パフォーマンス計測 1. サーバーを実行する。 2. クライアントを実行する。 ※Releaseビルドで実行すること。 計測が完了することを待つ。 計測開始時のターミナル出力 // Validating benchmarks: // ***** BenchmarkRunner: Start ***** // ***** Found 4 benchmark(s) in total ***** // ***** Building 1 exe(s) in Parallel: Start ***** : : 計測終了時のターミナル出力 // ***** BenchmarkRunner: End ***** // ** Remained 0 benchmark(s) to run ** Run time: 00:01:59 (119.94 sec), executed benchmarks: 4 Global total time: 00:02:02 (122.68 sec), executed benchmarks: 4 3. 集計結果を確認する。 ターミナル出力で「Summary」を探して確認する。 結果サマリーを確認する // * Summary * BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19043.985 (21H1/May2021Update) Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores [Host] : .NET Framework 4.8 (4.8.4341.0), X86 LegacyJIT DefaultJob : .NET Framework 4.8 (4.8.4341.0), X86 LegacyJIT | Method | Mean | Error | StdDev | Median | |------------ |---------:|----------:|----------:|---------:| | GetAsync | 1.921 ms | 0.1567 ms | 0.4469 ms | 1.749 ms | | PutAsync | 1.495 ms | 0.0319 ms | 0.0926 ms | 1.494 ms | | PostAsync | 1.472 ms | 0.0291 ms | 0.0638 ms | 1.474 ms | | DeleteAsync | 1.281 ms | 0.0256 ms | 0.0455 ms | 1.293 ms | ファイルのExportを確認する。 今回はHTMLファイル出力しているため、ターミナルでExport先を確認する。 // ***** BenchmarkRunner: Finish ***** // * Export * BenchmarkDotNet.Artifacts\results\BenchMark.WebApiClient-report.csv BenchmarkDotNet.Artifacts\results\BenchMark.WebApiClient-report-github.md BenchmarkDotNet.Artifacts\results\BenchMark.WebApiClient-report.html ファイルエクスプローラで下記パスに移動する。 ソリューションのパス\bin\Release\BenchmarkDotNet.Artifacts 下記画像のファイルが存在する。 「BenchMark.WebApiClient-20210606-231210.log(※)」に、ターミナル出力と同様の内容が記録されている。 ※ソリューション名-クラス名-実行日時.logって命名の様子。 ソリューションのパス\bin\Release\BenchmarkDotNet.Artifacts\resultsに移動する。 BenchmarkDotNet.Artifacts\results\BenchMark.WebApiClient-report.htmlを開く。 下図のようにサマリーと同様の内容が表示される。 logファイルとResultフォルダを控えておけば、報告資料や調査結果にそのまま利用できそう。 参考 No リンク 説明 1 【.NET/C#】メソッドのパフォーマンスを簡単に集計するライブラリの紹介 過去に書いたQuiitaの記事。内容はBenchmarkDotNetの紹介と使い方に関して記載した。 2 BenchmarkDotNetの公式ドキュメント 公式ドキュメントのリンク。 3 Github:本記事のソース 本記事のソースをGithubに公開してます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C# HttpClient 自動ログイン 「楽天市場」

はじめに WEBサイトから様々なデータを自動で収集するとき(いわゆるスクレイピングするとき)は、 時にユーザーアカウントを作成してログインするサイトも対象となるときがある。 私の記事でオンライン麻雀サイトに自動ログインしてプレイ履歴を取得するソフトは作成して紹介したが 他のサイトにもログインして自動ログインをいろいろ実行してみようと思う。 あらかじめ断っておくが今回、楽天市場にログインまではできたが商品情報などは楽天APIでしかアクセスできないよう スクリプトが組んであり商品情報の抜き取りはできなかったためご了承いただきたい。 それでは早速、楽天市場のログイン画面を開いてみる。 今回は初めからChromeブラウザの検証画面を同時に表示してある(画面上で右クリックして「検証」) HTMLのスクリプトをカーソルでなぞると左側のサイトの中のどこのことを書いているのか グレーにして表示してくれているので適時▽を押しながらツリーを表示していき該当箇所を探す。 「ユーザID」はinputフォームのname ="u" 「パスワード」はinputフォームのname ="p" ということが分かった。 また、<input>タグは必ず<form>タグの内側にあるのでformの中のaction="./logini"も確認しておく。 (actionはPOST要求する際にサイトのURLにactionを続けて書いたURLがPOSTの送り先になるため) メインスレッド(変更部分"./logini") Rakutenlogin.cs using System; using System.Net; using System.Net.Http; using System.Threading.Tasks; namespace HttpClient_test { class Program { private static HttpClient client; static void Main(string[] args) { //ログインページ string url_login = "https://grp01.id.rakuten.co.jp/rms/nid/vc?__event=login&service_id=top"; //formタグ内のaction string url_action = "./logini";        //POST要求先 string url_post = url_login + url_action; //ログイン後にジャンプするページ(ホーム画面) string url_history = "https://www.rakuten.co.jp/"; string USER_ID = "XXXXXXXXXXXXXX@mineo.jp"; string PASSWORD = "XXXXXXXX"; try { //クライアント接続開始 client = new HttpClient(); } catch (Exception ex) { Console.WriteLine("【接続エラー】:" + ex.Message); } string HTMLtext = ""; try { //POST要求(ログインしてクッキー取得) Task<bool> task_login = PostRequest(url_post, USER_ID, PASSWORD); //URLでHTMLを取得する。 if (task_login.Result) { Task<string> task_get = GetRequest(url_history); HTMLtext = task_get.Result; } } catch (Exception ex) { Console.WriteLine("【応答エラー】:" + ex.Message); } //HTMLテキスト確認 Console.WriteLine(HTMLtext); //HTML構造解析 //string resultstr = AnalizeHtml(50, HTMLtext); // ファイルの保存(別タスクで非同期処理 続けて処理をおこなうときはWait()を削除する) //Task task1 = Task.Run(() => { SaveFile(resultstr); }); //task1.Wait(); //End待機 //Thread.Sleep(1000); Console.ReadKey(); } //POST要求(ログイン) async static Task<bool> PostRequest(string url_post, string user, string password) { //POSTで送る内容作成。mContent.Add(属性の値, 属性の名前) MultipartFormDataContent mContent = new MultipartFormDataContent(); mContent.Add(new StringContent(user), "u"); mContent.Add(new StringContent(password), "p"); //ヘッダ情報確認 Console.WriteLine("【PostRequest Headers】" + mContent.Headers); //POSTしてレスポンスの要求。(要求先、要求内容) HttpResponseMessage response = await client.PostAsync(url_post, mContent); Console.WriteLine("【PostRequest Response】 " + response.StatusCode.ToString()); if (response.StatusCode == HttpStatusCode.OK) { //応答ステータスがOKならHTML文字列を取得する。 string contentstr = await response.Content.ReadAsStringAsync(); Console.WriteLine("【PostRequest HTMLcontent】" + contentstr); Console.WriteLine("【PostRequest Cookie】" + response.Headers.GetValues("Set-Cookie").First()); } return true; } PostRequest (変更部分"u","p") Method1.cs async static Task<bool> PostRequest(string url_post, string user, string password) { //POSTで送る内容作成。mContent.Add(属性の値, 属性の名前) MultipartFormDataContent mContent = new MultipartFormDataContent(); mContent.Add(new StringContent(user), "u"); mContent.Add(new StringContent(password), "p"); //ヘッダ情報確認 Console.WriteLine("【PostRequest Headers】" + mContent.Headers); //POSTしてレスポンスの要求。(要求先、要求内容) HttpResponseMessage response = await client.PostAsync(url_post, mContent); Console.WriteLine("【PostRequest Response】 " + response.StatusCode.ToString()); if (response.StatusCode == HttpStatusCode.OK) { //応答ステータスがOKならHTML文字列を取得する。 string contentstr = await response.Content.ReadAsStringAsync(); Console.WriteLine("【PostRequest HTMLcontent】" + contentstr); Console.WriteLine("【PostRequest Cookie】" + response.Headers.GetValues("Set-Cookie").First()); } return true; } GetRequest (変更なし) Method2.cs async static Task<string> GetRequest(string url) { HttpResponseMessage response = await client.GetAsync(url); string contentstr = response.StatusCode.ToString(); Console.WriteLine("【GetRequest Response】" + contentstr); if (response.StatusCode == System.Net.HttpStatusCode.OK) { contentstr = await response.Content.ReadAsStringAsync(); } else if (response.StatusCode == HttpStatusCode.Redirect) { //StatusCode リダイレクトのときの処理 var r = client.GetAsync(url).Result; Uri uri = new Uri(new Uri(url), r.Headers.Location); contentstr = client.GetAsync(uri).Result.Content.ReadAsStringAsync().Result; } return contentstr; } 終わりに 上記のソフトではPOST要求とGET要求それぞれで通信ステータスが返るのでURLの間違いなのか、入力フォームの間違いなのか、 エラーの確認がしやすく自作ではあるが使いやすい造りになっている。以前にMJサイトで右往左往して作った甲斐があった。 今回はあまりデータを抜き取れる要素が無かったので次回は楽天APIを使って商品情報の取得を目指したいと思います。 おわり
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む