連載:コードから触るIIS 8
C#でアプリケーション・プールの設定とWebサイトの作成
IISでのWebサイトの作成をC#コードから行ってみよう。一連の開発手順とコードを分かりやすく解説。
前回の記事では、予定を変えてARR(Application Request Routing)のインストールと設定を行った。今回は前回の予告どおり、Webサイトの作成をC#コードから行ってみたい。
Webサイトとアプリケーション・プールとは
実際のコードを紹介する前に、簡単にIISの構成要素であるWebサイトとアプリケーション・プールについて説明しよう。
IISの構成要素としての「Webサイト」は、通常のWebサイトとは意味が異なり、静的・動的問わずIISでコンテンツをネットワークに公開するときの単位である。Webサイトは、Windows Serverに割り当てられた「IPアドレス」「リッスンTCPポート」「ホスト・ヘッダー」の3つの組み合わせにより一意に識別される。Webサイトは、この3つの組み合わせに合致するリクエストに応答するようになっている。
このWebサイトのうち、ASP.NETを動かしているプロセスが「ワーカー・プロセス」である。そして、アプリケーション・プールがこれらWebサイトとワーカー・プロセスを関連付けている。アプリケーション・プールを通してWebサイトとワーカー・プロセスは多対多で関連付けることもできるが、今回は1つのアプリケーション・プールと1つのワーカー・プロセスを扱っている。
準備
前回に引き続き、Microsoft Azure(旧称:Windows Azure)上の仮想マシンとして起動した「Windows Server 2012 Datacenter Edition」を対象に説明する。なお、今回から日本語言語パックをインストールしている。日本語言語パックのインストール方法については、MSDN Blog の「Azure 仮想マシン上のWindows Server 2012の日本語化は標準手順で OK」を参考にしてもらいたい。
次にC#コードを書く開発環境を準備する。今回はVisual Studio 2012 Ultimateを使った。なお、無償のVisual Studio Express 2012 for Windows Desktopでも今回のアプリをビルドできることを確認済みである。Visual Studioをインストールするのは手元のWindowsマシン、Windows Server 2012のどちらでもよい。実際にサーバーを構築する場合はVisual Studioをインストールするのは不要だが、取りあえず試してみる分にはWindows Serverにインストールして、デバッグ実行するのが楽だろう。
プロジェクトの作成
それでは、[新しいプロジェクト]ダイアログで[Visual C#]-[Windows]の[コンソール アプリケーション]テンプレートを使って実際にプロジェクトを作成する。ソリューション名およびプロジェクト名は好きな名前で構わない。今回は「IISSiteSetup」という名前にした。
ライブラリの追加
今回実行するC#のコードをコンパイルおよび実行するのに必要なライブラリを追加しよう。
まず、プロジェクト「IISSiteSetup」の[参照設定]の右クリック・メニューから[参照の追加]を選択する。[参照マネージャー]が表示されるので、左側のツリーから[アセンブリ]-[フレームワーク]を選択し、右側のリストから[System.DirectoryServices]のチェックボックスにチェックを入れて[OK]ボタンを押して、ライブラリを追加する。
次にIISに対する操作を行うためのライブラリも追加する。これは前回の記事と同じ手順である。サイトを追加するWindows Server 2012の「C:\Windows\System32\inetsrv\Microsoft.Web.Management.dll」を参照に加える。ローカルの環境で開発する場合は、このファイルがローカル・コンピューター上には存在しないため、Windows Serverからこのファイルをコピーするのがいいだろう。再度、プロジェクトの[参照設定]を右クリックし、[参照の追加]を選択する。[参照]ボタンをクリックし、ローカル・コンピューターで開発している場合はコピーした場所を、Windows Serverで開発している場合は上記のパスを開き、「Microsoft.Web.Administration.dll」を選択する。
これで参照に追加されているはずだ。[ソリューション エクスプローラー]でプロジェクトの[参照設定]を開くと、2つのライブラリへの参照が追加されているのが分かるだろう。
それではWebサイト作成に必要な処理を記述していこう。
Webサイトのアプリケーション・プールの実行ユーザーを作成する
まず、Webサイトのアプリケーション・プールの実行ユーザーを作成する。
IISの初期設定ではIIS_IUSRSグループに属するユーザーが実行ユーザーになるが、今回は専用のユーザーを作成することにする。専用のユーザーを作成する理由は、フォルダーへのアクセス権など、ユーザーごとの権限設定を管理しやすくするためである。以下のコードのメソッドを実行してほしい。ユーザー名とパスワードは好きな値に変更できる。なお、本稿で説明する全メソッドをまとめて実行するコードを文末に掲載している。
private static void CreateUser(string user, string password)
{
using (var directoryEntry = new DirectoryEntry(string.Format("WinNT://{0},computer", Environment.MachineName)))
{
using (var userEntry = directoryEntry.Children.Add(user, "user"))
{
userEntry.Invoke("SetPassword", password);
userEntry.Invoke("Put", "Description", "AppPool Account for " + user);
userEntry.CommitChanges();
}
using (var userEntry = directoryEntry.Children.Find(user, "user"))
{
// パスワードの有効期限を無効化
var flag = (int)userEntry.Properties["UserFlags"].Value;
flag = flag | 0x10040;
userEntry.Invoke("Put", "UserFlags", flag);
userEntry.CommitChanges();
}
}
}
|
ファイルの先頭に「using System.DirectoryServices;」を記述する必要がある。
このコードでは、ユーザーを作成後、パスワードの有効期限を無効化している。無効化しない場合、有効期限が来る前にパスワードを変更しないと、有効期限経過後にサーバーを再起動した場合などにアプリケーション・プールを実行できなくなるため、注意が必要である。
アプリケーション・プールを作成する
次に、アプリケーション・プールを作成する。
デフォルトの挙動では、Webサイト作成時にWebサイトごとにアプリケーション・プールを作成し、そのワーカー・プロセスのオーナーはIIS_IUSRSに属するユーザーとなる。今回はアプリケーション・プールの名前を指定し、ユーザーは先ほど作成した専用のユーザーとなるように作成する(次のコードを参照)。Webサイトで利用するフォルダーのアクセス権限などを設定する際に便利であるため、筆者はこの方法を用いている。
private static void CreateAppPool(string poolName, string user, string password)
{
using (var serverManager = new ServerManager())
{
var applicationPools = serverManager.ApplicationPools;
try
{
if (applicationPools.Any(p => p.Name == poolName))
{
Console.WriteLine(poolName + " はすでに作成されています");
return;
}
var applicationPool = applicationPools.Add(poolName);
applicationPool.ProcessModel.UserName = user;
applicationPool.ProcessModel.Attributes["password"].SetMetadata("encryptionProvider", "");
applicationPool.ProcessModel.Password = password;
applicationPool.ProcessModel.IdentityType = ProcessModelIdentityType.SpecificUser;
}
catch (Exception ex)
{
Console.WriteLine(ex);
return;
}
serverManager.CommitChanges();
}
}
|
ファイルの先頭に「using Microsoft.Web.Administration;」を記述する必要がある。
上記のコードでは、ServerManagerクラス(Microsoft.Web.Administration名前空間)のオブジェクトを作成し、必要な処理をした後、CommitChangesメソッドを実行して処理を確定させている。
Webサイトを作成する
アプリケーション・プールを作成したら、Webサイトを作成する。
具体的には下記のコードを記述する。アプリケーション・プールの名前は先ほど作成した名前を指定する。
private static void CreateSite(string siteName, string poolName, string dirRoot, string bindingInformation)
{
using (var serverManager = new ServerManager())
{
var sites = serverManager.Sites;
// 「既存のサイトIDの最大値 + 1」を新しいIDとする
var siteId = (sites.Any() ? sites.Max(site => site.Id) : 0) + 1;
try
{
var site = sites.CreateElement();
site.Id = siteId;
site.SetAttributeValue("name", siteName);
sites.Add(site);
var application = site.Applications.CreateElement();
application.SetAttributeValue("path", "/");
application.SetAttributeValue("applicationPool", poolName);
site.Applications.Add(application);
var virtualDirectory = application.VirtualDirectories.CreateElement();
virtualDirectory.SetAttributeValue("path", "/");
virtualDirectory.SetAttributeValue("physicalPath", dirRoot);
application.VirtualDirectories.Add(virtualDirectory);
var binding = site.Bindings.CreateElement();
binding.SetAttributeValue("protocol", "http");
binding.SetAttributeValue("bindingInformation", bindingInformation);
site.Bindings.Add(binding);
serverManager.CommitChanges();
}
catch (Exception ex)
{
Console.WriteLine(ex);
return;
}
}
}
|
WebサイトのIDは一意なIDであるため、このサンプルでは「既存のサイトの最大値 + 1」を割り当てている。Webサイトはコンテンツを配置する物理ディレクトリを指定する必要があるため、dirRoot変数でそれを決めている。
物理ディレクトリを作成し、必要な権限を与える
次に先ほどWebサイトを作成したときに指定した物理ディレクトリを作成し、必要な権限を与える。
権限を与える対象は、アプリケーション・プール作成時に指定したオーナーのユーザーである。具体的に与える権限については下記のコードを参考にしてほしい。また、これは標準の構成で必要だと思われる権限である。他に必要な権限があれば、追加してほしい。
private static void SetupWebSiteDirectory(string dir, string siteUser)
{
if (!Directory.Exists(dir))
{
var directorySecurity = new DirectorySecurity();
AddFullControl(directorySecurity, "BUILTIN¥¥Administrators");
directorySecurity.AddAccessRule(new FileSystemAccessRule(siteUser, FileSystemRights.ReadData, AccessControlType.Allow));
directorySecurity.AddAccessRule(new FileSystemAccessRule(siteUser, FileSystemRights.ReadAndExecute, AccessControlType.Allow));
directorySecurity.AddAccessRule(new FileSystemAccessRule(siteUser, FileSystemRights.FullControl, InheritanceFlags.ObjectInherit, PropagationFlags.InheritOnly, AccessControlType.Allow));
directorySecurity.AddAccessRule(new FileSystemAccessRule(siteUser, FileSystemRights.FullControl, InheritanceFlags.ContainerInherit, PropagationFlags.InheritOnly, AccessControlType.Allow));
Directory.CreateDirectory(dir, directorySecurity);
return;
}
var accessControl = Directory.GetAccessControl(dir);
AddFullControl(accessControl, "BUILTIN¥¥Administrators");
accessControl.AddAccessRule(new FileSystemAccessRule(siteUser, FileSystemRights.ReadData, AccessControlType.Allow));
accessControl.AddAccessRule(new FileSystemAccessRule(siteUser, FileSystemRights.ReadAndExecute, AccessControlType.Allow));
accessControl.AddAccessRule(new FileSystemAccessRule(siteUser, FileSystemRights.FullControl, InheritanceFlags.ObjectInherit, PropagationFlags.InheritOnly, AccessControlType.Allow));
accessControl.AddAccessRule(new FileSystemAccessRule(siteUser, FileSystemRights.FullControl, InheritanceFlags.ContainerInherit, PropagationFlags.InheritOnly, AccessControlType.Allow));
Directory.SetAccessControl(dir, accessControl);
}
private static void SetAnonymousUserToProcessId()
{
using (var serverManager = new ServerManager())
{
try
{
var applicationHostConfiguration = serverManager.GetApplicationHostConfiguration();
var section = applicationHostConfiguration.GetSection("system.webServer/security/authentication/anonymousAuthentication");
section.SetAttributeValue("userName", "");
section.RawAttributes.Remove("password");
serverManager.CommitChanges();
}
catch (Exception ex)
{
Console.WriteLine(ex);
return;
}
}
}
private static void AddFullControl(DirectorySecurity dirsec, string user)
{
dirsec.AddAccessRule(new FileSystemAccessRule(user, FileSystemRights.FullControl, AccessControlType.Allow));
dirsec.AddAccessRule(new FileSystemAccessRule(user, FileSystemRights.FullControl, InheritanceFlags.ObjectInherit, PropagationFlags.InheritOnly, AccessControlType.Allow));
dirsec.AddAccessRule(new FileSystemAccessRule(user, FileSystemRights.FullControl, InheritanceFlags.ContainerInherit, PropagationFlags.InheritOnly, AccessControlType.Allow));
}
|
ファイルの先頭に「using System.IO;」と「using System.Security.AccessControl;」を記述する必要がある。
匿名認証を無効にする
最後に、必須ではないが、匿名認証を無効にしておく(次のコードを参照)。
private static void DisableAnonymousAuthentication()
{
using (var serverManager = new ServerManager())
{
try
{
var applicationHostConfiguration = serverManager.GetApplicationHostConfiguration();
var section = applicationHostConfiguration.GetSection("system.webServer/security/authentication/anonymousAuthentication");
section.SetAttributeValue("userName", "");
section.RawAttributes.Remove("password");
serverManager.CommitChanges();
}
catch (Exception ex)
{
Console.WriteLine(ex);
return;
}
}
}
|
以上でWebサイトを作成することができた。
一連のメソッドを全て実行するコード
以下に、一連のメソッドを実行するコードを載せておく。この際、パスワードは複雑なものにしないとポリシー違反で例外が発生する。また、最初の説明に書いたように、Webサイトに結び付いたホスト名、ポート番号を指定しているが、通常、80番ポートは既定で作られているWebサイトが使用しているため、このコードでは8080番を指定している。80番ポートを使いたい場合は、この既定のWebサイトを削除する、もしくは停止しておく必要がある。
static void Main(string[] args)
{
var userName = "BuildInsider";
var password = "Offline00!";
var poolName = "BuildInsider-pool";
var siteName = "BuildInsider";
var dirRoot = @"C:¥www¥BuildInsider";
var binding = "*:8080:";
CreateUser(userName, password);
CreateAppPool(poolName, userName, password);
CreateSite(siteName, poolName, dirRoot, binding);
SetupWebSiteDirectory(dirRoot, userName);
DisableAnonymousAuthentication();
Console.ReadKey();
}
|
作成したWebサイトの表示例
試しに、ブラウザーを起動して、「http://localhost:8080/」にアクセスしてみよう。次のようなエラー画面が出るはずである。
これは、Webサイトにコンテンツが存在していないためである。
試しに次のような単純なindex.htmlファイルをWebサイトの物理ディレクトリとして指定した「C:\www\BuildInsider」直下に置いて、再度、「http://localhost:8080/」にアクセスしてみよう。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>動作確認用</title>
</head>
<body>
<p>Webサイトが動いています</p>
</body>
</html>
|
今度は次のよう表示されるはずである。これでC#のコードから、アプリケーション・プールとWebサイトの作成が正常にできているのを確認できた。
■
次回は、Application Initialization Moduleの紹介と、URL Rewriteを活用したメンテナンス画面の表示を紹介したいと思う。
※以下では、本稿の前後を合わせて5回分(第1回~第5回)のみ表示しています。
連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。
1. PowerShellによるIIS 8のインストール
Windows Server 2012に搭載されているWebサーバー「IIS 8」の機能を「コードから触って動かす」をテーマにした連載スタート。今回はPowerShellスクリプトでIISのセットアップなどを実行する方法を紹介する。
2. C#コードによるARR 3.0 RTMのインストールと設定
7月26日に正式版がリリースされた「ARR(Application Request Routing) 3.0」とは? そのARRを、C#コードによりインストール&設定する方法を説明する。
4. オート・スタートによるアプリケーションの初期化処理とメンテナンスページ
IISによるWebサイト起動時に何らかの処理を行うための方法を解説。また、メンテナンスページをIISにより実現する方法も紹介。
5. IIS 8でさわるSignalR 2.0
SignalR 2.0をIIS 8(WebSocket)で動かす方法を説明。Windows Server/IIS、.NET/Visual Studioに関するアップデートについても簡単に紹介。