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

異なるフォルダのファイルをアップロード

by WebSurfer 9. July 2020 21:33

ブラウザを使って自分の PC の複数のフォルダから複数のファイルを一度にアップロードするにはどうしたらよいかという話を書きます。

複数ファイルをアップロード

html の input type="file" 要素を使うことが前提です。ブラウザがサポートしていれば input type="file" 要素に multiple="multiple" を追加すれば、ユーザーは複数のファイルを選択して一度にアップロードすることができます。

ファイルを選択する際、上の画像の[ファイル選択]ボタン(画像は Chrome の例です。ブラウザによって異なります)をクリックすると下の画像のダイアログが表示されますので、Shift キーや Ctrl キーを使って複数のファイルを選択できます。

送信するファイルの選択

その後[開く(O)]ボタンをクリックすると送信準備完了となり、form を method="post" で submit してやればサーバーに選択された複数のファイルが送信されます。

ただし、上の操作で送信するファイルを選択できるのは一回だけです。この後、もう一度同じ操作を行って別のファイルを追加することはできません。それをすると、前の操作で選択したファイルは送信対象には含まれなくなり、後の操作で選択したファイルのみが送信されるようになります。それは同じフォルダで繰り返し行う場合も異なるフォルダに移動して行う場合も同じです。

ということは、フォルダが異なるファイルを選択するには各フォルダに移動して選択操作を行わざるを得ませんが、送信できるのは最後の操作で選択したファイルのみになります。なので、複数のフォルダにある複数のファイルを一度にアップロードするのは普通のやり方(form を submit する方法)ではできないようです。

代案は HTML5 File APIFormData を利用し Ajax で送信することです。

以下に .NET Framework 版の ASP.NET MVC5 アプリの例を書いておきます。これは一番上の画像を表示したものです。詳しい説明はコードの中のコメントに書きましたのでそれを見てください。手抜きでスミマセン。

Model

MultipleUploadModels クラスをアクションメソッドの引数に設定すれば、送信されてきた複数ファイルは PostedFiles プロパティにモデルバインドされます。CustomField プロパティはファイル以外の追加情報を一緒に送信するためのものです。

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

namespace Mvc5App.Models
{
    public class MultipleUploadModels
    {
        public string CustomField { get; set; }
        public IList<HttpPostedFileBase> PostedFiles { get; set; }
    }
}

View

input type="file" 要素と input type="button" 要素(ボタン)を配置します。form 要素を生成するための html ヘルパー Html.BeginForm の引数(特に enctype = "multipart/form-data" とすること)に注意してください。

@section Scripts { ... } 内の JavaScript / jQuery のコードが HTML5 File API と FormData を利用し Ajax で複数フォルダ内の複数ファイルを送信するものです。このスクリプトがこの記事のキモです

@model Mvc5App.Models.MultipleUploadModels

@{
    ViewBag.Title = "MultipleUpload2";
}

<h2>MultipleUpload2</h2>

@using (Html.BeginForm("MultipleUpload2", "File", 
    FormMethod.Post, new { enctype = "multipart/form-data" }))
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>MultipleUploadModels</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })

        <div class="form-group">
            @Html.LabelFor(model => model.PostedFiles, 
                htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                <input id="mutiplefileupload" type="file" 
                       name="postedfiles" multiple="multiple" />
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="button" id="ajaxUpload" value="Ajax Upload"
                       class="btn btn-default" />
                <br />
                <div id="result"></div>
            </div>
        </div>
        
    </div>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")

    <script type="text/javascript">
        //<![CDATA[

        $(function () {
            // 複数回送信する場合、送信し終わったら fd をクリアして最初から
            // 始めないと、次に送信するとき前回選んだファイルも送信されダブ
            // ってしまう。このコードはそこは未対応なので注意。

            // ブラウザの HTML5 File API サポートを確認
            if (window.File && window.FileReader && window.FileList) {

                // CSRF 用のトークンを含んだ FormData オブジェクトを取得
                var fd = new FormData(document.querySelector("form"));

                // input type="file" 要素のオブジェクトを取得
                var fileUpload = document.getElementById("mutiplefileupload");

                // input type="file" 要素でダイアログを開いてファイルを選択
                // し[開く]ボタンをクリックすると、その都度 change イベン
                // トが発生する。それにリスナ(下のコードの function )を
                // アタッチして FormData オブジェクトを操作する
                fileUpload.addEventListener('change', function (e) {
                    // files プロパティで FileList オブジェクトを取得
                    var filelist = fileUpload.files;

                    // 一回の操作で複数のファイルを選択できるので、以下
                    // のようにループを回して
                    for (let i = 0; i < filelist.length; i++) {
                        // File オブジェクトを FormData に追加していく
                        fd.append("postedfiles", filelist[i]);
                    }
                });

                // [Ajax Upload] ボタンクリックの処置
                $('#ajaxUpload').on('click', function (e) {

                    // 追加データを以下のようにして送信できる。フォーム
                    // データの一番最後に追加されて送信される
                    fd.append("CustomField", "This is some extra data");

                    $.ajax({
                        url: '/file/multipleupload2',
                        method: 'post',
                        data: fd,
                        processData: false, // jQuery にデータを処理させない
                        contentType: false  // contentType を設定させない
                    }).done(function (response) {
                        $("#result").empty;
                        $("#result").html(response);
                    }).fail(function (jqXHR, textStatus, errorThrown) {
                        $("#result").empty;
                        $("#result").text('textStatus: ' + textStatus +
                            ', errorThrown: ' + errorThrown);
                    });
                });
            } else {
                $("#result").empty;
                $("#result").text('File API がサポートされてません。');
            }
        });
        //]]>
    </script>
}

Controller / Action Method

Ajax 呼び出しのみ可能としていますが、普通に input type="submit" ボタンをクリックして post した場合でもファイルのアップロードはできます。ただしその場合は最後のファイル選択操作で選んだファイルのみが送信されます。

using System.Web;
using System.Web.Mvc;
using Mvc5App.Models;
using System.IO;
using System.Collections.Generic;

namespace Mvc5App.Controllers
{
    public class FileController : Controller
    {
        public ActionResult MultipleUpload2()
        {
            return View();
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult MultipleUpload2(MultipleUploadModels model)
        {
            if (!Request.IsAjaxRequest())
            {
                return Content("Ajax 呼び出しのみ可能");
            }

            string result = "";
            string customFiled = model.CustomField;
            IList<HttpPostedFileBase> postedFiles = model.PostedFiles;

            // ファイルが選択されてない場合でも postedFiles は null にならないし、
            // postedFiles.Count は 0 にならない(1 になる)。従い、以下のコード
            // では制御が else に飛ぶことはないので注意
            if (postedFiles != null && postedFiles.Count > 0)
            {
                foreach (HttpPostedFileBase postedFile in postedFiles)
                {
                    if (postedFile != null && postedFile.ContentLength > 0)
                    {
                        // アップロードされたファイル名を取得。ブラウザが IE の
                        // 場合 postedFile.FileName はクライアント側でのフル
                        // パスになることがあるので Path.GetFileName を使う
                        string filename = Path.GetFileName(postedFile.FileName);

                        // 保存ホルダの物理パス\ファイル名
                        string path = Server.MapPath("~/UploadedFiles") + 
                                      "\\" + filename;

                        // アップロードされたファイルを保存
                        postedFile.SaveAs(path);

                        result += filename + " (" + postedFile.ContentType + 
                                  ") - " + postedFile.ContentLength.ToString() +
                                  " bytes アップロード完了<br />";
                    }
                }
                result += "CustomField の文字列: " + customFiled;
            }
            else
            {
                result = "ファイルアップロードに失敗しました";
            }

            return Content(result);
        }
    }
}

注意した方がよさそうと思う点を以下に書いておきます。

  1. 複数回送信する場合、送信し終わったら View の JavaScript のコードにある fd をクリアして最初から始めないと、次に送信するとき前回選んだファイルも送信されダブってしまいます。上のコードはそこは未対応なので注意してください。
  2. ファイルを選択しないで送信した場合でも、上の Controller / Action Method のコードにある postedFiles は null にならないし、postedFiles.Count は 0 になりません(1 になる)。従い、上のコードでは "ファイルアップロードに失敗しました" というエラーメッセージは出ません。  
  3. View のコードにある fd.append("CustomField", "This is some extra data"); でデータを送信して Model の CustomField プロパティにバインドできます。テキストボックスを配置してユーザーに入力してもらい送信してモデルバインドすることも考えましたが、送信前に FormData オブジェクトに append するのが難しく、それは今後の検討課題です。
  4. 上の 2 に書いたファイルを選択しないで送信した場合でも postedFile が null にならず、postedFiles.Count は 1 になる理由は、内容が空のパート(下の Fiddler による要求のキャプチャ画像の 2 つ目のパート)が送信されてくるからです。普通にファイルを選択して input type="submit" ボタンクリックで送信すれば空のパートはなくなりますが、この記事のように File API と FormData を JavaScript で細工するとそれが残ってしまいます。

    想像ですが、var fd = new FormData(document.querySelector("form")); で下の画像の 2 つ目までのパートが取得され、その後の操作で 3 つ目以降が追加されていくからであろうと思います。空のパートを削除する方法は分かりませんでした。

要求ヘッダとコンテンツ

Tags: , , , ,

Upload Download

ASP.NET Core アプリの Web サーバー

by WebSurfer 29. June 2020 15:43

ASP.NET Core 3.1 の Web アプリをホストするのに利用できる Web サーバーは何で、どのような構成になるかということを調べたので、備忘録として書いておきます。

(1) 開発環境

ASP.NET Core 3.1 の Web アプリを Visual Studio Community 2019 のテンプレートを利用して作成し、ツールバーの[デバッグ(D)]⇒[デバッグの開始(S)](または[デバッグなしで開始(H)]) で Visual Studio からアプリを実行すると IIS Express が起動されますので、デフォルトでは IIS Express が使われているのは間違いなさそうです。

IIS Express

Microsoft のドキュメント「ASP.NET Core での Web サーバーの実装」によると、IIS または IIS Express を使用するとインプロセス ホスティング モデルまたはアウトプロセス ホスティング モデルのどちらかで実行されるそうです。(下図参照・・・Microsoft のドキュメントから借用しました)

インプロセス ホスティング

インプロセス ホスティング モデル

アウトプロセス ホスティング

アウトプロセス ホスティング モデル

Visual Studio から実行した場合どちらで動いているかですが「インプロセス ホスティング モデル」と思われます。

そのものズバリを書いた Microsoft の文書は見つからなかったので、関係する資料や Visual Studio のテンプレートで作ったアプリの内容を調べての想像が入ってますが、自信度は 90% ぐらいあります。根拠は以下の通りです。

根拠 1

Microsoft のドキュメント「IIS を使用した Windows での ASP.NET Core のホスト」には、以下のように、デフォルトでは「インプロセス ホスティング モデル」を使うと思える説明があります。

"既存のアプリではインプロセス ホスティングがオプトインされています。ASP.NET Core Web テンプレートでは、インプロセス ホスティング モデルが使用されます。"

"CreateDefaultBuilder では、UseIIS メソッドを呼び出し、CoreCLR を起動して IIS ワーカー プロセス(w3wp.exe または iisexpress.exe) 内のアプリをホストすることで、IServer インスタンスを追加します。"

"CreateHostBuilder (Program.cs) でホストを構築する場合は、CreateDefaultBuilder を呼び出して IIS 統合を有効にします。"

実際にテンプレートを使って作成したアプリの Program.cs は以下のようになっています。

namespace RazorApp
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}

根拠 2

さらに、開発環境で IIS Express が使用する applicationHost.config に以下の設定があります。(場所に注意。IIS 用とは違います。詳しくは先の記事「ApplicationHost.config の場所」を見てください)

<system.webServer>
  <!-- 中略 -->
  <handlers>
    <add name="aspNetCore" path="*" verb="*"
      modules="AspNetCoreModuleV2"
      resourceType="Unspecified" />
  </handlers>
  <aspNetCore processPath="%LAUNCHER_PATH%"
    arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false"
    hostingModel="InProcess" startupTimeLimit="3600"
    requestTimeout="23:00:00" />
  <!-- 中略 -->
</system.webServer>

aspNetCore という名前の HTTP ハンドラが追加され、全ての要求に AspNetCoreModuleV2 ハンドラを使うよう設定されています。AspNetCoreModuleV2 というのは「ASP.NET Core モジュール」らしいです。

また、aspNetCore 要素に hostingModel="InProcess" とあり、「インプロセス ホスティング モデル」が指定されています。

ちなみに上の設定は IIS 用の applicationHost.config には含まれません。

根拠 3

Microsoft のドキュメント「ASP.NET Core モジュール」によると、"アウトプロセス ホスティング用にアプリを構成するには、プロジェクトファイル ( .csproj) で、<AspNetCoreHostingModel> プロパティの値を OutOfProcess に設定します" とのことです。

テンプレートで作ったアプリのプロジェクトファイルにはそのような設定はなく、デフォルトは InPorcess とのことなので、デフォルトでは「インプロセス ホスティング モデル」になるはずです。


ただし、別の Microsoft のドキュメント「ASP.NET Core への Kestrel Web サーバーの実装」には以下の記述があるのが気になります。

"ASP.NET Core プロジェクト テンプレートは既定では Kestrel を使用します。 Program.cs では、ConfigureWebHostDefaults メソッドにより UseKestrel が呼び出されます。"

これが自信度が 90% にとどまっている理由です。(笑)

上に述べた「ASP.NET Core への Kestrel Web サーバーの実装」の記述の他にも分からない点がいろいろあります。それらは以下の通りですが今後の調査課題ということで・・・

  • ASP.NET Core モジュールというのは、チュートリアル「IIS に ASP.NET Core アプリを発行する」のリンク先「現在の .NET Core ホスティング バンドルのインストーラー (直接ダウンロード)」からダウンロードされる dotnet-hosting-3.1.x-win.exe に含まれる Microsoft .NET Core 3.1.x - Windows Server Hosting のことだと思われる。でも、それをインストールする前から Visual Studio からアプリを実行できた。Visual Studio 2019 をインストールした時点で含まれていた?
  • IIS Express と IISHttpServer の間の設定は何もしてないがそれで動くのは何故? Microsoft のドキュメント「IIS を使用した Windows での ASP.NET Core のホスト」によると、ASP.NET Core モジュールがアプリの初期化を実行(Loads the CoreCLR と Calls Program.Main)すると書いてある。それだけで十分なのか?
  • そもそも、デフォルトで IIS Express を使うように設定される理由は何?
  • 開発環境で IIS Express リバースプロキシとして使わないで Kestrel に直接アクセスするように設定することはできるか?

(2) 運用環境

運用環境での Web serverについては、Microsoft のドキュメント「ASP.NET Core での Web サーバーの実装」がまとまっていて概要を理解するのに分かりやすいと思いました。

Linux 系の OS の場合は、上で紹介したドキュメントによると、要するに Nginx とか Apache をリバースプロキシに使って、Kestrel で ASP.NET Core アプリをホストするということのようです。Linux 系の OS は自分は触ったこともないので、残念ながらそれ以上詳しい話はできないです。

Windows OS の場合は、ASP.NET Core に付属している以下のサーバーを利用できるそうです。

  • Kestrel
  • IISHttpServer
  • HTTP.sys (旧称 WebListener)

運用環境で IIS によってホストする場合の説明は、Microsoft のドキュメント「IIS を使用した Windows での ASP.NET Core のホスト」が詳しいので、それを見てください。

上にも書きましたが、「インプロセス ホスティング」と「アウトプロセス ホスティング」という 2 つのモデルがあって、前者には IISHttpServer が、後者には Kestrel が使用されるそうです。

他に、HTTP.sys(IIS の HTTP Protocol Stack HTTP.sys とは違うもののようです)を使うというオプションもあるそうです。自分は勉強不足で多くは語れませんので、Microsoft のドキュメント「ASP.NET Core での HTTP.sys Web サーバーの実装」を見てください。

OS が Windows 10 Pro 64-bit の PC のローカル IIS に ASP.NET Core 3.1 アプリを発行するのは実際にやってみました。

その手順は Microsoft のチュートリアル「IIS に ASP.NET Core アプリを発行する」の通りですが、概略を以下に書いておきます。結果「インプロセス ホスティング モデル」になっていると思います。

  1. C:\WebSites2019 というフォルダ下に AspNetCoreWebSite という名前のフォルダを作成。
  2. IIS Manager を起動してそのフォルダをサイトに設定。
  3. サイトバインド設定は、種類: http, IP アドレス: 未使用の IP アドレスすべて, ポート: 80, ホスト名: www.aspnetcorewebsite.com とした。
  4. hosts ファイルに 127.0.0.1 www.aspnetcorewebsite.com と設定。
  5. VS2019 のテンプレートで自動生成される Startup.cs に HTTPS 通信を実行させるミドルウェアを適用するコード app.UseHsts(); と app.UseHttpsRedirection(); が含まれている。自分の PC のローカル IIS では HTTPS で通信できないので、そのあたりの設定を変更する必要があるかと思ったが、実際試すとそうでもなかった。 Microsoft のドキュメント Enforce HTTPS in ASP.NET Core にポートを設定しないとリダイレクトされないというようなことが書いてある。そのため?
  6. Microsoft のドキュメント「IIS を使用した Windows での ASP.NET Core のホスト」に "IIS サーバーのオプションを構成するには、IISServerOptions 用のサービス構成を ConfigureServices に含めます" と書いてあるが、テンプレートで作ったアプリにはその設定はない。設定しなければデフォルトが使われるので問題なさそう。
  7. チュートリアルの「アプリを発行および配置する」セクションに従って、上のステップで作成した C:\WebSites2019\AspNetCoreWebSite フォルダにプロジェクトを発行。IIS Manager で見た結果は下の画像のようになる。

IIS に発行された Razor ページ

  1. 以上の設定でブラウザから http://www.aspnetcorewebsite.com/ を要求すると Razor ページが表示される。 ワーカープロセスに C:\WebSites2019\AspNetCoreWebSite フォルダへの権限は何も与える必要はなかった。
  2. web.config は元のプロジェクトには含まれないが、自動生成されて配置される。aspNetCore という名前の HTTP ハンドラが追加され、全ての要求に AspNetCoreModuleV2 ハンドラを使うよう設定されている。AspNetCoreModuleV2 というのは「ASP.NET Core モジュール」らしい。
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <location path="." inheritInChildApplications="false">
    <system.webServer>
      <handlers>
        <add name="aspNetCore" path="*" verb="*" 
          modules="AspNetCoreModuleV2" 
          resourceType="Unspecified" />
      </handlers>
      <aspNetCore processPath="dotnet" 
        arguments=".\RazorApp.dll" stdoutLogEnabled="false" 
        stdoutLogFile=".\logs\stdout" 
        hostingModel="inprocess" />
    </system.webServer>
  </location>
</configuration>
<!--ProjectGuid: 8db7ea97-35b2-4168-81d5-7c68974ce862-->

Tags: , , ,

CORE

About this blog

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

Calendar

<<  July 2020  >>
MoTuWeThFrSaSu
293012345
6789101112
13141516171819
20212223242526
272829303112
3456789

View posts in large calendar