WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

.NET Framework での Dependency Injection

by WebSurfer 2023年4月13日 15:12

ASP.NET Core で Dependency Injection (DI) に使われている Microsoft.Extensions.DependencyInjection 名前空間にあるクラス類は .NET Framework でもバージョン 4.6.1 以降であれば利用できるそうですので、.NET Framework 4.8 のコンソールアプリで試してみました。

先の記事「.NET Core での Dependency Injection」でターゲットフレームワーク .NET 5.0 のコンソールアプリに DI 機能を実装してみましたが、その .NET Framework 4.8 版です。

まず、Visual Studio 2022 のテンプレートを使ってターゲットフレームワーク .NET Framework 4.8 でコンソースアプリを作成し、NuGet から Microsoft.Extensions.DependencyInjection をインストールします。この記事を書いた時点での最新版 7.0.0 をインストールしました。

NuGet でインストール

他の Micosoft.Bcl.AsyncInterfaces などのパッケージは Microsoft.Extensions.DependencyInjection をインストールした時に同時に自動的にインストールされたものです。

検証に使ったコードは以下の通りです。先の記事「.NET Core での Dependency Injection」のものと同じです。

using Microsoft.Extensions.DependencyInjection;
using System;

namespace ConsoleAppDependencyInjection
{
    internal class Program
    {
        static void Main(string[] args)
        {
            IServiceCollection services = new ServiceCollection();

            services.AddTransient<IOrderRepository, SqlOrderRepository>();
            services.AddSingleton<ILogger, Logger>();
            services.AddScoped<IEventPublisher, EventPublisher>();
            services.AddTransient<CancelOrderHandler>();

            var provider = services.BuildServiceProvider();
            var handler = provider.GetRequiredService<CancelOrderHandler>();

            var orderId = Guid.NewGuid();
            var command = new Order { OrderId = orderId };
            handler.Handle(command);
        }
    }

    public class CancelOrderHandler
    {
        private readonly IOrderRepository repository;
        private readonly ILogger logger;
        private readonly IEventPublisher publisher;

        // Use constructor injection for the dependencies
        public CancelOrderHandler(IOrderRepository repository,
                                  ILogger logger,
                                  IEventPublisher publisher)
        {
            this.repository = repository;
            this.logger = logger;
            this.publisher = publisher;
        }

        public void Handle(Order command)
        {
            this.logger.Log($"Cancelling order {command.OrderId}");
            var order = this.repository.GetById(command.OrderId);
            order.OrderStatus = "Cancelled";
            this.repository.Save(order);
            this.publisher.Publish(order);
        }
    }

    public interface IOrderRepository
    {
        Order GetById(Guid orderId);

        void Save(Order order);
    }

    public class SqlOrderRepository : IOrderRepository
    {
        private readonly ILogger logger;

        // Use constructor injection for the dependencies
        public SqlOrderRepository(ILogger logger)
        {
            this.logger = logger;
        }

        public Order GetById(Guid orderId)
        {
            this.logger.Log($"Getting Order {orderId}");

            // Retrieve from db.・・・のつもり
            var order = new Order
            {
                OrderId = orderId,
                ProductName = "911-GT3",
                OrderStatus = "Ordered"
            };

            return order;
        }

        public void Save(Order order)
        {
            this.logger.Log($"Saving order {order.OrderId}");
            // Save to db.
        }
    }

    public interface ILogger
    {
        void Log(string log);
    }

    public class Logger : ILogger
    {
        public void Log(string log)
        {
            Console.WriteLine(log);
        }
    }

    public interface IEventPublisher
    {
        void Publish(Order order);
    }

    public class EventPublisher : IEventPublisher
    {
        public void Publish(Order order)
        {
            Console.WriteLine($"Publish order {order.OrderId}, " +
                $"{order.ProductName}, {order.OrderStatus}");
        }
    }

    public class Order
    {
        public Guid OrderId { get; set; }

        public string ProductName { get; set; }

        public string OrderStatus { get; set; }
    }
}

実行結果は以下の画像のようになります。先の記事の .NET 5.0 版と同様に期待した結果になっています。

実行結果

Tags: ,

.NET Framework

input type="file" を生成してアップロード

by WebSurfer 2023年4月2日 20:46

JavaScript を使って、ボタンクリックでファイル選択ダイアログを表示し、ユーザーがダイアログに表示されたファイルを選択したら即アップロードする方法を書きます。

ファイルアップロード

ファイルのアップロードでよくあるパターンは、<form> 要素の中に <input type="file"> 要素と <input type="submit"> 要素を静的に配置しておいて、(1) <input type="file"> のファイル選択ボタンをクリックしてファイル選択ダイアログを表示、(2) ユーザーがダイアログからファイルを選択、(3) <input type="submit"> をクリックして選択されたファイルを POST 送信する・・・というものだと思います。

その (1) から (3) のステップを短縮し、<input type="button"> ボタンをクリックしたらファイル選択ダイアログを表示し、ユーザーがファイルを選択したら File API を利用して File オブジェクトを取得し、それを fetch API を使って即 Web API にアップロードしてみます。

クライアント側のコードは以下の通りです。<input type="button"> ボタンだけを静的に配置しておき、それ以外は JavaScript で処置しています。説明はコード内のコメントに書きましたのでそれを見てください。

コードは ASP.NET Core MVC の View をものをそのままコピー&ペーストしましたが、その中の html と JavaScript のコードはどのような Web アプリにも使えるはずです。

@{
    ViewData["Title"] = "UploadFile";
}

<h1>UploadFile</h1>

<button type="button" class="btn btn-primary" id="btn">
    ファイル選択してアップロード
</button>

<div id="result"></div>

@section Scripts {
   <script type="text/javascript">
        window.addEventListener('DOMContentLoaded', () => {

            // Promise を返す非同期メソッド
            const showOpenFileDialog = async () => {
                return new Promise(resolve => {
                    // input type="file" 要素を動的に生成、
                    const input = document.createElement('input');
                    input.type = 'file';

                    // .jpg ファイルのみ選択できるよう設定してみた
                    input.accept = '.jpg';

                    // ユーザーがファイルを選択すると change イベント
                    // が発生するのでそれにリスナをアタッチし、File
                    // オブジェクトを取得。それを Promise の resolve
                    // コールバックに設定
                    input.addEventListener('change', 
                            e => resolve(e.target.files[0]));

                    // クリックしてファイル選択ダイアログを開く
                    input.click();
                });
            };

            // ボタンクリックで上のメソッドを呼び出し File オブジェ
            // クトを取得。それを fetch API を使って Web API に送信
            document.getElementById("btn")
                    .addEventListener('click', async () => {
                const file = await showOpenFileDialog();
                const formData = new FormData();
                formData.append("postedFile", file);
                const param = {
                    method: "POST",
                    body: formData
                }
                const response = await fetch("/api/Upload", param);
                const data = await response.text();
                document.getElementById("result").innerText = data;
            });
        });
   </script>
}

サーバー側で、アップロードされてきたファイルを受けて保存する ASP.NET Core Web API のコードも参考までに以下に載せておきます。

using Microsoft.AspNetCore.Mvc;

namespace MvcCore6App3.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class UploadController : ControllerBase
    {
        // 物理パスの取得用
        private readonly IWebHostEnvironment _hostingEnvironment;

        public UploadController(IWebHostEnvironment hostingEnvironment)
        {
            this._hostingEnvironment = hostingEnvironment;
        }

        // .NET 6.0 で作ったプロジェクトは「Null 許容」オプションが「有効化」
        // に設定してあるので、引数も null 許容にしておかないと、クライアント
        // でファイルを選択しないで送信した場合、HTTP 400 Bad Request エラー
        // になって "The postedFile field is required."というエラーメッセージ
        // が返ってくる
        [HttpPost]
        public async Task<string> Post([FromForm] IFormFile? postedFile)
        {
            string result = "";
            if (postedFile != null && postedFile.Length > 0)
            {
                // アップロードされたファイル名を取得
                string filename = System.IO.Path.GetFileName(postedFile.FileName);

                // アプリケーションルートの物理パスを取得
                string contentRootPath = _hostingEnvironment.ContentRootPath;

                string filePath = $"{contentRootPath}\\UploadedFiles\\" +
                    $"{filename}{DateTime.Now.ToString("yyyyMMddHHmmss")}.jpg";

                // フォルダ UploadedFile に画像ファイルを保存
                using (var stream = new FileStream(filePath, FileMode.Create))
                {
                    await postedFile.CopyToAsync(stream);
                }

                result = $"{filename} ({postedFile.ContentType}) - " +
                    $"{postedFile.Length} bytes アップロード完了";
            }
            else
            {
                result = "ファイルアップロードに失敗しました";
            }

            return result;
        }
    }
}

Tags: , , ,

Upload Download

dotnet dev-certs https コマンドについて

by WebSurfer 2023年3月30日 21:43

dotnet dev-certs https コマンドについて調べたことを備忘録として残しておきます。

dotnet dev-certs コマンド

dotnet dev-certs コマンドは、Microsoft のドキュメント dotnet dev-certs に書いてありますように、開発用のサーバー証明書の作成・管理を行うためのツールです。

具体的には、フレンドリ名が ASP.NET Core HTTPS development certificate という自己署名証明書を作成・管理するツールです。その証明書を使って、ASP.NET Core Web アプリの開発中に、Web サーバーの Kestrel がブラウザなどのクライアントと HTTPS 通信ができるようになります。

(注: IIS Express でホストする場合は IIS Express Development Certificate というフレンドリ名の証明書が使用されます。このスレッドの話は関係ないので注意してください。)

このコマンドの実行によって証明書マネージャーに表示される証明書がどのようになるか、証明書がないときアプリを Kestrel で動かそうとするとどうなるかも以下に合わせて書いておきます。

(1) 初期状態

Visual Studio を開発に使っていれば自力で dotnet dev-certs コマンドを打って証明書を作る必要はなく、Visual Studio がコマンドを使って必要な証明書を生成してくれます。下の証明書マネージャーの画像がその結果で、発行先が localhost となっているのが開発用サーバー証明書です。

開発用サーバー証明書

有効期限切れのものが含まれていますが (何年か Visual Studio で作業しているうちに追加されたものです)、作業していて支障はなかったのでそのままにしていました。

この中のフレンドリ名 ASP.NET Core HTTPS development certificate という証明書をすべて削除してから、新しく証明書を作成し、開発環境でそれを使って HTTPS 通信ができるようにします。

(2) dotnet dev-certs https --clean 実行

既存の証明書を削除するためコマンドラインから dotnet dev-certs https --clean を実行します。

コマンドを打つと下の画像の「ルート証明書ストア」というダイアログが出て削除してよいかが確認されるので[はい]をクリックします。(上の画像のように複数証明書がある場合は複数回ダイアログが出ます)

「ルート証明書ストア」ダイアログ

削除に成功するとコマンドラインに "HTTPS development certificates successfully removed from the machine." と表示され、証明書マネージャーの[個人]>[証明書]および[信頼されたルート証明書]>[証明書]にあったフレンドリ名 ASP.NET Core HTTPS development certificate の証明書はすべて削除されます。

(IIS Express 用の証明書 IIS Express Development Certificate にはこのコマンドは影響ありません)

この状態でコマンドラインから dotnet dev-certs https --check を実行すると "No valid certificate found." と表示されます。

(3) アプリの実行

証明書を削除した後、Visual Studio 2022 で Kestrel を使って ASP.NET Core Web アプリを実行すると「ASP.NET Core SSL 証明書を信頼する」というダイアログが出ます。

ASP.NET Core SSL 証明書を信頼する

[いいえ]を選択すると下の画像のダイアログが出て「Web サーバー 'MvcCore6App2' に接続できません。Web サーバーはもう実行されてません」というメッセージが表示されて終わってしまいます。

ダイアログ

コンソールを見ると以下の例外が出ていました。

Unhandled exception. System.InvalidOperationException: Unable to configure HTTPS endpoint. No server certificate was specified, and the default developer certificate could not be found or is out of date. To generate a developer certificate run 'dotnet dev-certs https'. To trust the certificate (Windows and macOS only) run 'dotnet dev-certs https --trust'.

(IIS Express 用の証明書 IIS Express Development Certificate には影響はありませんので、Visual Studio 2022 の設定を IIS Express を使うようにしてアプリを実行すれば上のような問題はありません)

(4) dotnet dev-certs https 実行

証明書を作成するためコマンドラインから dotnet dev-certs https を実行します。作成に成功すると "The HTTPS developer certificate was generated successfully." というメッセージが表示されます。

証明書マネージャーを見ると[個人]>[証明書]にフレンドリ名 ASP.NET Core HTTPS development certificate という証明書が追加されています。

証明書マネージャー

ただし、この時点では[信頼されたルート証明書]>[証明書]には ASP.NET Core HTTPS development certificate は存在しません。

この状態で上の (3) と同様にアプリを実行してみます。同じく「ASP.NET Core SSL 証明書を信頼する」というダイアログが出るので[いいえ]を選択すると、今度は InvalidOperationException 例外はスローされず、自己署名証明書を使った場合の警告がブラウザに表示されます。

プライバシーエラー

警告を無視して進めればブラウザにコンテンツの表示はできます。

(5) dotnet dev-certs https --check 実行

コマンドラインから dotnet dev-certs --check を実行しても証明書が発行されたかどうかを確認できます。発行されていると以下のようなメッセージが返ってきます。

A valid certificate was found: 8216607E62A7221B7EFEC3022C8AD599DC1728B1 - CN=localhost - Valid from 2023-03-28 13:07:39Z to 2024-03-27 13:07:39Z - IsHttpsDevelopmentCertificate: true - IsExportable: true
Run the command with both --check and --trust options to ensure that the certificate is not only valid but also trusted.

このコマンドは証明書の情報を確認するためだけのものです。証明書を作成することもなく、作成済みの証明書には何も影響しません。

(6) dotnet dev-certs https --trust 実行

上の (4) で発行した証明書を「信頼されたルート証明書」として登録するため、コマンドラインから dotnet dev-certs https --trust を実行します。

コマンドを入力すると、コマンドラインには "Trusting the HTTPS development certificate was requested. A confirmation prompt will be displayed if the certificate was not previously trusted. Click yes on the prompt to trust the certificate." というメッセージが表示され、下の画像のセキュリティ警告ダイアログが表示されます。

セキュリティ警告ダイアログ

[はい]をクリックして処理を継続します。成功するとコマンドラインには "Successfully trusted the existing HTTPS certificate." というメッセージが表示されます。

証明書マネージャーを見ると[信頼されたルート証明書]>[証明書]に ASP.NET Core HTTPS development certificate が追加されています。

証明書マネージャー

この後で Visual Studio からアプリを実行すれば証明書の問題はなくなり HTTPS 通信を使っての開発ができるようになります。

Tags: , , , ,

CORE

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年4月  >>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar