- 投稿日:2020-08-18T19:42:11+09:00
ASP.NET Core でアプリの起動時に設定を検証する
経緯
ASP.NET Core には appsettings.json 等の設定から読み込んだデータを検証する機能があります。
ASP.NET Core のオプション パターン : オプションの検証
上記の機能を利用すると簡単に検証を実装出来て非常に便利なのですが、検証のタイミングが DI コンテナから取り出したタイミングになってしまい設定ミスに気付くのが遅れてしまうことが何度かありました。
設定ミスはなるべく早く気づくようにしたいので、こちらの Github Issue の実装例を参考に、起動時に設定を検証する実装を試しましたので、備忘録を兼ねて実装と使い方の例を残します。
環境
ASP.NET Core 3.1
実装
設定を検証する StartupFilter を定義します。
public class StartupOptionsValidationFilter<T> : IStartupFilter { public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next) { return builder => { var options = builder.ApplicationServices.GetService(typeof(IOptions<>).MakeGenericType(typeof(T))); if (options != null) _ = ((IOptions<object>)options).Value; next(builder); }; } }上記の StartupFilter を DI に登録する拡張メソッドを定義します。
public static class OptionsBuilderValidationExtensions { public static OptionsBuilder<TOptions> <TOptions>(this OptionsBuilder<TOptions> optionsBuilder) where TOptions : class { optionsBuilder.Services.AddTransient<IStartupFilter, StartupOptionsValidation<TOptions>>(); return optionsBuilder; } }使い方例
設定ファイルをバインドするクラスを用意します。
今回は DataAnnotations による検証と、カスタムの検証ルールIsValid()
を用意しました。public class SettingA { [Required] public string OptionA { get; set; } public bool UseOptionB { get; set; } public string OptionB { get; set; } public bool IsValid() { // UseOptionB が true の場合、OptionB の設定が必須 return !(UseOptionB && OptionB == null); } }Startup.cs の
ConfigureServices
メソッドで設定ファイルを DI に以下のように追加します。public void ConfigureServices(IServiceCollection services) { services.AddOptions<SettingA>() .Configure(option => Configuration.Bind("settingA", option)) .ValidateDataAnnotations() // DataAnnotations による検証 .Validate(option => option.IsValid()) // カスタムの検証ルール "IsValid()" による検証 .ValidateEagerly(); // 起動時に設定を検証する StartupFilter を DI に追加 services.AddControllers(); }
- 投稿日:2020-08-18T19:42:11+09:00
ASP.NET Core の IStartupFilter でアプリの起動時に設定を検証する
経緯
ASP.NET Core には appsettings.json 等の設定から読み込んだデータを検証する機能があります。
ASP.NET Core のオプション パターン : オプションの検証
上記の機能を利用すると簡単に検証を実装出来て非常に便利なのですが、検証のタイミングが DI コンテナから取り出したタイミングになってしまい設定ミスに気付くのが遅れてしまうことが何度かありました。
設定ミスはなるべく早く気づくようにしたいので、こちらの Github Issue の実装例を参考に、起動時に設定を検証する実装を試しましたので、備忘録を兼ねて実装と使い方の例を残します。
環境
ASP.NET Core 3.1
実装
設定を検証する StartupFilter を定義します。
public class StartupOptionsValidationFilter<T> : IStartupFilter { public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next) { return builder => { var options = builder.ApplicationServices.GetService(typeof(IOptions<>).MakeGenericType(typeof(T))); if (options != null) _ = ((IOptions<object>)options).Value; next(builder); }; } }上記の StartupFilter を DI に登録する拡張メソッドを定義します。
public static class OptionsBuilderValidationExtensions { public static OptionsBuilder<TOptions> <TOptions>(this OptionsBuilder<TOptions> optionsBuilder) where TOptions : class { optionsBuilder.Services.AddTransient<IStartupFilter, StartupOptionsValidation<TOptions>>(); return optionsBuilder; } }使い方例
設定ファイルをバインドするクラスを用意します。
今回は DataAnnotations による検証と、カスタムの検証ルールIsValid()
を用意しました。public class SettingA { [Required] public string OptionA { get; set; } public bool UseOptionB { get; set; } public string OptionB { get; set; } public bool IsValid() { // UseOptionB が true の場合、OptionB の設定が必須 return !(UseOptionB && OptionB == null); } }Startup.cs の
ConfigureServices
メソッドで設定ファイルを DI に以下のように追加します。public void ConfigureServices(IServiceCollection services) { services.AddOptions<SettingA>() .Configure(option => Configuration.Bind("settingA", option)) .ValidateDataAnnotations() // DataAnnotations による検証 .Validate(option => option.IsValid()) // カスタムの検証ルール "IsValid()" による検証 .ValidateEagerly(); // 起動時に設定を検証する StartupFilter を DI に追加 services.AddControllers(); }
- 投稿日:2020-08-18T15:02:55+09:00
C#で立体を描く簡易的なプログラム~その2~
C#で立体を描く簡易的なプログラム
前回の記事で投稿したプログラムがいろいろと間違っていたので、修正しました。ライブラリなど使えば簡単にできると思いますが、やはりここは、全て自作です!
立体を描くクラス
G3D.csusing System; using System.Drawing; using System.Windows.Forms; namespace _3D { class G3D { private Bitmap canvas; private Graphics g; private PictureBox pictureBox; private double x_adj, y_adj; private double x_theta = 0, y_theta = 0, z_theta = 0; public G3D(PictureBox p) { pictureBox = p; canvas = new Bitmap(p.Width, p.Height); g = Graphics.FromImage(canvas); x_adj = p.Width / 2; y_adj = p.Height / 2; } public void cuboid(double x1, double y1, double z1, double x2, double y2, double z2, double x_rot_theta, double y_rot_theta, double z_rot_theta) { double x_rot = x_theta; double y_rot = y_theta; double z_rot = z_theta; Point[] p = { point_culc(x_rot, y_rot, z_rot, x_rot_theta,y_rot_theta,z_rot_theta,x1, y1, z1), point_culc(x_rot, y_rot, z_rot, x_rot_theta,y_rot_theta,z_rot_theta, x2, y1, z1), point_culc(x_rot, y_rot, z_rot, x_rot_theta,y_rot_theta,z_rot_theta,x2, y2, z1), point_culc(x_rot, y_rot, z_rot, x_rot_theta,y_rot_theta,z_rot_theta,x1, y2, z1) }; g.DrawPolygon(Pens.White, p); Point[] p1 = { point_culc(x_rot, y_rot, z_rot, x_rot_theta,y_rot_theta,z_rot_theta,x2, y1, z1), point_culc(x_rot, y_rot, z_rot, x_rot_theta,y_rot_theta,z_rot_theta,x2, y2, z1), point_culc(x_rot, y_rot, z_rot, x_rot_theta,y_rot_theta,z_rot_theta,x2, y2, z2), point_culc(x_rot, y_rot, z_rot, x_rot_theta,y_rot_theta,z_rot_theta, x2, y1, z2) }; g.DrawPolygon(Pens.White, p1); Point[] p2 = { point_culc(x_rot, y_rot, z_rot, x_rot_theta,y_rot_theta,z_rot_theta,x1, y1, z1), point_culc(x_rot, y_rot, z_rot, x_rot_theta,y_rot_theta,z_rot_theta, x1, y2, z1), point_culc(x_rot, y_rot, z_rot, x_rot_theta,y_rot_theta,z_rot_theta, x1, y2, z2), point_culc(x_rot, y_rot, z_rot, x_rot_theta,y_rot_theta,z_rot_theta,x1, y1, z2)}; g.DrawPolygon(Pens.White, p2); Point[] p3 = { point_culc(x_rot, y_rot, z_rot, x_rot_theta,y_rot_theta,z_rot_theta, x1, y1, z2), point_culc(x_rot, y_rot, z_rot, x_rot_theta,y_rot_theta,z_rot_theta, x2, y1, z2), point_culc(x_rot, y_rot, z_rot, x_rot_theta,y_rot_theta,z_rot_theta, x2, y2, z2), point_culc(x_rot, y_rot, z_rot, x_rot_theta,y_rot_theta,z_rot_theta, x1, y2, z2)}; g.DrawPolygon(Pens.White, p3); Point[] p4 = { point_culc(x_rot, y_rot, z_rot, x_rot_theta,y_rot_theta,z_rot_theta, x1, y1, z1), point_culc(x_rot, y_rot, z_rot, x_rot_theta,y_rot_theta,z_rot_theta, x2, y1, z1), point_culc(x_rot, y_rot, z_rot, x_rot_theta,y_rot_theta,z_rot_theta, x2, y1, z2), point_culc(x_rot, y_rot, z_rot, x_rot_theta,y_rot_theta,z_rot_theta, x1, y1, z2)}; g.DrawPolygon(Pens.White, p4); Point[] p5 = { point_culc(x_rot, y_rot, z_rot, x_rot_theta,y_rot_theta,z_rot_theta, x1, y2, z1), point_culc(x_rot, y_rot, z_rot, x_rot_theta,y_rot_theta,z_rot_theta, x2, y2, z1), point_culc(x_rot, y_rot, z_rot, x_rot_theta,y_rot_theta,z_rot_theta, x2, y2, z2), point_culc(x_rot, y_rot, z_rot, x_rot_theta,y_rot_theta,z_rot_theta, x1, y2, z2)}; g.DrawPolygon(Pens.White, p5); g.DrawLine(Pens.DarkRed, point_culc(x_theta, y_theta, z_theta, x_rot_theta, y_rot_theta, z_rot_theta, ((x1 + x2) / 2) + 50, (y1 + y2) / 2, (z1 + z2) / 2), point_culc(x_theta, y_theta, z_theta, x_rot_theta, y_rot_theta, z_rot_theta, (x1 + x2) / 2, (y1 + y2) / 2, (z1 + z2) / 2) ); g.DrawLine(Pens.DarkGreen, point_culc(x_theta, y_theta, z_theta, x_rot_theta, y_rot_theta, z_rot_theta, (x1 + x2) / 2, ((y1 + y2) / 2) + 50, (z1 + z2) / 2), point_culc(x_theta, y_theta, z_theta, x_rot_theta, y_rot_theta, z_rot_theta, (x1 + x2) / 2, (y1 + y2) / 2, (z1 + z2) / 2) ); g.DrawLine(Pens.LightYellow, point_culc(x_theta, y_theta, z_theta, x_rot_theta, y_rot_theta, z_rot_theta, (x1 + x2) / 2, (y1 + y2) / 2, ((z1 + z2) / 2) + 50), point_culc(x_theta, y_theta, z_theta, x_rot_theta, y_rot_theta, z_rot_theta, (x1 + x2) / 2, (y1 + y2) / 2, (z1 + z2) / 2) ); } public void clear() { g.Clear(Color.Black); } public void draw() { g.DrawLine(Pens.Red, point_culc(x_theta, y_theta, z_theta, 0, 0, 0, 100, 0, 0), point_culc(x_theta, y_theta, z_theta, 0, 0, 0, 0, 0, 0) ); g.DrawLine(Pens.Green, point_culc(x_theta, y_theta, z_theta, 0, 0, 0, 0, 100, 0), point_culc(x_theta, y_theta, z_theta, 0, 0, 0, 0, 0, 0) ); g.DrawLine(Pens.Yellow, point_culc(x_theta, y_theta, z_theta, 0, 0, 0, 0, 0, 100), point_culc(x_theta, y_theta, z_theta, 0, 0, 0, 0, 0, 0) ); pictureBox.Image = canvas; } public void camera_change(double rotx,double roty,double rotz) { x_theta = rotx; y_theta = roty; z_theta = rotz; theta_lim(ref x_theta); theta_lim(ref y_theta); theta_lim(ref z_theta); } public void camera_step(double rotx, double roty, double rotz) { x_theta += rotx; y_theta += roty; z_theta += rotz; theta_lim(ref x_theta); theta_lim(ref y_theta); theta_lim(ref z_theta); } private void theta_lim(ref double t) { if (t > 360) { t -= 360; } if (t < 0) { t = 360 + t; } } /// <summary> /// 回転後のポイント計算 /// </summary> /// <param name="rotx">X軸周りでの回転角度(視点)</param> /// <param name="roty">Y軸周りでの回転角度(視点)</param> /// <param name="rotz">Z軸周りでの回転角度(視点)</param> /// <param name="f_rotx">X軸周りでの回転角度(図形)</param> /// <param name="f_roty">Y軸周りでの回転角度(図形)</param> /// <param name="f_rotz">Z軸周りでの回転角度(図形)</param> /// <param name="x">X座標</param> /// <param name="y">Y座標</param> /// <param name="z">Z座標</param> /// <returns></returns> private Point point_culc(double rotx, double roty, double rotz, double f_rotx, double f_roty, double f_rotz, double x, double y, double z) { double rad = Math.PI / 180; //図形の回転 //X軸周りで回転 double X_x = x * 1; double Y_x = y * Math.Cos(f_rotx * rad) - z * Math.Sin(f_rotx * rad); double Z_x = y * Math.Sin(f_rotx * rad) + z * Math.Cos(f_rotx * rad); //Y軸周りで回転 double X_y = X_x * Math.Cos(f_roty * rad) + Z_x * Math.Sin(f_roty * rad); double Y_y = Y_x * 1; double Z_y = -X_x * Math.Sin(f_roty * rad) + Z_x * Math.Cos(f_roty * rad); //Z軸周りで回転 double X_z = X_y * Math.Cos(f_rotz * rad) - Y_y * Math.Sin(f_rotz * rad); double Y_z = X_y * Math.Sin(f_rotz * rad) + Y_y * Math.Cos(f_rotz * rad); double Z_z = Z_y * 1; //視点の回転 double X_ = Math.Cos(roty * rad) * Math.Cos(rotz * rad) * X_z - Math.Cos(roty * rad) * Math.Sin(rotz * rad) * Y_z + Math.Sin(roty * rad) * Z_z; double Y_ = (Math.Sin(rotx * rad) * Math.Sin(roty * rad) * Math.Cos(rotz * rad) + Math.Cos(rotx * rad) * Math.Sin(rotz * rad)) * X_z + (-Math.Sin(rotx * rad) * Math.Sin(roty * rad) * Math.Sin(rotz * rad) + Math.Cos(rotx * rad) * Math.Cos(rotz * rad)) * Y_z - Math.Sin(rotx * rad) * Math.Cos(roty * rad) * Z_z; return new Point((int)(X_ + x_adj), (int)(Y_ + y_adj)); } } }今回は、視点の回転、図形そのものの回転をできるようにしました。
前回は視点の回転のみだったんですが、視点を変えると言うことは、3次元座標自体も回転しないといけません。
例えばX軸周りに回転させた場合Y軸とZ軸も傾きます。
そこのプログラムがちょっとまずかったので、直しました。図形そのものの回転をしたときの座標を計算するプログラムは、前回の視点の回転をしたときの座標を計算するプログラムと同じです。
そこのところを誤解してしまったようです。実行結果
真ん中の赤緑黄は、3次元空間上のX軸Y軸Z軸を表しています。
小さく赤緑黄で表示されているのは、表示されている立体の元のX軸Y軸Z軸を表しています。
視点だけではなく、立体そのものも回転させているため、3次元空間上の3軸と、立体の元の3軸は向きが一致していません。
- 投稿日:2020-08-18T11:39:14+09:00
Microsoft Teams の直近の次のミーティングに参加するプログラムの作り方
Teams の会議参加依頼メールとかでメール内のリンクをクリックすると Teams が立ち上がって会議参加画面に行くやつありますよね。
あそこに書いてある URL さえ取得できれば、あとは OS ごとの作法に従って URL を起動する API を叩けばいけるはずです。Xamarin.Forms だと
await Xamarin.Essential.Launcher.OpenAsync(オンライン会議のURL);
みたいな感じで。オンライン会議の URL の取得方法
この URL の取得方法ですが Microsoft Graph API でとってこれます。多くのケースで自分の予定の会議をとってくることになると思うので、この API を使うことになります。
結果の値にはイベントを表すオブジェクトの配列(生 HTTP API で叩いたときは JSON です)で
isOnlineMeeting
プロパティでオンラインのミーティングかどうかを確認できる bool 値が取得できます。これが true だったらonlineMeeting.joinUrl
の中に目的の URL が入っています。C# でやると API を叩いて URL ゲットして開く処理は以下のような感じです。
var eventsResult = await _graphServiceClient.Me.Events .Request() .GetAsync(); var firstOnlineMeeting = eventsResult.FirstOrDefault(x => x.IsOnlineMeeting ?? false); if (firstOnlineMeeting == null) { await DisplayAlert("Information", "Cound not find an online meeting", "OK"); return; } if (await Launcher.CanOpenAsync(firstOnlineMeeting.OnlineMeeting.JoinUrl)) { await Launcher.OpenAsync(firstOnlineMeeting.OnlineMeeting.JoinUrl); }
_graphServiceClient
クラスは Microsoft.Graph パッケージにある Microsoft.Graph.GraphServiceClient クラスのインスタンスです。この API を呼ぶには Calendar.Read のアクセス許可がいるので Azure AD に以下のような権限を付けたアプリを登録して、その ClientId などを使ってサインインしてアクセストークンは事前にゲットしておきましょう。動く様子
ボタンのクリックイベントに先ほどのソースコードを張り付けたものを Android で動かすと以下のように動きます。
- 投稿日:2020-08-18T11:39:02+09:00
C#開発者がはまったJavaScriptのDate型から月日を取得する方法
- 投稿日:2020-08-18T10:28:08+09:00
Hull Voronoi 3Dで何度もSingular input data errorが出る原因
Hull Voronoi 3Dについて
3DのVoronoi図を計算するプログラムで、原作はC#で記述されています。私が作ったProcessing(Java)バージョンもあります。Convex HullのfindInitialPoints内で頻繁にSingular input data errorが出るので原因を調べてみました。
https://github.com/Scrawk/Hull-Delaunay-Voronoi
https://github.com/Nekodigi/Hull-Voronoi-3D原因
そもそも、Singular input data errorは入力した点が、一致しているときに発生します。findInitialPointsには、各軸の座標が最小値、最大値の点に絞り込んで渡すのですが、複数の軸の最小値、最大値を兼ねている場合、稀に点が不足し、同じ点が選ばれてしまうという事態が起こります。
解決法
点が少ない場合は、Singular input data errorが起こりやすいですが、その分処理も軽いので、findInitialPointsに全ての点を渡すことで解決しています。