C# のプロビジョニング サンプル

公開日: 2007 年 12 月 22 日 (作業者: walterov (英語))

更新日: 2008 年 3 月 10 日 (作業者: walterov (英語))

概要

ここ何年かの間に、Web サイトは、ある程度のスキルを持つ Web ユーザーであれば容易に作成できるものとなりました。ドメイン名を登録したら、次にすることはホスティング事業者を選ぶことです。エンド ユーザーは、さまざまなタイプのホスティング事業者の中から自分の好みのホスティング事業者を選ぶことができます。エンド ユーザーのニーズに応える Web ホスティング事業者は世界各地に存在します。しかし、事業者のタイプはさまざまでも、契約を交わし Web サイトを作成するというシナリオにほとんど違いはありません。

まず最初の段階として、ユーザーのために新しいユーザー アカウントを作成する必要があります。アカウントのセットアップが完了すると、エンド ユーザーはサイトにどのような機能やオプションを組み込むかを決定します。たとえば、必要なディスク容量や FTP 機能、仮想ディレクトリの作成、データベースの必要性などについて検討する必要があります。ホスティング事業者はコントロール パネルやダッシュボード アプリケーションを構築して、エンド ユーザーがこれらの機能を作成および管理できるようにします。

コントロール パネルにこれらの機能を実装する方法は、いくつかあります。このセクションでは、マネージ コードを使用してプロビジョニングに関連する機能を実装する方法について考えます。取り上げる機能の概要は、以下のとおりです。

新しいユーザー アカウントのプロビジョニング

コンテンツ ストレージの作成

アプリケーション プールの作成

Web サイトの作成

  • バインドの作成
  • ルート アプリケーションの作成
  • 仮想ディレクトリの作成

FTP サイトの作成

これらの機能に加えて、ホスティング事業者は電子メールのプロビジョニング、DNS マッピング、課金などのサービスも実装します。ただし、この記事では、IIS7 サーバー上で共有される Web サイトのプロビジョニングに直接関連する上記の項目のみを取り上げます。

ここで説明されるソース コードはすべて、Visual Studio 2005 のサンプル プロジェクト (英語) に含まれています。

新しいユーザー アカウントのプロビジョニング

サイトを管理および維持するサイトの所有者は、ユーザー アカウントを持っている必要があります。このアカウントは、Active Directory のアカウントかローカル ユーザー アカウントのいずれかになります。シナリオが複雑になることを避けるため、ここではローカル ユーザー アカウントを作成する手順のみを説明します。

以下のコードは、ローカル アカウントの作成方法を示しています。

System.DirectoryServices 名前空間を指定する必要があります。

prepublic static bool CreateLocalUserAccount(string userName, string password)
{
   try
   {
      if (string.IsNullOrEmpty(userName))
        throw new ArgumentNullException("userName", "Invalid User Name.");
      if (string.IsNullOrEmpty(password))
        throw new ArgumentNullException("password", "Invalid Password.");
      DirectoryEntry directoryEntry = new DirectoryEntry("WinNT://" +
      Environment.MachineName + ",computer");
      bool userFound = false;
      try
      {
         if (directoryEntry.Children.Find(userName, "user") != null)
           userFound = true;
      }
      catch
      {
         userFound = false;
      }
      if (!userFound)
      {
         DirectoryEntry newUser = directoryEntry.Children.Add(userName, "user");
         newUser.Invoke("SetPassword", new object[] { password });
         newUser.Invoke("Put", new object[] { "Description", "Application Pool User Account" });
         newUser.CommitChanges();
         newUser.Close();
      }
   }
   catch (Exception ex)
   {
        throw new Exception(ex.Message, ex);
   }
   return true;
}

コンテンツ ストレージの作成

Web サイトを構築するには、サイトのコンテンツをアップロードする際などにユーザーが使用できる場所をファイル システム上に用意する必要があります。.NET の Directory クラスの API を使用すると、ファイル システム上にディレクトリを作成できます。

Directory.CreateDirectory(parentPath + "\\" + directoryName);

ユーザーが自分のコンテンツを管理できるように、コンテンツ ストレージにはアクセス許可を設定する必要があります。以下のコードは、C# のマネージ コードを使用してディレクトリのアクセス許可を設定する方法を示しています。

public static bool AddDirectorySecurity(string directoryPath, string userAccount, FileSystemRights rights, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AccessControlType controlType)
{
   try
   {
       // Create a new DirectoryInfo object.
       DirectoryInfo dInfo = new DirectoryInfo(directoryPath);
       // Get a DirectorySecurity object that represents the 
       // current security settings.
       DirectorySecurity dSecurity = dInfo.GetAccessControl();
       // Add the FileSystemAccessRule to the security settings. 
       dSecurity.AddAccessRule(new FileSystemAccessRule(userAccount, rights, inheritanceFlags, propagationFlags, controlType));
       // Set the new access settings.
       dInfo.SetAccessControl(dSecurity);
   }
   catch (Exception ex)
   {
       throw new Exception(ex.Message, ex);
   }
   return true;
}

また、ディスク クォータが制限されている場合は、以下のコードのように、マネージ コードを使用してディスク クォータを設定できます。ディスク クォータの管理機能を使用するには、Windows のディスク クォータ管理コンポーネントへの参照を追加する必要があります。このコンポーネントは、window\system32\dskquota.dll にあります。

public static bool AddUserDiskQuota(string userName, double quota, double quotaThreshold, string diskVolume)
{
   try
   {
       DiskQuotaControlClass diskQuotaCtrl = new DiskQuotaControlClass();
       diskQuotaCtrl.Initialize(diskVolume, true);
      diskQuotaCtrl.UserNameResolution = UserNameResolutionConstants.dqResolveNone;
      DIDiskQuotaUser diskUser = diskQuotaCtrl.AddUser(userName);
      diskUser.QuotaLimit = quota;
      diskUser.QuotaThreshold = quotaThreshold;
    }
    catch (Exception ex)
    {
      throw new Exception(ex.Message, ex);
    }
    return true;
}

アプリケーション プールの作成

アプリケーション プールは、ワーカー プロセスの設定を定義します。ワーカー プロセスとは、1 つ以上の IIS 7 アプリケーションをホストし、アプリケーションの要求を処理するプロセスです。アプリケーションの要求はすべてアプリケーション プールのワーカー プロセス内で処理されるため、アプリケーション プールはプロセス分離の単位であるということができます。

また、セキュリティの観点からも、アプリケーション プールは分離の単位といえます。アプリケーション プールは異なる ID で実行できるため、ACL によって必要なリソースを独占的に確保し、他のアプリケーション プールのアプリケーションがそのリソースにアクセスできないようにすることができます。以下のコードは、アプリケーション プールの作成、ID の設定、およびプロパティの設定方法を示しています。

public static bool CreateApplicationPool(string applicationPoolName, ProcessModelIdentityType identityType, string applicationPoolIdentity, string password, string managedRuntimeVersion, bool autoStart, bool enable32BitAppOnWin64,ManagedPipelineMode managedPipelineMode, long queueLength, TimeSpan idleTimeout, long periodicRestartPrivateMemory, TimeSpan periodicRestartTime)
{
   try
   {
      if (identityType == ProcessModelIdentityType.SpecificUser)
      {
         if (string.IsNullOrEmpty(applicationPoolName))
            throw new ArgumentNullException("applicationPoolName", "CreateApplicationPool: applicationPoolName is null or empty.");
         if (string.IsNullOrEmpty(applicationPoolIdentity))
            throw new ArgumentNullException("applicationPoolIdentity", "CreateApplicationPool: applicationPoolIdentity is null or empty.");
         if (string.IsNullOrEmpty(password))
            throw new ArgumentNullException("password", "CreateApplicationPool: password is null or empty.");
      }
      using (ServerManager mgr = new ServerManager())
      {
         ApplicationPool newAppPool = mgr.ApplicationPools.Add(applicationPoolName);
         if (identityType == ProcessModelIdentityType.SpecificUser)
         {
            newAppPool.ProcessModel.IdentityType = ProcessModelIdentityType.SpecificUser;
            newAppPool.ProcessModel.UserName = applicationPoolIdentity;
            newAppPool.ProcessModel.Password = password;
         }
         else
         {
            newAppPool.ProcessModel.IdentityType = identityType;
         }
            if (!string.IsNullOrEmpty(managedRuntimeVersion))
               newAppPool.ManagedRuntimeVersion = managedRuntimeVersion;
               newAppPool.AutoStart = autoStart;
               newAppPool.Enable32BitAppOnWin64 = enable32BitAppOnWin64;
               newAppPool.ManagedPipelineMode = managedPipelineMode;
            if (queueLength > 0)
               newAppPool.QueueLength = queueLength;
            if (idleTimeout != TimeSpan.MinValue)
               newAppPool.ProcessModel.IdleTimeout = idleTimeout;
            if (periodicRestartPrivateMemory > 0)
               newAppPool.Recycling.PeriodicRestart.PrivateMemory = periodicRestartPrivateMemory;
            if (periodicRestartTime != TimeSpan.MinValue)
               newAppPool.Recycling.PeriodicRestart.Time = periodicRestartTime;
            mgr.CommitChanges();
         }
   }
   catch (Exception ex)
   {
      throw new Exception(ex.Message, ex);
   }
   return true;
 }

サイトの作成

サイトは、HTTP 要求の受信および処理方法を指定する、最上位の論理コンテナーです。サイトでは、一連のバインドが定義されます。サイトがどのような受信要求をリッスンするかは、これらのバインドによって決まります。また、サイトには、サイトの URL 名前空間を分割するアプリケーション/仮想ディレクトリの定義が含まれます。これらのアプリケーション/仮想ディレクトリによって、アプリケーションのコンテンツが構造化されます。

以下のコードは、新しいサイトの作成方法を示しています。Microsoft.Web.Administration 名前空間は、ServerManager オブジェクトを実装する際に必要となります。Application コレクション、Site コレクション、Binding オブジェクトなどはすべて ServerManager オブジェクトを介してアクセスされます。

public static bool CreateWebSite(string siteName)
{
   try
   {
     if (string.IsNullOrEmpty(siteName))
     {
        throw new ArgumentNullException("siteName", "CreateWebSite: siteName is null or empty.");
     }
     //get the server manager instance
     using (ServerManager mgr = new ServerManager())
     {
        Site newSite = mgr.Sites.CreateElement();
        //get site id
        newSite.Id = GenerateNewSiteID(mgr, siteName);
        newSite.SetAttributeValue("name", siteName);
        mgr.Sites.Add(newSite);
        mgr.CommitChanges();
     }
   }
   catch (Exception ex)
   {
      throw new Exception(ex.Message, ex);
   }
   return true;
}

バインドの作成

バインド****とは、プロトコル名とプロトコル固有のバインド情報の組み合わせを指します。IIS 7 ではマルチプロトコルのバインド (WCF の SOAP/TCP、FTP など) がサポートされていますが、ここでは HTTP パスのみを取り上げますので、以下をリッスンする HTTP エンドポイントを 1 つの HTTP バインドで十分に定義できます。

  • 特定のインターフェイス IP アドレス (あるいは、すべてのインターフェイス)
  • 特定のポート番号
  • 特定の HTTP ホスト ヘッダー (あるいは、すべてのホスト ヘッダー)

このようにサーバー上では、さまざまな IP アドレスやポート、または異なるホスト ヘッダーを持つ同一の IP アドレス/ポートをリッスンする多くのサイトを構成できます。

public static bool AddSiteBinding(string siteName, string ipAddress, string tcpPort, string hostHeader, string protocol)
{
    try
    {
        if (string.IsNullOrEmpty(siteName))
        {
            throw new ArgumentNullException("siteName", "AddSiteBinding: siteName is null or empty.");
        }
        //get the server manager instance
        using (ServerManager mgr = new ServerManager())
        {
            SiteCollection sites = mgr.Sites;
            Site site = mgr.Sites[siteName];
            if (site != null)
            {
                string bind = ipAddress + ":" + tcpPort + ":" + hostHeader;
                //check the binding exists or not
                foreach (Binding b in site.Bindings)
                {
                    if (b.Protocol == protocol && b.BindingInformation == bind)
                    {
                        throw new Exception("A binding with the same ip, port and host header already exists.");
                    }
                }
                Binding newBinding = site.Bindings.CreateElement();
                newBinding.Protocol = protocol;
                newBinding.BindingInformation = bind;
                site.Bindings.Add(newBinding);
                mgr.CommitChanges();
                return true;
            } 
            else
                throw new Exception("Site: " + siteName + " does not exist.");
        }
    }
    catch (Exception ex)
    {
        throw new Exception(ex.Message, ex);
    }
}

ルート アプリケーションの作成

アプリケーションは、Web サイトの機能を格納する論理コンテナーです。アプリケーションにより、サイトの URL 名前空間を複数の部分に分割し、各部分のランタイムの挙動を個別に制御することができます。

たとえば、各アプリケーションを別個のアプリケーション プールに割り当て、別個のプロセスで実行することにより、アプリケーションを分離することができます。また、プロセスを異なる Windows ID で実行して、サンドボックス化することも可能です。ルート アプリケーション レベルでは、ASP.NET アプリケーションやアプリケーション ドメインの作成も実行できます。

public static bool AddApplication(string siteName, string applicationPath, string applicationPool, string virtualDirectoryPath, string physicalPath, string userName, string password)
    {
        try
        {
            if (string.IsNullOrEmpty(siteName))
                throw new ArgumentNullException("siteName", "AddApplication: siteName is null or empty.");
            if (string.IsNullOrEmpty(applicationPath))
                throw new ArgumentNullException("applicationPath", "AddApplication: application path is null or empty.");
            if (string.IsNullOrEmpty(physicalPath))
                throw new ArgumentNullException("PhysicalPath", "AddApplication: Invalid physical path.");
            if (string.IsNullOrEmpty(applicationPool))
                throw new ArgumentNullException("ApplicationPool", "AddApplication: application pool namespace is Nullable or empty.");
            using (ServerManager mgr = new ServerManager())
            {
                ApplicationPool appPool = mgr.ApplicationPools[applicationPool];
                if (appPool == null)
                    throw new Exception("Application Pool: " + applicationPool + " does not exist.");
                Site site = mgr.Sites[siteName];
                if (site != null)
                {
                    Application app = site.Applications[applicationPath];
                    if (app != null)
                        throw new Exception("Application: " + applicationPath + " already exists.");
                    else
                    {
                        app = site.Applications.CreateElement();
                        app.Path = applicationPath;
                        app.ApplicationPoolName = applicationPool;
                        VirtualDirectory vDir = app.VirtualDirectories.CreateElement();
                        vDir.Path = virtualDirectoryPath;
                        vDir.PhysicalPath = physicalPath;
                        if (!string.IsNullOrEmpty(userName))
                        {
                            if (string.IsNullOrEmpty(password))
                                throw new Exception("Invalid Virtual Directory User Account Password.");
                            else
                            {
                                vDir.UserName = userName;
                                vDir.Password = password;
                            }
                        }
                        app.VirtualDirectories.Add(vDir);
                    }
                    site.Applications.Add(app);
                    mgr.CommitChanges();
                    return true;
                }
                else
                    throw new Exception("Site: " + siteName + " does not exist.");
            }
        }
        catch (Exception ex)
        {
            throw new Exception(ex.Message, ex);
        }
   }

仮想ディレクトリの作成

仮想ディレクトリは、アプリケーションの URL 名前空間の特定の部分をディスク上の物理的な場所にマッピングします。要求がアプリケーションにルーティングされると、通常のアルゴリズムを使用して、要求の絶対パスのアプリケーション パス以降の部分と最も一致する仮想パスを持つ仮想ディレクトリにアクセスします。

public static bool AddVirtualDirectory(string siteName, string application, string virtualDirectoryPath, string physicalPath, string userName, string password)
    {
        try
        {
            if (string.IsNullOrEmpty(siteName))
                throw new ArgumentNullException("siteName", "AddVirtualDirectory: siteName is null or empty.");
            if (string.IsNullOrEmpty(application))
                throw new ArgumentNullException("application", "AddVirtualDirectory: application is null or empty.");
            if (string.IsNullOrEmpty(virtualDirectoryPath))
                throw new ArgumentNullException("virtualDirectoryPath", "AddVirtualDirectory: virtualDirectoryPath is null or empty.");
            if (string.IsNullOrEmpty(physicalPath))
                throw new ArgumentNullException("physicalPath", "AddVirtualDirectory: physicalPath is null or empty.");
            using (ServerManager mgr = new ServerManager())
            {
                Site site = mgr.Sites[siteName];
                if (site != null)
                {
                    Application app = site.Applications[application];
                    if (app != null)
                    {
                        VirtualDirectory vDir = app.VirtualDirectories[virtualDirectoryPath];
                        if (vDir != null)
                        {
                            throw new Exception("Virtual Directory: " + virtualDirectoryPath + " already exists.");
                        }
                        else
                        {
                            vDir = app.VirtualDirectories.CreateElement();
                            vDir.Path = virtualDirectoryPath;
                            vDir.PhysicalPath = physicalPath;
                            if (!string.IsNullOrEmpty(userName))
                            {
                                if (string.IsNullOrEmpty(password))
                                    throw new Exception("Invalid Virtual Directory User Account Password.");
                                else
                                {
                                    vDir.UserName = userName;
                                    vDir.Password = password;
                                }
                            }
                            app.VirtualDirectories.Add(vDir);
                        }
                        mgr.CommitChanges();
                        return true;
                    }
                    else
                        throw new Exception("Application: " + application + " does not exist.");
                }
                else
                    throw new Exception("Site: " + siteName + " does not exist.");
            }
        }
        catch (Exception ex)
        {
            throw new Exception(ex.Message, ex);
        }
   }

FTP サイトの作成

FTP サイトは、ユーザーが Web サイトにコンテンツをアップロードするための場所です。また、FTP サイトを利用することにより、インターネットを介してファイルを移動することもできます。ユーザーは、コンテンツおよびアクセス許可を管理できます。FTP サイトの作成方法は、Web サイトの作成方法とほぼ同様です。以下のコードに示されているように、ルート アプリケーションや仮想ディレクトリを含むアプリケーション プールおよびサイトを作成し、FTP バインドを適用します。

public static bool CreateFtpSite(string applicationPoolName,string siteName, string domainName, string userName, string password,string contentPath, string ipAddress, string tcpPort, string hostHeader)
    {
        try
        {
            //provision the application pool
            using (ServerManager mgr = new ServerManager())
            {
                ApplicationPool appPool = mgr.ApplicationPools[applicationPoolName];
                //per IIS7 team recommendation, we always create a new application pool
                //create new application pool
                if (appPool == null)
                {
                    appPool = mgr.ApplicationPools.Add(applicationPoolName);
                    //set the application pool attribute
                    appPool.ProcessModel.IdentityType = ProcessModelIdentityType.SpecificUser;
                    appPool.ProcessModel.UserName = domainName + "\\" + userName;
                    appPool.ProcessModel.Password = password;
                }
                //if the appPool is null, we throw an exception. The appPool should be created or already exists.
                if (appPool == null)
                    throw new Exception("Invalid Application Pool.");
                //if the site already exists, throw an exception
                if (mgr.Sites[siteName] != null)
                    throw new Exception("Site already exists.");
                //create site
                Site newSite = mgr.Sites.CreateElement();                   
                newSite.Id = GenerateNewSiteID(mgr, siteName);
                newSite.SetAttributeValue("name", siteName);
                newSite.ServerAutoStart = true;
                mgr.Sites.Add(newSite);
                //create the default application for the site
                Application newApp = newSite.Applications.CreateElement();
                newApp.SetAttributeValue("path", "/"); //set to default root path
                newApp.SetAttributeValue("applicationPool", applicationPoolName);
                newSite.Applications.Add(newApp);
                //create the default virtual directory
                VirtualDirectory newVirtualDirectory = newApp.VirtualDirectories.CreateElement();
                newVirtualDirectory.SetAttributeValue("path", "/");
                newVirtualDirectory.SetAttributeValue("physicalPath", contentPath);
                newApp.VirtualDirectories.Add(newVirtualDirectory);
                //add the bindings 
                Binding binding = newSite.Bindings.CreateElement();
                binding.SetAttributeValue("protocol", "ftp");
                binding.SetAttributeValue("bindingInformation", ipAddress + ":" + tcpPort + ":" + hostHeader);
                newSite.Bindings.Add(binding);
                //commit the changes
                mgr.CommitChanges();
            }
        }
        catch (Exception ex)
        {
            throw new Exception(ex.Message, ex);
        }
        return true;
    }