C# 中的预配示例

作者:Walter Oliver

概述

近年来,高级 Web 用户可以很轻松地创建自己的网站。 最终用户注册其域名后,有多种类型的托管服务提供商可供选择。 Web 托管服务提供商遍布全球,可满足最终用户的需求。 但无论选择哪个托管服务提供商,注册和创建网站的方案在所有情况下都是类似的。

首先需要为用户建立新的用户帐户。 设置帐户后,最终用户将决定站点应包含哪些功能和选项:例如,磁盘空间、FTP 功能、创建虚拟目录、决定是否需要数据库等。托管服务提供商生成控制面板或仪表板应用程序,使最终用户能够创建和管理这些功能。

可通过多种方式在控制面板中实现这些功能。 在本节中,我们将了解如何通过托管代码实现这些功能的预配方面。 功能概述如下:

预配一个新用户帐户

负责管理和维护站点的站点所有者需要一个新的用户帐户。 该帐户可以是 Active Directory® 帐户,也可是本地用户帐户。 以下代码片段演示了如何创建本地帐户。 请注意,必须指定 System.DirectoryServices 命名空间。

public 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;
}

创建内容存储

网站需要在文件系统上有一个位置,用户可以将网站的内容上载到这个位置。 Microsoft® .NET Directory Class 提供了应用程序编程接口 (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® 磁盘配额管理组件的引用;该组件在 Windows\system32\dskquota.dll 下面。

public static bool AddUserDiskQuota(string userName, double quota, double quotaThreshold, string diskVolume)
{
   try
   {
      DiskQuotaControlClass diskQuotaCtrl = GetDiskQuotaControl(diskVolume);
      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;
}

创建应用程序池

应用程序池定义工作进程的设置,工作进程可承载一个或多个 Internet Information Services 7 (IIS 7) 应用程序(负责执行其请求处理)。 应用程序池是进程隔离单元,因为应用程序的所有请求处理在其应用程序池的工作进程中运行。

应用程序池还提供隔离,进而提供安全性。 每个应用程序池都可以使用唯一标识运行,并且可以使用访问控制列表 (ACL) 来阻止其他池中的应用程序访问自己的资源。 以下代码片段演示如何创建应用程序池、设置标识和设置属性。

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 命名空间分区以实现应用程序内容的结构化。

以下代码片段演示如何创建新站点。 实现 ServerManager 对象时,需要 Microsoft.Web.Administration 命名空间。 请注意,应用程序集合、网站集和绑定对象都是通过 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 支持多协议绑定(如 Windows® Communication Foundation [WCF] SOAP-TCP 和 FTP),但以下代码仅使用 HTTP 路径;HTTP 绑定有效地定义了一个 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);
    }
}

创建根应用程序

应用程序是网站功能的逻辑容器,用于将站点 URL 命名空间划分为单独的部分,并单独控制每一部分的运行时行为。

例如,可以将每个应用程序配置为驻留在单独的应用程序池中。 这会隔离应用程序,方法是将应用程序置于单独的进程中,然后选择性地使该进程以不同的 Windows 标识运行,以将其沙盒化。

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 站点能让客户将内容上传到其网站,并提供跨 Internet 移动文件的功能。 客户可以同时管理内容和访问权限。 创建 FTP 站点类似于创建网站:应用程序池和站点与根应用程序和虚拟目录一起创建,然后会应用 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;
}