WebSurfer's Home

Filter by APML

ASP.NET Core のオプション パターン

by WebSurfer 28. September 2026 15:45

ASP.NET Core のオプション パターンについて調べて、いろいろ分かったことがありましたので、以下に備忘録として書いておくことにします。

検証用に作成した Config ページ

ASP.NET Core でのオプションパターンとは何かを簡単に書きますと、appsettings.json などの構成設定を、厳密な型が定義された POCO (Plain Old CLR Object) クラスにバインドし、型安全かつシンプルに扱うためのデザインパターンということです。詳しくは Microsoft のドキュメント「ASP.NET Core のオプション パターン」を見てください。

この記事では、OptionsConfigurationServiceCollectionExtensions.Configure メソッドのオーバーロード Configure<TOptions>(IServiceCollection, IConfiguration) を利用して、appsettings.json のあるセクションから情報を読んで、その情報を POCO に格納し、DI コンテナに登録して ASP.NET Core アプリで利用するケースについて説明します。

(1) appsettings.json

以下の内容とします。この中の SoapService セクションの内容を POCO に取り込みます。

{
  ・・・中略・・・

  "SoapService": {
    "SoapScheme": "https",
    "SoapServiceHost": "SoapDevService",
    "CustomerAccountManager": {
      "Endpoint": "{SoapScheme}://{SoapServiceHost}/CustomerAccountManager.asmx",
      "Username": "",
      "Password": "",
      "TimeoutSeconds": 30,
      "MaxReceivedMessageSize": 10485760
    }
  }
}

(2) POCO クラスの定義

POCO のクラス定義は以下の通りとします。パブリックプロパティの名前は appsettings.json の各項目の名前と一致させる必要があります。SoapService フィールドは Microsoft のドキュメントのサンプルコードにならって追加しました。アプリのコード(この記事では Program.cs)で "SoapService" 文字列をハードコーディングしないようにするために追加するとのことです。

namespace RazorPages.Models
{
    public class SoapServiceOptions
    {
        public const string SoapService = "SoapService";
        public string SoapScheme { get; set; } = "";
        public string SoapServiceHost { get; set; } = "";
        public CustomerAccountManagerConfig CustomerAccountManager { get; set; } = new();
    }

    public class CustomerAccountManagerConfig
    {
        public string Endpoint { get; set; } = "";
        public string Username { get; set; } = "";
        public string Password { get; set; } = "";
        public int TimeoutSeconds { get; set; } = 0;
        public int MaxReceivedMessageSize { get; set; } = 0;
    }
}

(3) Configure メソッドを追加

Program.cs に以下のように Configure メソッドを追加します。Configure メソッドは内部で次のことを行います: (1) IConfiguration のセクションを読み取る、(2) Bind() を使って TOptions に値をコピーする、(3) 結果 Options パターン(IOptions<TOptions>, IOptionsSnapshot<TOptions>, IOptionsMonitor<TOptions>)が DI コンテナに登録されます。

namespace RazorPages
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            // ・・・中略・・・

            // Configure メソッドを追加
            builder.Services.Configure<SoapServiceOptions>(
                builder.Configuration.GetSection(SoapServiceOptions.SoapService));

            // ・・・中略・・・
        }
    }
}

Options パターン IOptions<TOptions>, IOptionsSnapshot<TOptions>, IOptionsMonitor<TOptions> の違いを簡単に説明すると以下の通りです。

  1. IOptions<TOptions>(シングルトン相当)アプリ起動時に設定ファイルを読み込み、常に同じ設定値を保持し続けます。
  2. IOptionsSnapshot<TOptions>(スコープ相当)IOptionsSnapshot を使ってDIを受け取ると、HTTPリクエストごとに appsettings.json の変更を検知して最新の値に再生成してくれます。
  3. IOptionsMonitor<SoapService>(シングルトン相当+リアルタイム通知)シングルトンでありながら、設定ファイルが書き換わった瞬間に最新の値を反映・通知できる特殊なライフサイクルです。

(4) 一部内容を書き換え

PostConfigure を使って一部の内容を書き換えます。Program.cs で Configure メソッドの後に以下のようにコードを追加します。SoapServiceHost に DateTime.Now.ToString() を設定したのは書き変わるタイミングを知るためです。IOptionsSnapshot と IOptionsMonitor で SoapServiceHost の書き換えのタイミングがどのように異なるかが興味のあるところです。

namespace RazorPages
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            // ・・・中略・・・

            // Configure メソッドを追加
            builder.Services.Configure<SoapServiceOptions>(
                builder.Configuration.GetSection(SoapServiceOptions.SoapService));

            // 一部内容の書き換え
            builder.Services.PostConfigure<SoapServiceOptions>(soap =>
            {
                soap.SoapServiceHost = DateTime.Now.ToString();
                soap.CustomerAccountManager.Endpoint = "/new-endpoint";
            });

            // ・・・中略・・・
        }
    }
}

(5) ページ/コントローラーでの情報の取得

ページ/コントローラーでの情報の取得は、ASP.NET Core に備わっている DI 機能を使って、コンストラクタの引数に IOptions<TOptions>, IOptionsSnapshot<TOptions>, IOptionsMonitor<TOptions> を注入してやることで可能です。以下のコードは Razor Pages の例で、この記事の一番上に貼った画像に示すページを表示したものです。

Config.html.cs

using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Options;
using RazorPages.Models;

namespace RazorPages.Pages
{
    public class ConfigModel : PageModel
    {
        private readonly SoapServiceOptions _soap;
        private readonly SoapServiceOptions _soapSnapshot;
        private readonly SoapServiceOptions _soapMonitor;

        public ConfigModel(IOptions<SoapServiceOptions> soap, 
               IOptionsSnapshot<SoapServiceOptions> soapSnapshot, 
               IOptionsMonitor<SoapServiceOptions> soapMonitor)
        {
            _soap = soap.Value;
            _soapSnapshot = soapSnapshot.Value;
            _soapMonitor = soapMonitor.CurrentValue;
        }

        public string SoapScheme => _soap.SoapScheme;
        public string SoapServiceHost => 
            _soap.SoapServiceHost;
        public string SoapEndpoint => 
            _soap.CustomerAccountManager.Endpoint;

        public string SoapSchemeSnapshot => 
            _soapSnapshot.SoapScheme;
        public string SoapServiceHostSnapshot => 
            _soapSnapshot.SoapServiceHost;
        public string SoapEndpointSnapshot => 
            _soapSnapshot.CustomerAccountManager.Endpoint;

        public string SoapSchemeMonitor => 
            _soapMonitor.SoapScheme;
        public string SoapServiceHostMonitor => 
            _soapMonitor.SoapServiceHost;
        public string SoapEndpointMonitor => 
            _soapMonitor.CustomerAccountManager.Endpoint;

        public void OnGet()
        {
        }
    }
}

Config.html

@page
@model RazorPages.Pages.ConfigModel

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

<h3>IOptions<TOptions></h3>
<ul>
    <li>SoapScheme: @Model.SoapScheme</li>
    <li>SoapServiceHost: @Model.SoapServiceHost</li>
    <li>SoapEndpoint: @Model.SoapEndpoint</li>
</ul>

<h3>IOptionsSnapshot<TOptions></h3>
<ul>
    <li>SoapScheme: @Model.SoapSchemeSnapshot</li>
    <li>SoapServiceHost: @Model.SoapServiceHostSnapshot</li>
    <li>SoapEndpoint: @Model.SoapEndpointSnapshot</li>
</ul>

<h3>IOptionsMonitor<TOptions></h3>
<ul>
    <li>SoapScheme: @Model.SoapSchemeMonitor</li>
    <li>SoapServiceHost: @Model.SoapServiceHostMonitor</li>
    <li>SoapEndpoint: @Model.SoapEndpointMonitor</li>
</ul>

(6) 検証結果の説明

この記事の一番上の画像は検証用に作成した ASP.NET Core Razor Pages アプリの Config ページを表示したものです。赤および青の下線を引いた部分の違いがポイントです。

IOptions<TOptions> はシングルトンですので、アプリを再起動しない限り常に同じ値を保持し続けます。appsettings.json ファイルの SoapScheme を https ⇒ http に書き換えても、Program.cs の PostConfigure メソッドで時刻を設定し直しても、赤および青の下線を引いた部分は変わりません。

IOptionsSnapshot<TOptions> スコープ相当ですので、HTTP 要求ご��に appsettings.json の変更および PostConfigure メソッドでの書き換え検知して最新の値に再生成してくれます。上の画像は appsettings.json ファイルの SoapScheme を https ⇒ http に書き直して保存した後、再度 HTTP リクエストをかけてその応答を示したものです。この後も SoapServiceHost は要求・応答の度にその時刻に変わります。

IOptionsMonitor<TOptions> は基本シングルトンですが、appsettings.json ファイルが書き換わった瞬間に最新の値を反映してくれます。上の画像は appsettings.json ファイルの SoapScheme を https ⇒ http に書き直して保存した後、再度 HTTP リクエストをかけてその応答を示したものです。SoapServiceHost は appsettings.json ファイルが書き換わった時刻になります。その後はシングルトンとして動きますので、appsettings.json ファイルを書き換えない限り何度 HTTP リクエストをかけても値は変わりません。


上記のような Page や Controller で値を受け取るという使い方なら IOption または IOptionSnapshot を使えばよく、IOptionsMonitor の出番は無さそうです。IOptionsMonitor はこのような使い方ではなくて、IOptionsMonitor<TOptions>.OnChange メソッドでリスナーを登録し、設定変更を即時検知してそのリスナーでサーバー側で何か動かす(キャッシュ更新、長寿命サービスの設定差し替え、接続中クライアントへの通知など)のに使うのが適切なようです。

IOptionsMonitor<TOptions>.OnChange メソッドを使った例は別の記事に書く予定です。

Tags: , , , ,

CORE

MVC5 で IHttpClientFactory を DI で利用

by WebSurfer 27. January 2025 14:26

下の図の構成で、ASP.NET MVC5 において Dependency Injection (DI) により IHttpClientFactory を取得し、それから HttpClient を生成して Web API からデータを取得し、そのデータをブラウザに表示する方法を書きます。

MVC5 で IHttpClientFactory を利用

ASP.NET Core MVC の例は先の記事「ASP.NET と HttpClient (CORE)」に書きました。

ASP.NET Core の場合は、Visual Studio のテンプレートを使ってプロジェクトを作成すればデフォルトで DI 機能が実装されるので、Program.cs で AddHttpClient メソッドを使って IHttpClientFactory を IServiceCollection (DI コンテナ) に登録するだけで、Controller はコンストラクタの引数経由で IHttpClientFactory を取得できます。

一方、ターゲットフレームワークが .NET Framework 場合、Visual Studio のテンプレートで作成した MVC5 アプリには DI 機能は実装されません。なので、最初の課題はそれにどのように DI 機能を追加するかになります。

ちなみに DI を利用して IHttpClientFactory を取得するのは必須です。何故かと言うと、Microsoft のドキュメント「IHttpClientFactory の代替手段」に書いてありますように、以下のことを回避できるというメリットがあるからです。

  • HttpMessageHandler インスタンスをプールすることによるリソース枯渇の問題
  • 一定の間隔で HttpMessageHandler インスタンスを循環させることによって発生する古くなった DNS の問題

ターゲットフレームワークが .NET Framework のアプリでも、バージョンが 4.6.2 以降であれば ASP.NET Core で DI に使われている Microsoft.Extensions.DependencyInjection 名前空間にあるクラス類を利用して DI 機能を実装できます。

.NET Framework 4.8 の MVC5 アプリに DI 機能を実装する詳しい方法は、先の記事「MVC5 での Dependency Injection」に書きました。この記事では、その記事のアプリの IServiceCollection (DI コンテナ) に IHttpClientFactory を登録し、それを Controller でどのように使って Web API に要求を出し、データを取得するかを書きます。

(1) Microsoft.Extensions.Http

NuGet パッケージ Microsoft.Extensions.Http をインストールします。これは AddHttpClient メソッドを使用して IHttpClientFactory を IServiceCollection(DI コンテナ)に登録できるようにするため必要です。

Microsoft.Extensions.Http

(2) AddHttpClient メソッドの追加

先の記事「MVC5 での Dependency Injection」に書いた Global.asax.cs のコード内の ConfigureServices メソッドに、以下のように AddHttpClient メソッドを追加します。この一行で Controller はコンストラクタの引数経由で IHttpClientFactory を受け取れるようになります。

// DI コンテナにサービスを登録するメソッド
private void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<ScopedThing>();
    services.AddTransient<HomeController>();

    // IHttpClientFactory を DI して利用できるよう追加
    services.AddHttpClient();
}

(3) Controller

Controller の例です。ブラウザが Hero アクションメソッドを呼び出すと、DI 機能により IHttpClientFactory が Controller のコンストラクタの引数経由で渡されます。

IHttpClientFactory から生成した HttpClient を使って Web API に要求を出して JSON 形式のデータを取得し、それを .NET の List<Hero> 型のオブジェクトにデシリアライズして View に渡しています。

using Mvc5DependencyInjection.Models;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Mvc;
using System.Text.Json;

namespace Mvc5DependencyInjection.Controllers
{
    public class HomeController : Controller
    {
        private readonly ScopedThing _scopedThing;

        // IHttpClientFactory を DI して利用できるよう追加
        private readonly IHttpClientFactory _clientFactory;

        // コンストラクタの引数経由 IHttpClientFactory が DI される
        public HomeController(ScopedThing scopedThing, 
                              IHttpClientFactory clientFactory)
        {
            this._scopedThing = scopedThing;
            this._clientFactory = clientFactory;
        }

        // ・・・中略・・・

        public async Task<ActionResult> Hero()
        {
            // IHttpClientFactory から HttpClient を取得
            HttpClient client = _clientFactory.CreateClient();

            var url = "Web API の url";
            var request = new HttpRequestMessage(HttpMethod.Get, url);
            HttpResponseMessage response = await client.SendAsync(request);
            List<Hero> list = null;

            if (response.IsSuccessStatusCode)
            {
                using (Stream responseStream =
                              await response.Content.ReadAsStreamAsync())
                {
                    list = await JsonSerializer.
                           DeserializeAsync<List<Hero>>(responseStream);
                }
            }

            return View(list);
        }
    }
}

(4) Model

namespace Mvc5DependencyInjection.Models
{
    public class Hero
    {
        public int id { get; set; }
        public string name { get; set; }
    }
}

(5) View

@model IEnumerable<Mvc5DependencyInjection.Models.Hero>

@{
    ViewBag.Title = "Hero";
}

<h2>Hero</h2>

<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.id)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.name)
        </th>
    </tr>

    @foreach (var item in Model)
    {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.id)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.name)
            </td>
        </tr>
    }

</table>

(6) 実行結果

上のコードを実行するとブラウザには以下のように表示されます。

実行結果

Tags: , , ,

MVC

MVC5 での Dependency Injection

by WebSurfer 15. April 2023 16:11

.NET Framework の ASP.NET MVC5 アプリで Microsoft.Extensions.DependencyInjection 名前空間にあるクラスを利用して Dependency Injection (DI) 機能を実装してみましたので、忘れないように備忘録として残しておきます。

MVC5 で Dependency Injection

ターゲットフレームワークが .NET Framework 4.6.1 以降であれば ASP.NET Core で DI に使われている Microsoft.Extensions.DependencyInjection 名前空間にあるクラス類を利用できるそうなので試してみた次第です。

参考にしたのはググって探して GitHub に見つかった ASP.NET MVC and ServiceCollection sample という記事です。その記事のコードをほぼそのまま Global.asax.cs にコピーすれば必要最低限の DI 機能が実装できます。

ベースとした ASP.NET MVC5 アプリのプロジェクトは Visual Studio 2022 のテンプレートを使ってターゲットフレームワークを .NET Framework 4.8 として作成したものです。

プロジェクトの作成

そのプロジェクトに NuGet から Microsoft.Extensions.DependencyInjection をインストールします。この記事を書いた時点での最新版 7.0.0 をインストールしました。(他に関係パッケージが 4 つ同時に自動的にインストールされます)

NuGet パッケージ

自動生成された Global.asax.cs を開き以下のように書き換えます。参考にした記事 ASP.NET MVC and ServiceCollection sample のコードほぼそのままです。自分なりに調べていろいろ分かったことをコメントとして追記してあります。

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using Microsoft.Extensions.DependencyInjection;
using Mvc5DependencyInjection.Controllers;
using Mvc5DependencyInjection.Repositories;

// 下の設定により Application_Start より前に InitModule メソッド
// が実行され HTTP モジュール ServiceScopeModule が登録される
[assembly: PreApplicationStartMethod(
    typeof(Mvc5DependencyInjection.MvcApplication), "InitModule")]

namespace Mvc5DependencyInjection
{
    public class MvcApplication : System.Web.HttpApplication
    {
        public static void InitModule()
        {
            // HTTP モジュール ServiceScopeModule を登録
            RegisterModule(typeof(ServiceScopeModule));
        }

        protected void Application_Start()
        {
            // DI コンテナの作成
            var services = new ServiceCollection();

            // DI コンテナにサービスを登録する。
            // ConfigureServices メソッドは下のコード参照
            ConfigureServices(services);

            // サービスプロバイダの作成
            ServiceProvider provider = services.BuildServiceProvider();

            // HTTP モジュール ServiceScopeModule に上の provider を渡し、
            // モジュール内でサービスプロバイダを利用できるようにする
            ServiceScopeModule.SetServiceProvider(provider);

            // 下の 4 行は Global.asax.cs にもともと含まれていたもの
            AreaRegistration.RegisterAllAreas();
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);

            // SetResolver メソッドは Microsoft のドキュメントによると
            // "Provides a registration point for dependency resolvers,
            // using the specified dependency resolver interface." との
            // こと。要するに、これにより MVC5 アプリで DI が実現できる
            // ようになるらしい
            var resolver = new ServiceProviderDependencyResolver();
            DependencyResolver.SetResolver(resolver);
        }

        // DI コンテナにサービスを登録するメソッド。
        // Controller については、ASP.NET Core では AddController,
        // AddMvc, AddControllersWithViews, AddRazorPages などを使うと
        // アセンブリをスキャンして全てを DI コンテナに登録するという
        // ことが行われるようだが、それはこのサンプルでは未実装。
        // すべて以下のメソッド内で登録する。
        private void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<ScopedThing>();
            services.AddTransient<HomeController>();
        }
    }

    // このサンプルではこのオブジェクトを HomeController に Inject
    public class ScopedThing : IDisposable
    {
        public ScopedThing()
        {
        }

        // Home/Index にてメッセージを取得できるように追加
        public string GetMessage()
        {
            return "Message form ScopedThing";
        }


        public void Dispose()
        {
        }
    }

    // HTTP モジュール
    // BeginRequest で IServiceScope を取得して HttpContext.Items
    // に保持する。EndRequest で IServiceScope を Dispose すること
    // により登録されたサービス (AddSingleton で登録されたものは除
    // く) は Dispose される
    internal class ServiceScopeModule : IHttpModule
    {
        private static ServiceProvider _serviceProvider;

        // インターフェイスに定義されているので実装が必要
        public void Dispose() { }

        public void Init(HttpApplication context)
        {
            context.BeginRequest += Context_BeginRequest;
            context.EndRequest += Context_EndRequest;
        }

        // IServiceScope オブジェクトを HttpContext.Items に保持
        private void Context_BeginRequest(object sender, EventArgs e)
        {
            var context = ((HttpApplication)sender).Context;
            context.Items[typeof(IServiceScope)] = 
                _serviceProvider.CreateScope();
        }

        // IServiceScope オブジェクトを Dispose する
        private void Context_EndRequest(object sender, EventArgs e)
        {
            var context = ((HttpApplication)sender).Context;
            if (context.Items[typeof(IServiceScope)] 
                is IServiceScope scope)
            {
                scope.Dispose();
            }
        }

        // Application_Start でサービスプロバイダが渡される
        public static void SetServiceProvider(
            ServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
        }
    }

    // BeginRequest で IServiceScope が HttpContext.Items に保持され
    // ているので、それを使って DI に登録されたサービスを取得できる
    internal class ServiceProviderDependencyResolver 
                   : IDependencyResolver
    {
        public object GetService(Type serviceType)
        {
            if (HttpContext.Current?.Items[typeof(IServiceScope)] 
                is IServiceScope scope)
            {
                return scope.ServiceProvider.GetService(serviceType);
            }

            throw new InvalidOperationException(
                                    "IServiceScope not provided");
        }

        public IEnumerable<object> GetServices(Type serviceType)
        {
            if (HttpContext.Current?.Items[typeof(IServiceScope)] 
                is IServiceScope scope)
            {
                return scope.ServiceProvider.GetServices(serviceType);
            }

            throw new InvalidOperationException(
                                      "IServiceScope not provided");
        }
    }
}

上の ConfigureServices メソッドに Controller とそれが依存するクラスを登録すれば DI 機能は働くようになります。

この記事では HomeController と ScopedThing を登録しています。自動生成された HomeController のコードに手を加えて、以下のようにコンストラクタ経由で ScopedThing を受け取れるようにすれば、クライアントからの要求を受けて HomeController が呼び出されたときに DI 機能により ScopedThing のインスタンスへの参照がコンストラクタの引数に渡されます。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace Mvc5DependencyInjection.Controllers
{
    public class HomeController : Controller
    {
        private readonly ScopedThing _scopedThing;

        public HomeController(ScopedThing scopedThing)
        {
            this._scopedThing = scopedThing;
        }

        public ActionResult Index()
        {
            string msg = _scopedThing.GetMessage();
            ViewBag.Msg = msg;
            return View();
        }        
    }
}

Index アクションメソッドに書いたように、Inject された ScopedThing インスタンスの GetMessage メソッドで "Message form ScopedThing" という文字列を取得できます。

処理が終わると HttpApplication.EndRequest イベントが発生するので、HTTP モジュール ServiceScopeModule により ScopedThing の Dispose メソッドが自動的に呼ばれます。


先の記事「ASP.NET MVC5 で Autofac.Mvc5 使って DI」と同様に、下の画像のようなリポジトリパターンを実装し、「Entity データモデル」を「本番用クラス」に、「本番用クラス」を「コントローラークラス」に Inject することもできます。

リポジトリパターン

具体例を書くと、まず、先の記事と同様に (1) Visual Studio の ADO.NET Entity Data Model ウィザードで Entity Data Model (EDM) を作成し、(2) それをベースにスキャフォールディング機能を使って CRUD 用の Controller を自動生成させ、(3) Controller から SQL Server にアクセスして操作するコードをリポジトリクラスに切り出し、(4) Controller にリポジトリクラスを Inject できるようにコンストラクタを追加します。その手順の詳しい説明とコード例は先の記事にありますので見てください。

その後、作成した EDM のコンテキストクラス、リポジトリクラス、コントローラークラス を上の Global.asax.cs の ConfigureServices メソッドで DI コンテナに登録します。以下のコードで「// 追加」とコメントした下の 3 行がそれです。それだけで DI 機能が働くようになります。

private void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<ScopedThing>();
    services.AddTransient<HomeController>();

    // 追加
    services.AddScoped<NORTHWINDEntities>();
    services.AddScoped<IProductRepository, ProductRepository>();
    services.AddTransient<ProductsController>();
}

上の画像の「コントローラークラス」が ProductsController に、「インターフェイス」が IProductRepository に、「本番用クラス」が ProductRepository に、「Entity データモデル」が NORTHWINDEntities に該当します。

ブラウザから Prodtcuts/Index を要求すると、ProductsController が依存する ProductRepository、ProductRepository が依存する NORTHWINDEntities は DI 機能により自動的に Inject され、SQL Server からデータを取得して結果が表示されます。それがこの記事の一番上の画像です。

Tags: , ,

MVC

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。ブログ2はそれ以外の日々の出来事などのトピックスになっています。

Calendar

<<  October 2026  >>
MoTuWeThFrSaSu
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

View posts in large calendar