WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

SimpleInjector を ASP.NET MVC & Web API で利用

by WebSurfer 19. October 2019 15:40

以下の画像の通り、Visual Studio Community 2015 の .NET 4.6.1 テンプレートでプロジェクトを作って、ASP.NET MVC と Web API の両方が実装された Web アプリケーションに SimpleInjector を実装してみました。

プロジェクト作成

その話を備忘録として書いておきます。元の話は Teratail のスレッド「SimpleInjectorを使って、ASP.net MVC | WebAPI の両方のコントローラーを持つWebアプリケーションを動作させたい。」です。

上にも書きましたが、プロジェクトは MVC と Web API の両方を実装しており、それらの両方の Controller に Dependency Injection (DI) を行う必要があります。さらに、ユーザー認証は「個別のユーザーアカウント」すなわち ASP.NET Identity を利用しており、自動生成される AccountController.cs の Controller にも DI を行うように設定する必要があります。

以下に、Web API, MVC, AccountControlle の順に実装方法を書きます。

(1) Web API の Controller

ネットの記事「【連載】ASP.NET Web API を使おう:第4回 Simple Injector(DIコンテナ)を適用する」を参考に Web API 用の SimpleInjector の NuGet パッケージ「SimpleInjector.Integration.WebApi.WebHost.QuickStart」を追加します。

SimpleInjector の DI コンテナは Controller のコンストラクタが 1 つだけでないと Controller を初期化できないようで、NuGet の適用で自動生成される SimpleInjectorWebApiInitializer クラスの container.Verify() メソッドでそれをチェックしています。

実際、上のテンプレートで自動生成される AccountController には 2 つコンストラクタが定義されているので、そのままでは container.Verify() でエラーになります。

とりあえずは認証は使わくても Web API の Controller への DI の実装はできますので、AccountController の引数のある方のコンストラクタをコメントアウトすると container.Verify() は通りますのでそうしておきます。

上に紹介したネットの記事のように実装すると、Web API の Controller の初期化の際、DI コンテナが EmployeeRepository クラスを初期化して、Controller のコンストラクタに設定した IEmployeeRepository 型の引数を通じて渡します。すなわち、Inject する Dependency というのは EmployeeRepository クラスのインスタンスということになります。(DI の Dependency を「依存性」と訳すのは誤訳かも)

以上の設定で、ブラウザから jQuery ajax などで API を呼び出せば、期待した応答が返ってきます。

ここまでは、Web API に SimpleInjector を追加しただけです。MVC に追加するにはどうするかというのを次に書きます。

(1) MVC の Controller

MVC の場合も、上記の Web API と同様、Controller の初期化の際、DI コンテナが EmployeeRepository クラスを初期化して、Controller のコンストラクタに設定した IEmployeeRepository 型の引数を通じて渡せるようにしてみます。

まず、SimpleInjector の設定ですが、それについては stackoverflow の記事「Simple Injector initialize for both MVC and Web API controllers」に以下の記述がありました。

"we should use both .RegisterMvcControllers() and RegisterWebApiControlers() as well as both System.Web.Mvc.DependencyResolver(new SimpleInjectorDependencyResolver(container)) and GlobalConfiguration.Configuration.DependencyResolver(new SimpleInjectorWebApiDependencyResolver(container))"

具体的にどのようにしたかと言うと:

  1. まず、NuGet パッケージの SimpleInjector.Integration.Web.Mvc を追加インストール。
  2. 上の stacloverflow の記事を参考に、Web API 用に自動生成済みの SimpleInjectorWebApiInitializer に MVC 用の container.RegisterMvcControllers(...) と DependencyResolver.SetResolver(...) を追加。
  3. container.Options の DefaultScopedLifestyle の設定を AsyncScopedLifestyle() から WebRequestLifestyle() に変更。

    (SimpleInjector サイトの記事「Async Scoped lifestyle vs. Web Request lifestyle」に書いてある通り、MVC と Web API 混合プロジェクトでは WebRequestLifestyle を使うのが正解らしい)
  4. MVC に Controller を追加し、Web API の Controller と同様に、IEmployeeRepository 型の引数を持つコンストラクタを一つだけ実装。

以上で MVC でも Web API でも SimpleInjector は動くようになりました。次は AccountController です。

(1) AccountController.cs の Controller

これの対応が問題で、一筋縄ではいきませんでした。(笑)

まず、MVC 5 template (ASP.NET Identity 2.0) combined with Simple InjectorSimple Injector with ASP.NET MVC 5 Identity 2.0 を参考に手を加えてみました。

後者の記事によると前者の記事のコードは "it’s based on an older version of Simple Injector, so some syntax changes were required." とのこと故、SimpleInjectorInitializer クラスは主に後者の記事のコードを参考にして実装しました。

しかし、上記の対応だけでは動かなくて、AccountController のコンストラクタにある ISecureDataFormat<AuthenticationTicket> 型の引数が登録されてないというエラーが出ます。

それについては、stackoverflow の記事 Simple Injector and default AccountContoller dependency issue の回答を参考に 4 行追加して対処しました。

それで 100% OK かは分かりませんが、登録・ログイン・ログアウトは問題なくできることだけは確認しました。それ以外の External Login などがどうかは未検証・未確認です。

登録・ログイン画面

上の画像の確認用の画面は、Microsoft のドキュメント「個々のアカウントと ASP.NET Web API 2.2 でのローカル ログインを使用して Web API をセキュリティで保護します」のサンプルコードから借用したものです。AcountController の API に対して jQuery ajax を利用して要求を送信し、登録・ログイン・ログアウトの操作を行います。

最後に、上記の対応済みの SimpleInjectorWebApiInitializer のコードを下に貼っておきます。

手を加える必要があるのは SimpleInjectorWebApiInitializer のコードだけではなく、MVC 5 template (ASP.NET Identity 2.0) combined with Simple Injector に書いてある通り他に何か所もありますので注意してください。

// Startup.cs で初期化するので以下の属性はコメントアウト
//[assembly: WebActivator.PostApplicationStartMethod(
//  typeof(WebApi2.App_Start.SimpleInjectorWebApiInitializer), 
//  "Initialize")]

namespace WebApi2.App_Start
{
  using System.Web.Http;
  using SimpleInjector;
  using SimpleInjector.Integration.WebApi;
  using SimpleInjector.Lifestyles;
  using WebApi2.Models;

  // MVC 用に追加
  using System.Web.Mvc;
  using SimpleInjector.Integration.Web;
  using SimpleInjector.Integration.Web.Mvc;
  using System.Reflection;

  // AccountController に Inject できるように追加
  using Microsoft.AspNet.Identity;
  using Microsoft.AspNet.Identity.EntityFramework;
  using Microsoft.AspNet.Identity.Owin;
  using Microsoft.Owin.Security.DataProtection;
  using Owin;
  using Microsoft.Owin;
  using System.Collections.Generic;
  using System.Web;
  using Microsoft.Owin.Security;
  using Microsoft.Owin.Security.DataHandler;
  using Microsoft.Owin.Security.DataHandler.Serializer;
  using Microsoft.Owin.Security.DataHandler.Encoder;

  public static class SimpleInjectorWebApiInitializer
  {
    // 自動生成された public static void Initialize() { ... }
    // は以下に差し替え。
    public static Container Initialize(IAppBuilder app)
    {
      var container = GetInitializeContainer(app);

      container.Verify();

      // Web API 用
      GlobalConfiguration.Configuration.DependencyResolver = 
        new SimpleInjectorWebApiDependencyResolver(container);

      // MVC 用
      DependencyResolver.SetResolver(
          new SimpleInjectorDependencyResolver(container));

      return container;
    }

    public static Container GetInitializeContainer(
                                             IAppBuilder app)
    {
      var container = new Container();

      container.Options.DefaultScopedLifestyle = 
          new WebRequestLifestyle();

      container.RegisterInstance(app);

      container.Register<ApplicationUserManager>(
                                            Lifestyle.Scoped);

      // 接続文字列名は "DefaultConnection" で固定したので不要
      container.Register(
          () => new ApplicationDbContext(), Lifestyle.Scoped);

      container.Register<IUserStore<ApplicationUser>>(
          () => new UserStore<ApplicationUser>(
              container.GetInstance<ApplicationDbContext>()), 
          Lifestyle.Scoped);

      container.RegisterInitializer<ApplicationUserManager>(
          manager => InitializeUserManager(manager, app));

      // SignInManager は使ってないのでコメントアウト
      //container.Register<SignInManager<ApplicationUser,string>, 
      //    ApplicationSignInManager>(Lifestyle.Scoped);

      container.Register(
          () => container.IsVerifying ? 
              new OwinContext(new Dictionary<string, object>()).
                        Authentication
              : 
              HttpContext.Current.GetOwinContext().
                  Authentication, 
          Lifestyle.Scoped);

      InitializeContainer(container);

      // Web API 用
      container.RegisterWebApiControllers(
                GlobalConfiguration.Configuration);

      // MVC 用
      container.RegisterMvcControllers(
                Assembly.GetExecutingAssembly());

      return container;
    }

    private static void InitializeUserManager(
            ApplicationUserManager manager, IAppBuilder app)
    {
      manager.UserValidator = 
          new UserValidator<ApplicationUser>(manager)
      {
        AllowOnlyAlphanumericUserNames = false,
        RequireUniqueEmail = true
      };

      // Configure validation logic for passwords
      manager.PasswordValidator = new PasswordValidator
      {
        RequiredLength = 6,
        RequireNonLetterOrDigit = true,
        RequireDigit = true,
        RequireLowercase = true,
        RequireUppercase = true,
      };

      var dataProtectionProvider = 
                app.GetDataProtectionProvider();

      if (dataProtectionProvider != null)
      {
        manager.UserTokenProvider = 
            new DataProtectorTokenProvider<ApplicationUser>(
                dataProtectionProvider.Create(
                    "ASP.NET Identity"));
      }
    }

    private static void InitializeContainer(Container container)
    {
      // AccountController のコンストラクタの引数の
      // ISecureDataFormat<AuthenticationTicket> 型の登録

      container.Register<ISecureDataFormat<AuthenticationTicket>, 
        SecureDataFormat<AuthenticationTicket>>(Lifestyle.Scoped);

      container.Register<IDataSerializer<AuthenticationTicket>,
          TicketSerializer>(Lifestyle.Scoped);

      container.Register<ITextEncoder, Base64UrlTextEncoder>(
                                               Lifestyle.Scoped);

      container.Register<IDataProtector>(() =>
          new DpapiDataProtectionProvider().
              Create("ASP.NET Identity"), Lifestyle.Scoped);

      // 以下は自分でコーディングした MVC と Web API の
      // Controller に注入するためのもの
      container.Register<IEmployeeRepository, EmployeeRepository>
          (Lifestyle.Scoped);            
    }
  }
}

Tags: , , ,

Web API

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  November 2019  >>
MoTuWeThFrSaSu
28293031123
45678910
11121314151617
18192021222324
2526272829301
2345678

View posts in large calendar