20210729のC#に関する記事は4件です。

Prism コードサンプル学習:12-UsingCompositeCommands

Prism コードサンプル学習:12-UsingCompositeCommands はじめに 以下の記事の続きです。 https://qiita.com/mngreen/items/95dd62ee8086887f5d0b 12-UsingCompositeCommands 本サンプルでは、CompositeCommandsの実装方法を紹介しています。 Compositeという名前の通り、CompositeCommandが実行されるとそのコマンドに登録されている複数のDelegateCommandが実行される仕組みになっています。 エントリーポイントでは、ApplicationCommandsをシングルトンで登録しています。 namespace UsingCompositeCommands { public partial class App : PrismApplication { ... protected override void RegisterTypes(IContainerRegistry containerRegistry) { containerRegistry.RegisterSingleton<IApplicationCommands, ApplicationCommands>(); } } } CompositeCommandsでは複数モジュールから利用されること意識してか、別パッケージ管理となっていました。以下が実際の定義です。 namespace UsingCompositeCommands.Core { public interface IApplicationCommands { CompositeCommand SaveCommand { get; } } public class ApplicationCommands : IApplicationCommands { private CompositeCommand _saveCommand = new CompositeCommand(); public CompositeCommand SaveCommand { get { return _saveCommand; } } } } 実際に、xamlにバインディングするときには通常のコマンドと同様にバインディングするだけでよいように作られています。 <Window x:Class="UsingCompositeCommands.Views.MainWindow" ...> ... <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*" /> </Grid.RowDefinitions> <Button Content="Save" Margin="10" Command="{Binding ApplicationCommands.SaveCommand}"/> <TabControl Grid.Row="1" Margin="10" prism:RegionManager.RegionName="ContentRegion" /> </Grid> </Window> CompositeCommands実行時に実行してほしいコマンドは、以下のように登録しておく必要があります。 namespace ModuleA.ViewModels { public class TabViewModel : BindableBase { ... public TabViewModel(IApplicationCommands applicationCommands) { _applicationCommands = applicationCommands; UpdateCommand = new DelegateCommand(Update).ObservesCanExecute(() => CanUpdate); _applicationCommands.SaveCommand.RegisterCommand(UpdateCommand); } private void Update() { UpdateText = $"Updated: {DateTime.Now}"; } } } CompositeCommandsの定義はここにあります。 CompositeCommands.RegisterCommandはここにあります。ICommand型のリストにコマンドを詰め込んでいきます。 CompositeCommands.Executeでは、いったんキューに詰めなおし、そのキューが空になるまでループさせています。 想像でしかありませんが、キューに詰めなおすタイミングでlockしていることから、非同期でも操作されても困らないようにしているのだと思われます。 おわりに 今回はCompositeCommandsの利用方法を学びました。 このコマンドを利用すれば、閉じたりした場合に、一括で初期化できたりしそうですね。 次回、13-IActiveAwareCommandsについて見ていこうと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#  PDFでダイナミックスタンプを追加 

ダイナミックスタンプ(Dynamic Stamp)はコンピューターのユーザー情報パネルから情報を取得して、ユーザーと日付などの情報を表示できる動的なスタンです。今日はSpire.PDFという無料で使いやすいライブラリを利用してPDFでダイナミックスタンプを作成する方法を紹介します。 下準備 1.E-iceblueの公式サイトからFree Spire.PDF無料版をダウンロードしてください。 2.Visual Studioを起動して新規プロジェクトを作成してから、インストールされたファイルにあった相応しいSpire. PDF.dllを参照に追加してください。 (Net 4.0を例としたら、デフォルトパスは“Bin→NET4.0→PDF.dll”というようです。)   using System; using System.Drawing; using Spire.Pdf; using Spire.Pdf.Annotations; using Spire.Pdf.Annotations.Appearance; using Spire.Pdf.Graphics; namespace ConsoleApp8 { class Program { static void Main(string[] args) { //PdfDocument obejctを作成します。 PdfDocument doc = new PdfDocument(); //PDFをロードします。 doc.LoadFromFile(@"C:\Users\Administrator.SD-20151030NEMY\Desktop\java输出\吾輩.pdf"); //スタンプを作成するページを取得します。 PdfPageBase page = doc.Pages[4]; //テンプレートオブジェクトを作成します。 PdfTemplate template = new PdfTemplate(180, 50); //フォントを設定します。 PdfCjkStandardFont font1 = new PdfCjkStandardFont(PdfCjkFontFamily.SinoTypeSongLight, 16f, PdfFontStyle.Bold | PdfFontStyle.Italic); PdfTrueTypeFont font2 = new PdfTrueTypeFont(new Font("ms mincho", 10f), true); //ソリッドブラシとグラデーションブラシを作成します。 PdfSolidBrush brush = new PdfSolidBrush(Color.Purple); RectangleF rect = new RectangleF(new PointF(0, 0), template.Size); PdfLinearGradientBrush gradientBrush = new PdfLinearGradientBrush(rect, Color.White, Color.LightBlue, PdfLinearGradientMode.Horizontal); //角丸長方形のパスを作成します。 int CornerRadius = 10; PdfPath path = new PdfPath(); path.AddArc(template.GetBounds().X, template.GetBounds().Y, CornerRadius, CornerRadius, 180, 90); path.AddArc(template.GetBounds().X + template.Width - CornerRadius, template.GetBounds().Y, CornerRadius, CornerRadius, 270, 90); path.AddArc(template.GetBounds().X + template.Width - CornerRadius, template.GetBounds().Y + template.Height - CornerRadius, CornerRadius, CornerRadius, 0, 90); path.AddArc(template.GetBounds().X, template.GetBounds().Y + template.Height - CornerRadius, CornerRadius, CornerRadius, 90, 90); path.AddLine(template.GetBounds().X, template.GetBounds().Y + template.Height - CornerRadius, template.GetBounds().X, template.GetBounds().Y + CornerRadius / 2); // テンプレートで角丸長方形のパスを描き、グラデーションで塗りつぶします。 template.Graphics.DrawPath(gradientBrush, path); //テンプレートで角丸長方形のパスを描き、紫色でパスを塗りつぶします。 template.Graphics.DrawPath(PdfPens.Purple, path); //テンプレートでテキスト、ユーザー名と日付を描きます。 String s1 = "検証済みです。"; String s2 = System.Environment.UserName + " " + DateTime.Now.ToString("F"); template.Graphics.DrawString(s1, font1, brush, new PointF(5, 5)); template.Graphics.DrawString(s2, font2, brush, new PointF(2, 28)); //PdfRubberStampAnnotationオブジェクトを作成し、そのサイズを設定します。 PdfRubberStampAnnotation stamp = new PdfRubberStampAnnotation(new RectangleF(new PointF(page.ActualSize.Width - 250, 200), template.Size)); //PdfApperanceオブジェクトを作成し、テンプレートをnormalにします。 PdfAppearance apprearance = new PdfAppearance(stamp); apprearance.Normal = template; //スタンプでPdfApperanceオブジェクトを適用します。 stamp.Appearance = apprearance; //スタンプをPdfAnnotationに追加します。 page.AnnotationsWidget.Add(stamp); //保存します。 doc.SaveToFile("output.pdf", FileFormat.PDF); } } 実行結果  
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

IdentityServer 4 v3.xからv4.xへの移行手順まとめ

はじめに .NETで標準的(?)に利用されているOIDCプロバイダーであるIdentityServer 4ですが、現在のバージョンの更新は2022/11までになっています。移行先はDuende Softwareが公開するDuende IdentityServerです。 IdentityServer 4 はデータベースにEntityFrameworkを使っているか、利用中のバージョンが4.x以前かどうかで移行方法が大きく変わってきます。それぞれの移行方法はDuendeのドキュメントにまとめられているので確認してみてください。この記事では、Duende IdentityServerへの前段階として3.x -> 4.x への移行でドキュメントには書かれていない部分を中心に説明します。 IdentityServer 4とDuende IdentityServerの使用料的なはなし 今回はIdentityServer 4のv3.x -> v4.x のバージョンアップなので直接の話題ではありませんが、後継のDuende IdentityServerは独自のDUENDE™ SOFTWARE LICENSE AGREEMENTを利用しているため、今後Duende IdentityServerを商用で利用する場合は注意が必要です。 ライセンスの内容は各自確認していただくとして、お金の話だけだとざっくりこんな感じです。 個人が非商用で利用する場合は無料 事業体全体の収入が100万米ドル未満であれば無料 事業体全体の収入が100万米ドル以上であれば有償 → 価格表 移行手順 ざっくり下記の3つのステップを実施します。 ライブラリのバージョンアップ 3.x->4.xでブレーキングチェンジとなった項目の修正 PersistedGrantDbContext及びConfigurationDbContextのマイグレーション適用(使っていれば) うちの認証サーバーではPersistedGrantDbContextとConfigurationDbContextは利用していないので、この記事では触れませんがそれぞれのRDBMS毎のマイグレーション方法がMigrating Your IdentityServer4 v3 Database to IdentityServer4 v4に詳しくまとまっているので確認してみてください。 1. ライブラリのバージョンアップ NuGetで置き換えてもよいですし、csprojファイルを直に編集してもよいです。 うちの環境では、PersistedGrantStoreに前述の通りデータベースではなくRedisを使っているので、関連するIdentityServer4.Contrib.RedisStoreも同時にバージョンアップしています。 <ItemGroup> - <PackageReference Include="IdentityServer4" Version="3.0.2" /> + <PackageReference Include="IdentityServer4" Version="4.1.2" /> - <PackageReference Include="IdentityServer4.Contrib.RedisStore" Version="3.1.2" /> + <PackageReference Include="IdentityServer4.Contrib.RedisStore" Version="4.0.0" /> ... 略 ... </ItemGroup> 2. 3.x->4.xでブレーキングチェンジとなった項目の修正 IdentityServerのこのIssueが参考になります。Migration from v3 to V4対応が必要だった項目を見ていきましょう。 ApiResourceから細分化されたApiScopeを追加する IdentityServer 4 v3.xでは、次のようにApiResourceの中にそのApiリソースで利用するスコープを直に記載していました。 Config.cs public class Config { public IEnumerable<ApiResource> ApiResources => new ApiResource[] { new ApiResource("api1", "api1 description") }; // ... 略 ... } startup.cs var builder = services.AddIdentityServer() .AddOperationalStore(options => { options.RedisConnectionString = "xxxxxx"; options.KeyPrefix = cfg.CachingStorePrefix; }) .AddInMemoryApiResources(Config.ApiResources) .AddInMemoryIdentityResources(Config.IdentityResources) .AddInMemoryClients(Config.IdentityClients) .AddProfileService<CustomProfileService>(); IdentityServer 4 v4.xでは、ApiResouce が参照する ApiScope を汎用的に扱えるように別々に定義するように仕様変更が行われています。今回のような単純なAPI定義であれば、ApiResouce と同じ内容の ApiScope を定義して Startup 時に登録してあげればよいです。 Config.cs public class Config { public static IEnumerable<ApiScope> ApiScopes => new ApiScope[] { new new ApiScope("api1", "api1") }; public IEnumerable<ApiResource> ApiResources => new ApiResource[] { new ApiResource("api1", "api1 description") }; // ... 略 ... } startup.cs public void ConfigureServices(IServiceCollection services) { // ... 略 ... var builder = services.AddIdentityServer() .AddOperationalStore(options => { options.RedisConnectionString = "xxxxxx"; options.KeyPrefix = cfg.CachingStorePrefix; })   .AddInMemoryApiScopes(Config.ApiScopes) .AddInMemoryApiResources(Config.ApiResources) .AddInMemoryIdentityResources(Config.IdentityResources) .AddInMemoryClients(Config.IdentityClients) .AddProfileService<CustomProfileService>() ; // ... 略 ... } 詳細については、Defining ResourcesドキュメントにV4の変更点とマイグレーションステップを参照してください。 このドキュメントの v3.x 版と v4.x 版を見比べてもどのような変更があったかを確認できると思います。 https://identityserver4.readthedocs.io/en/3.1.0/topics/resources.html#defining-api-resources https://identityserver4.readthedocs.io/en/latest/topics/resources.html#api-resources クレームにaudクレームが含まれない場合は、EmitStaticAudienceClaim を有効にする トークンのリクエスト方法によっては、audクレームが含まれない場合があります。 その場合はEmitStaticAudienceClaimプロパティーをtrueにしてあげると含まれるようになります。 startup.cs public void ConfigureServices(IServiceCollection services) { // ... 略 ... var builder = services.AddIdentityServer(options => { options.EmitStaticAudienceClaim = false; }) // ... 略 ... } 削除されたPublicOriginの代わりにミドルウェアを作成する v3.x では、リバースプロキシの後方に IdentityServer を配置した場合に、ホスト名を正しく解決するためにPublicOriginというプロパティーが用意されていました。v4.x ではこのプロパティーが削除されているため、startup 時に新たにミドルウェアを登録して解決してあげます。 https://github.com/IdentityServer/IdentityServer4/issues/4535 startup.cs public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // ... 略 ... app.Use(async (ctx, next) => { ctx.SetIdentityServerOrigin("https://foo.com"); await next(); }); // ... 略 ... } token エンドポイントのリクエスト時にコンテンツタイプ multipart/form-data が拒否される もともとOpenId Connectの仕様書では token エンドポイントへのリクエストには、application/x-www-form-urlencoded で行うように記載されていましたが、v4.x 以前まではコンテンツタイプが multipart/form-data であっても処理を受け付けていました。 v4.x では、このIssueの対応時にコンテンツタイプが application/x-www-form-urlencoded 出ない場合はエラーを返すように修正されています。 token エンドポイントリクエスト時にコンテンツタイプを multipart/form-data で送ってきているクライアントには、リクエスト時のコンテンツタイプを application/x-www-form-urlencoded に変更してもらう必要があります。 QuickStart変更に伴う修正 IdentityServer をアプリに組み込む場合は、リファレンス実装となる QuickStart をベースに組み込みを行うことが多いです。 QuickStart も v4.x リリース時に大きく変更が入っているので、QuickStart をそのまま利用していたりする場合はこのあたりも合わせて確認する必要があるかもしれません。 ConsentResponse.Deniedがなくなったことによる修正 SignInAsyncがなくなったことによる修正 DefaultInboundClaimTypeMap.Clearの代替え おわりに とりあえず、コミットログをたよりに修正した内容を列挙してみました。 さて、次は Duende IdentityServer への移行だけれど、これもちょっと腰が重いなぁ。 このレベルのミドルウェアはMSが保守してほしい気がしないでもないです...
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Selenium ChromeDriverで60秒のタイムアウトが発生する

1.ChromeDriverのタイムアウト SeleniumでChromeを自動操作している時に、ファイルアップロード等で60秒以上かかる場合に、タイムアウトが発生します。 これを回避するには、new ChromeDriver()で第3引数(TimeSpan)にタイムアウト時間を指定します。 海外のサイトで--no-sandboxオプションをつけたらうまくいったという話も見かけましたが、私はうまくいきませんでした。 アップロードの場合には、併せてページロードタイムアウトも設定が必要です。 C#で確認しています。 2.セッション0で実行時にnew ChromeDriver()でタイムアウトまで待たされて例外が発生することがある Windowsサービス(セッション0で動作)からセッション0にC# exeを起動し、そのexeでnew ChromeDriver()を行うと、数回~数十回に一度、上記のタイムアウトまで待たされてから例外が発生します。 new ChromeDriver()の第3引数タイムアウトの指定の有無に関わらず発生します。 ChromeとChromeDriverのバージョン91と92で確認しています。 サービスをLocalSYSTEMで動作させた場合と、Administratorsに所属するユーザーで動作させた場合のどちらでも発生します。 LocalSYSTEMで動作しているサービスからCreateProcessしてexeをLocalSYSTEMで実行させた場合と、CreateProcessAsUserでAdministratorsに所属するユーザーで動作させた場合のどちらでも発生します。 HttpWebRequestのGetResponse()で例外と出力されるので、ChromeDriverとchromedriver.exeが通信できていないのではないかと推測。 サービスを経由しないでexeを実行した場合(セッション1)や、サービスからコンソールユーザーのセッション(セッション1)に起動した場合には100回実行しても発生しません。 画面表示なしで実行させるためにセッション0で実行させたいため、new ChromeDriver()が済んでいるexeをプールしてワーカープロセスとして利用するようにして回避しました。そのまま半常駐として動かすとメモリリークが怖いので、一定時間でリサイクルさせています。 画面表示なしにするためには、セッション1でChromeのheadlessモードを使用する手もありますが、ファイルのダウンロードが失敗するので使用していません。回避方法もあるようですが、未確認です。ログオンしていない状態で動かしたいのでheadlessモードは使用しません。 ただ、この場合なのかchromedriver.exeとchrome.exe複数が残る場合があり、対処を模索中です。 3.セッション0で実行時に?GoToUrl()が例外を返し続けることがある 4秒くらいかかって、例外が発生します。一度発生したらそのプロセスでは発生し続けるので、終了させて別プロセスを起動しています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む