WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

ASP.NET アプリの Settings.settings

by WebSurfer 13. July 2020 16:44

ASP.NET Web アプリでも Web アプリケーションプロジェクトであれば Settings.settings を利用できます。ASP.NET Web アプリでこれを使うことはあまりなさそうですし、実際、自分は使ったことがないのですが、こういうこともできるということで備忘録に残しておきます。

Settings.settings の設定

上の画像は ASP.NET MVC5 アプリのプロジェクトの例です。プロジェクトルート直下にある Properties フォルダを右クリックして開き、[設定]タブを選択すると「このプロジェクトには既定の設定ファイルが含まれていません。ファイルを作成するにはここをクリックしてください。」と表示されるので、それをクリックして情報を設定できます。

Windows Forms アプリなどと同様に「種類」には文字列の他に bool, int, double, 接続文字列などを選択でき、Settings.Designer.cs に定義されている強く型付けされたプロパティを使って値を取得できます。その点では web.config の appSettings を使うよりメリットがあるかもしれません。

ただし「スコープ」はアプリケーションのみで、ユーザーは選択できません。複数のユーザーがアクセスしてくる ASP.NET Web アプリなので、当然と言えば当然なのですが。

情報の保存場所はどこになるかと言えば、.NET Framework 版の ASP.NET Web アプリでは当たり前かもしれませんが、アプリケーションルート直下の web.config になります。

web.config 内のどこにどのような形で設定情報が保存されるかと言うと、上の画像の設定例では以下のようになります。

<configuration>
  <configSections>
    <!-- 中略 -->
    <sectionGroup name="applicationSettings" 
      type="System.Configuration.ApplicationSettingsGroup, 
            System, Version=4.0.0.0, Culture=neutral, 
            PublicKeyToken=b77a5c561934e089">
      <section name="Mvc5App.Properties.Settings" 
        type="System.Configuration.ClientSettingsSection, 
              System, Version=4.0.0.0, Culture=neutral, 
              PublicKeyToken=b77a5c561934e089" 
              requirePermission="false"/>
    </sectionGroup>
  </configSections>

  <connectionStrings>
    <!-- 中略 -->
    <add name="Mvc5App.Properties.Settings.connString" 
         connectionString="Data Source=lpc:(local)\SQLEXPRESS;
                           Initial Catalog=ContosoUniversity;
                           Integrated Security=SSPI;" />
  </connectionStrings>

  <!-- 中略 -->

  <applicationSettings>
    <Mvc5App.Properties.Settings>
      <setting name="settingsInfo" serializeAs="String">
        <value>Settings.settings に追加した文字列</value>
      </setting>
      <setting name="intValue" serializeAs="String">
        <value>100</value>
      </setting>
    </Mvc5App.Properties.Settings>
  </applicationSettings>
</configuration>

configSections 要素で Settings.Designer.cs に定義された Settings クラスを利用できるように設定しています。接続文字列は connectionStrings 要素内に、int 型と string 型の値は applicationSettings 要素内に設定されています。

では、ASP.NET Web アプリが利用する別プロジェクトのクラスライブラリがあるとして、それが Settings.settings を使っている場合はどうなるでしょうか?

自動的に web.config に取り込んでくれるということはなさそうです。自分が試した限りですが、.dll.config という構成ファイルが .dll と一緒に bin フォルダに配置され、それが使われるようです。先の記事「構成ファイルの保存場所」に書いたのと同様な配置になるようです。

どのように試したかを以下に書いておきます。

MVC アプリのプロジェクトと同じソリューション内にクラスライブラリ LibraryA を追加し、Settings.settings ファイルに文字列情報を追加します。クラスファイル Class1.cs の中には設定した文字列を取得するコードを書きます。

クラスライブラリの Settings.settings

注: スコープは全て「アプリケーション」でなければなりません。一つでも「ユーザー」に設定されていると、MVC アプリから情報を取得する際、その項目が「アプリケーション」であっても ConfigurationErrorsException がスローされます。エラーメッセージは "現在の構成システムでは、ユーザーによってスコープされた設定はサポートされません" です。

MVC プロジェクトで LibraryA を参照に追加します。

参照の追加

ソリューションをビルドすると自動的に MVC プロジェクトの bin フォルダに LibraryA の .dll とともに .dll.config が コピーされます。

bin フォルダの .dll と .dll.config

その .dll.config に LibraryA の Settings.Settings ファイルに設定した情報が含まれています。MVC アプリから .dll のコード経由でその情報を取得できます。

Tags: , , ,

ASP.NET

ASP.NET MVC の Model

by WebSurfer 29. May 2020 13:46

ASP.NET MVC で言う Model がどういうものかについて書いておきます。ネットの記事などで目にする一般的な MVC の Model の説明とは少々違っていて理解し難いかもしれませんので (実は自分がそうでした)。

ASP.NET MVC の Model

上の画像はマイクロソフト公式解説書「プログラミング Microsoft ASP.NET MVC」に記載されている図を借用したものです。ASP.NET MVC には「入力モデル」「ビューモデル」「ドメインモデル」という 3 種類の Model があることを示しています。それら各モデルが重なっている点にも注意してください。

重なっているのは、Model 自体は一つのクラスとして定義されており同じものですが、使われ方によって「入力モデル」「ビューモデル」「ドメインモデル」というように役割が変わるということと理解すれば良いと思います。

一方、ネットなどでよく目にする一般的な MVC の Model の説明は、例えば Wikipedia の記事 Model View Controller にあるように「アプリケーションデータ、ビジネスルール、ロジック、関数」という役割を持ち、クライアント側から見て View と Controller の背後にあるという構成で説明されていることが多いと思います。

ASP.NET MVC の 3 種類の Model のうち、「ドメインモデル」が上で言う「アプリケーションデータ、ビジネスルール、ロジック、関数」に近いものです。

「ビューモデル」は Controller から View にデータを渡すために使われます。「入力モデル」はクライアントから送信されてくるデータを Controller のアクションメソッドに渡す時に使われます。

以下に説明のための具体例を書きます。

まず、プロジェクトの Models フォルダに、以下の Student クラスの定義(即ち、Model の定義)を含むクラスファイルがあるとします。

using System;
using System.Collections.Generic;

namespace Mvc5App.Models
{
    public class Student
    {
        public int ID { get; set; }
        public string LastName { get; set; }
        public string FirstMidName { get; set; }
        public DateTime EnrollmentDate { get; set; }
    }
}

さらに、データーベースに Student テーブルがあって、それを Entity Framework を利用して編集・更新を行うための Controller と View を作るとします。

編集用の画面を表示するための Controller のアクションメソッドは以下のようになります。

public async Task<ActionResult> Edit(int? id)
{
    Student student = await db.Students.FindAsync(id);

    return View(student);
}

db.Students.FindAsync(id) の db は DbContext クラスを継承するコンテキストクラスで、Entity Framework を利用してデータベースへのクエリの実行などを行うものです。このコードでは、指定された id のレコードをデータベースから取得して Student オブジェクトを作っています。その部分は上の図で「ドメインモデル」に該当します。

return View(student) で View に Student オブジェクトを渡していますが、それが「ビューモデル」に該当します。

アクションメソッドに対応する View (Edit.cshtml) のコードの一行目に @model Mvc5App.Models.Student と書けば、View の中では Model プロパティで Student オブジェクトを取得できます。また、EditorFor などの Html ヘルパーでは、例えばその引数を model => model.LastName とすれば model に Student オブジェクトが渡されます。

View によって html ソースが生成されクライアント (ブラウザ) に送信されます。ユーザーがブラウザに表示されたテキストボックスの内容を編集後、ボタンクリック操作などでデータを送信するとサーバー側ではモデルバインディングという操作が行われます。

ブラウザから送信されてきたデータを受け取ってデーターベースを更新する Controller のアクションメソッドは以下のようになっています。モデルバインディングというのは、送信されてきたデータで Student オブジェクトを作成し、それをアクションメソッドの引数 student に渡す操作ですが、その時使うのが上の図の「入力モデル」になります。

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit(Student student)
{
    if (ModelState.IsValid)
    {
        db.Entry(student).State = EntityState.Modified;
        await db.SaveChangesAsync();
        return RedirectToAction("Index");
    }
    return View(student);
}

ModelState.IsValid が true の場合(送信データの検証結果が OK の場合)、if 文の中の最初の 2 行で既存のレコードが更新されます。その部分も上の図で「ドメインモデル」に該当します。

ASP,NET MVC アプリを作っているときに目立つのが「ビューモデル」と「入力モデル」と思いますが、それらは上に紹介した Wikipedia の記事にあるような一般的な MVC の Model の説明の機能「アプリケーションデータ、ビジネスルール、ロジック、関数」は持たないので、説明と違うということが混乱を招くところと思います。

Tags: , ,

MVC

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

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

View posts in large calendar